Events in JavaScript are actions or occurrences that happen in the browser, such as clicking a button, scrolling a web page, or pressing a key on the keyboard. JavaScript can detect and respond to these events by executing a specific block of code, allowing developers to create interactive and dynamic web applications.
JavaScript provides a set of built-in events that can be used to capture user input and interaction with a web page, such as:
- Mouse events: click, dblclick, mousedown, mouseup, mousemove, mouseover, mouseout
- Keyboard events: keydown, keyup, keypress
- Form events: submit, reset, change, focus, blur
- Window events: load, unload, resize, scroll
To respond to an event, you can use an event listener, which is a function that is triggered when the specified event occurs. In JavaScript, you can add event listeners to an element using the addEventListener
method.
For example, the following code adds an event listener to a button element that logs a message to the console when the button is clicked:
const button = document.querySelector('button');
button.addEventListener('click', () => {
console.log('Button clicked');
});
This is just a simple example, but event handling in JavaScript can become quite complex and powerful, allowing developers to create highly interactive and responsive web applications.
Readings:
In JavaScript, there are several ways to listen to browser events, depending on the context in which the events are being used. Here are some common ways to listen to browser events:
-
Using the
addEventListener
method - This is the most common way of listening to events in the browser. It allows you to attach an event listener to a specific DOM element and specify the type of event you want to listen to. Here's an example:const button = document.querySelector('button'); button.addEventListener('click', () => { console.log('Button clicked'); });
-
Using the
on
property - Some DOM elements have an on property that can be used to attach an event listener. This method is less common than addEventListener, but it can be useful in certain situations. Here's an example:const input = document.querySelector('input'); input.onchange = () => { console.log('Input value changed'); };
-
Using inline event handlers - This method involves adding event handlers directly to HTML elements using the
onclick
,onchange
, or other similar attributes. This method is considered outdated and not recommended, but it is still supported in modern browsers. Here's an example:<button onclick="console.log('Button clicked')">Click me</button>
-
Using the
event
object - When an event is triggered, the browser creates an event object that contains information about the event. You can access this object and use it to perform additional operations or modify the default behavior of the event. Here's an example:const link = document.querySelector('a'); link.addEventListener('click', (event) => { event.preventDefault(); console.log('Link clicked'); });
In this example, we prevent the default behavior of the click
event (which is to navigate to the link URL) using the preventDefault()
method on the event
object.
Overall, the addEventListener
method is the preferred way of listening to browser events in modern JavaScript, as it provides more flexibility and better separation of concerns. However, the other methods may still be useful in certain situations.
The removeEventListener()
method is used to remove an event listener that was previously added to a DOM element using the addEventListener()
method. It takes two arguments: the type of the event to remove (as a string), and the callback function that was used to handle the event.
Here's an example that demonstrates how to use removeEventListener()
to remove an event listener:
// Get the button element
const button = document.querySelector('button');
// Define the callback function
function handleClick() {
console.log('Button clicked');
}
// Add the event listener
button.addEventListener('click', handleClick);
// Remove the event listener after 3 seconds
setTimeout(() => {
button.removeEventListener('click', handleClick);
}, 3000);
In this example, we start by getting a reference to a button element on the page using the document.querySelector()
method. We then define a callback function called handleClick()
that logs a message to the console when the button is clicked.
Next, we use the addEventListener()
method to attach the handleClick()
function to the button element as a click event listener.
Finally, we use the setTimeout()
method to remove the event listener after 3 seconds using the removeEventListener()
method. This ensures that the event listener will only be active for a limited period of time and will not continue to consume resources unnecessarily.
Note that the second argument of removeEventListener()
must be the same function that was used to add the event listener. If you pass a different function, the event listener will not be removed.
While removeEventListener()
is a powerful and useful method for removing event listeners from DOM elements, there are a few pitfalls that you should be aware of when using it:
-
Event listeners must be added and removed with the same function reference: As mentioned in the previous example, the
removeEventListener()
method requires the same function reference that was used to add the event listener. If you pass a different function reference, the event listener will not be removed. -
Event listeners should not be anonymous functions: When you add an event listener to a DOM element using an anonymous function, there is no easy way to remove the listener later. This is because you cannot reference an anonymous function by name. To remove an anonymous function event listener, you must use a named function or a reference to the function.
const button = document.querySelector('button'); // Adding anonymous function event listener button.addEventListener('click', function() { console.log('Button clicked'); }); // Attempting to remove the event listener - won't work! button.removeEventListener('click', function() { console.log('Button clicked'); });
In this example, an anonymous function is used to add an event listener to a button element. However, because the function is anonymous, it cannot be removed later using the
removeEventListener()
method. Therefore, the call tobutton.removeEventListener()
has no effect. To avoid this pitfall, you should use named functions to add and remove event listeners, like this:const button = document.querySelector('button'); // Adding named function event listener function handleClick() { console.log('Button clicked'); } button.addEventListener('click', handleClick); // Removing named function event listener button.removeEventListener('click', handleClick);
-
Removing an event listener that wasn't added:
const button = document.querySelector('button'); // Adding event listener function handleClick() { console.log('Button clicked'); } button.addEventListener('click', handleClick); // Removing the event listener using a different function reference - won't work! function handleOtherClick() { console.log('Other click event'); } button.removeEventListener('click', handleOtherClick);
In this example, a different function reference is used to attempt to remove an event listener that was added earlier. Because the function reference is different, the call to
button.removeEventListener()
has no effect.To avoid this pitfall, you should ensure that you are using the same function reference to add and remove event listeners.
-
Removing event listeners can impact performance: While removing event listeners can be a useful way to optimize your code, it can also impact performance if you do it too frequently. Removing and re-adding event listeners repeatedly can cause unnecessary memory allocation and deallocation, which can slow down your application. Therefore, it's important to only remove event listeners when they are no longer needed.
const button = document.querySelector('button'); function handleClick() { console.log('Button clicked'); } // Adding event listener button.addEventListener('click', handleClick); // Removing event listener after a delay setTimeout(() => { button.removeEventListener('click', handleClick); }, 1000); // Re-adding event listener after a delay setTimeout(() => { button.addEventListener('click', handleClick); }, 2000);
In this example, the event listener is removed and then re-added after a delay. However, this is not an efficient way to handle events because it requires unnecessary memory allocation and deallocation. Instead, you should only remove event listeners when they are no longer needed.
To avoid this pitfall, you should be mindful of how often you are adding and removing event listeners and ensure that you only remove them when they are no longer needed.
Readings:
The Event
Object
An Event
object is created whenever an event is triggered, such as a button click or a form submission. The Event
object contains information about the event, such as the type of event, the target element that triggered the event, and any additional data associated with the event.
Here's an example of using the Event
object to log information about a button click event:
// Get the button element
const button = document.querySelector('button');
// Add a click event listener to the button
button.addEventListener('click', function(event) {
// Log information about the event
console.log('Event type:', event.type);
console.log('Target element:', event.target);
});
In this example, the addEventListener()
method is used to add a click event listener to a button element. The second argument to the addEventListener()
method is an anonymous function that takes an Event object as its argument.
Inside the anonymous function, information about the Event
object is logged to the console. The event.type
property is used to log the type of event (in this case, 'click'
), and the event.target
property is used to log the target element that triggered the event (in this case, the button element).
By using the Event
object, you can access a variety of properties and methods to help you handle events in JavaScript. For example, you can use the event.preventDefault()
method to prevent the default behavior of an event (such as submitting a form), or you can use the event.stopPropagation()
method to stop an event from propagating up the DOM tree.
Readings:
There are many other event types exists other than click
event. For example,
-
The
mouseenter
event is a type of mouse event that is triggered when the mouse cursor enters a specific element. This event is similar to themouseover
event, but with a few key differences. Specifically, themouseenter
event does not bubble up the DOM tree like themouseover
event does, and it only triggers when the mouse cursor enters the element itself, not any of its child elements.Here's an example of using the
mouseenter
event in JavaScript:<div id="box">Mouse over me!</div>
const box = document.getElementById('box'); box.addEventListener('mouseenter', function(event) { box.style.backgroundColor = 'blue'; }); box.addEventListener('mouseleave', function(event) { box.style.backgroundColor = 'white'; });
In this example, we first get a reference to a
div
element with the ID ofbox
. We then add two event listeners to this element using theaddEventListener()
method. The first event listener listens for themouseenter
event and changes the background color of thediv
element to blue when triggered. The second event listener listens for themouseleave
event and changes the background color back to white when triggered.When the mouse cursor enters the
div
element, themouseenter
event is triggered, and the background color of thediv
element is changed to blue. When the mouse cursor leaves thediv
element, themouseleave
event is triggered, and the background color is changed back to white.Note that the
mouseenter
event is only triggered when the mouse cursor enters thediv
element itself, and not any of its child elements. This can be useful in cases where you want to trigger an event only when the mouse cursor enters a specific area of an element, and not when it enters any of its child elements. -
The
scroll
event is triggered when an element's scrollbar is scrolled, either by using the mouse wheel, trackpad, arrow keys, or other methods.Here's an example of using the
scroll
event in JavaScript:<div id="container" style="height: 300px; overflow-y: scroll;"> <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce vehicula ipsum ac eros varius, sit amet bibendum arcu venenatis. Sed sollicitudin, purus vitae placerat ullamcorper, nisi tortor semper mauris, quis tempor lectus est et metus. Nullam ut orci quis velit ullamcorper pretium. Nam tristique placerat leo, ac aliquam dolor pretium quis. </p> <p> Nullam sed nulla euismod, laoreet metus sed, luctus mi. Vestibulum sed feugiat libero, quis varius ex. Vivamus eleifend justo ut turpis pellentesque, vitae ullamcorper dolor scelerisque. Sed non eros vel velit consectetur posuere. In hac habitasse platea dictumst. Fusce porttitor finibus turpis vel rutrum. </p> <p> Donec in massa justo. Integer a aliquet nisl. Nam ultrices lacus et leo maximus, at semper turpis fringilla. Ut sed elit quam. Pellentesque accumsan auctor ipsum, nec molestie nibh. Nullam ac nulla nunc. Proin lacinia justo et ligula congue, eget consequat ex varius. </p> </div>
const container = document.getElementById('container'); container.addEventListener('scroll', function(event) { console.log('Scroll position:', container.scrollTop); });
Let's have fun with the
scroll
event and create a list which you canscroll
infinitely (explanations below)!You can run this code snippet on any page, just make sure that you can scroll vertically (either by adding enough dummy content, by adding some styles that add a lot of height to some elements or by shrinking the browser window vertically).
let curElementNumber = 0; function scrollHandler() { const distanceToBottom = document.body.getBoundingClientRect().bottom; if (distanceToBottom < document.documentElement.clientHeight + 150) { const newDataElement = document.createElement('div'); curElementNumber++; newDataElement.innerHTML = `<p>Element ${curElementNumber}</p>`; document.body.append(newDataElement); } } window.addEventListener('scroll', scrollHandler);
So what's happening here?
At the very bottom, we register the
scrollHandler
function as a handler for thescroll
event on our window object.Inside that function, we first of all measure the total distance between our viewport (top left corner of what we currently see) and the end of the page (not just the end of our currently visible area) => Stored in
distanceToBottom
.For example, if our browser window has a height of
500px
, then distanceToBottom could be684px
, assuming that we got some content we can scroll to.Next, we compare the distance to the bottom of our overall content (
distanceToBottom
) to the window height + a certain threshold (in this example150px
).document.documentElement.clientHeight
is preferable towindow.innerHeight
because it respects potential scroll bars.If we have less than
150px
to the end of our page content, we make it into the if-block (where we append new data).Inside of the if-statement, we then create a new
<div>
element and populate it with a<p>
element which in turn outputs an incrementing counter value.
To know which event types are applicable to a specific DOM element, you can check the official documentation or use the getEventListeners()
method in the browser console.
Here are the steps to use getEventListeners()
method in the browser console:
- Open the browser console by pressing F12 or right-clicking on the webpage and selecting "Inspect" or "Inspect Element".
- Select the "Elements" or "Inspector" tab.
- Click on the element that you want to check the events for.
- In the right-hand panel, select the "Event Listeners" tab.
- The list of event types and their corresponding event listeners will be displayed.
Alternatively, you can refer to the official documentation of the DOM element to check the event types it supports. For example, the official documentation of the input
element lists the following events: input
, change
, focus
, blur
, select
, keydown
, keyup
, keypress
.
Checkout all other events types here
Working with preventDefault()
preventDefault()
is a method in the DOM that is used to prevent the default behavior of an event. For example, if you want to prevent a form from submitting when a submit button is clicked, you can use preventDefault()
method to stop the default form submission behavior.
Here's an example of using preventDefault()
method to prevent a form from submitting:
<form id="myForm">
<input type="text" name="username">
<button type="submit">Submit</button>
</form>
const form = document.getElementById('myForm');
form.addEventListener('submit', function(event) {
event.preventDefault();
console.log('Form submitted');
});
In this example, we first create a form
element with an input field and a submit button. We then add an event listener to the form element using the addEventListener()
method with the submit
event.
Inside the event listener function, we call the preventDefault()
method on the event
object to prevent the default form submission behavior. We also log a message to the console to indicate that the form was submitted.
Now, when the user clicks on the submit button, the submit
event is triggered, but the default form submission behavior is prevented, and only the message is logged to the console.
preventDefault()
method can be useful in many scenarios, such as preventing links from navigating to another page, preventing form submissions, and preventing the default behavior of keyboard shortcuts.
Readings:
When an event is triggered on an element in the DOM, the event goes through two phases: the capturing phase and the bubbling phase. These phases determine the order in which event handlers are executed when an event is triggered on an element that is a descendant of another element.
In the capturing phase, the event starts from the top of the DOM tree and moves down to the target element. In the bubbling phase, the event starts from the target element and moves up to the top of the DOM tree. During each phase, event handlers can be executed on each element.
Here's an example that illustrates the capturing and bubbling phases:
<div id="outer">
<div id="inner">
Click me
</div>
</div>
const outer = document.getElementById('outer');
const inner = document.getElementById('inner');
// Demonstrating Capturing Event
outer.addEventListener('click', function() {
console.log('Capturing phase - outer');
}, true);
// Demonstrating Capturing Event
inner.addEventListener('click', function() {
console.log('Capturing phase - inner');
}, true);
// Demonstrating Bubbling Event
outer.addEventListener('click', function() {
console.log('Bubbling phase - outer');
});
// Demonstrating Bubbling Event
inner.addEventListener('click', function() {
console.log('Bubbling phase - inner');
});
In this example, we have an outer and an inner div
element. We add event listeners to each element to log messages to the console during the capturing and bubbling phases of the click
event.
When the user clicks on the inner div
element, the following messages will be logged to the console in order:
Capturing phase - outer
Capturing phase - inner
Bubbling phase - inner
Bubbling phase - outer
As we can see, during the capturing phase, the event handlers are executed in the order of the DOM tree hierarchy, from top to bottom. During the bubbling phase, the event handlers are executed in the opposite order, from bottom to top.
It's important to note that not all events have both capturing and bubbling phases. For example, the focus
and blur
events do not have a capturing phase. The order of event handlers for these events is determined solely by the order in which they are attached to the element.
Readings:
Event Propagation and stopPropagation()
Now, this entire process of having multiple listeners for the same event because the event does not just trigger on the element itself but also on ancestors, that's called propagation.
Event propagation is the process of propagating an event from its source to its target in the Document Object Model (DOM) tree. Event propagation can occur in two ways: bubbling and capturing which we have discussed earlier.
Event propagation can be controlled by calling the stopPropagation()
method on the event object. When this method is called, the event is prevented from propagating any further. For example, if a button is clicked and the stopPropagation()
method is called on the event object inside one of the event listeners, the event will not be triggered on any parent elements of the button.
Here's an example that demonstrates event propagation:
<div id="outer">
<div id="inner">
<button id="button">Click me</button>
</div>
</div>
const outer = document.getElementById('outer');
const inner = document.getElementById('inner');
const button = document.getElementById('button');
outer.addEventListener('click', function() {
console.log('Outer element clicked');
});
inner.addEventListener('click', function() {
console.log('Inner element clicked');
});
button.addEventListener('click', function(event) {
console.log('Button clicked');
event.stopPropagation();
});
In this example, we have an outer div
element, an inner div
element, and a button element. We add event listeners to each element to log messages to the console when they are clicked. We also call the stopPropagation()
method on the event object when the button is clicked.
When the user clicks on the button, the following messages will be logged to the console:
Button clicked
As we can see, the click
event first triggers on the button, However, because we called the stopPropagation()
method inside the event listener for the button, the event is not propagated any further up the DOM tree.
The stopImmediatePropagation()
method is similar to stopPropagation()
, but it not only stops the propagation of the current event, it also prevents any further listeners of the same event from being executed.
Here's an example:
<!DOCTYPE html>
<html lang="en">
<head>
<title>
stopPropagation() & stopImmediatePropagation()
</title>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
#one {
width: 300px;
height: 300px;
background-color: orange;
}
#two {
width: 200px;
height: 200px;
background-color: lightblue;
}
#three {
width: 100px;
height: 100px;
background-color: pink;
}
</style>
</head>
<body>
<h1>Event Methods</h1>
<div id="one"> 1
<div id="two"> 2
<div id="three">
3
</div>
</div>
</div>
<script>
function div1(e) {
this.style.backgroundColor = 'red';
}
function div2(e) {
this.style.backgroundColor = 'blue';
e.stopPropagation(); // Stop the event from bubbling up to parent elements
// e.stopImmediatePropagation(); // Stop the event from bubbling up to parent elements and prevent other event handlers from being called
}
function div2_2(e) {
this.style.width = '250px';
}
function div3(e) {
this.style.backgroundColor = 'green';
}
const divOne = document.getElementById("one");
const divTwo = document.getElementById("two");
const divThree = document.getElementById("three");
divOne.addEventListener('click', div1);
divTwo.addEventListener('click', div2);
divTwo.addEventListener('click', div2_2);
divThree.addEventListener('click', div3);
</script>
</body>
</html>
In this example, we have three nested div
elements with different background colors. When we click on the innermost div (with id "three"), the click
event will be triggered and will propagate up to its parent elements (divs with ids "two" and "one"). We have attached event listeners to each of these elements, which will change their background colors when the click
event is triggered.
The event listener attached to the div with id "two" uses the stopPropagation()
method to stop the event from bubbling up to its parent element (div with id "one"). As a result, the event listener attached to the div with id "one" is not triggered, and its background color remains unchanged.
If we replace the stopPropagation()
method with stopImmediatePropagation()
, then the event listener attached to the div with id "two" will also prevent other event handlers from being called. In this case, the second event listener attached to the div with id "two" will not be triggered, and the width of the div with id "two" will not be changed.
Note Some events in the DOM do not propagate. These events are known as non-bubbling events or non-propagating events. These events cannot be captured or bubble up the DOM tree. Examples of non-bubbling events include focus, blur, load, unload, reset, submit, change, input etc. Check official docs for more information.
Readings:
Event delegation is a technique in JavaScript where instead of attaching event listeners to each individual element in the DOM, you attach a single event listener to a parent element and use it to handle events that occur on its child elements.
When an event occurs on a child element, it first bubbles up through the DOM tree until it reaches the parent element. The parent element can then use event delegation to handle the event by checking the target
property of the event object to determine which child element triggered the event. This allows you to handle events on dynamically created elements that were not present in the DOM when the page first loaded.
Here is an example of event delegation in action:
<ul id="list">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
<button id="add-item">Add Item</button>
<script>
const list = document.getElementById('list');
list.addEventListener('click', (event) => {
if (event.target.nodeName === 'LI') {
console.log(`Clicked on ${event.target.textContent}`);
}
});
const addItemButton = document.getElementById('add-item');
addItemButton.addEventListener('click', () => {
const newItem = document.createElement('li');
newItem.textContent = `Item ${list.children.length + 1}`;
list.appendChild(newItem);
});
</script>
In this example, we have a list of items and a button to add new items to the list. Instead of attaching a click event listener to each individual list item, we attach a single event listener to the parent ul
element using event delegation.
When the click
event occurs on a list item, the event bubbles up to the ul
element, which uses event delegation to handle the event. It checks the target
property of the event object to determine which list item was clicked and logs a message to the console.
When the "Add Item" button is clicked, a new list item is created and appended to the ul
element. Because the event listener is attached to the ul
element, it automatically handles events on the new list item without requiring any additional event listener attachment.
Readings:
There may be situations where you not only need to listen to an event, but also trigger an event programmatically. An example will be demonstrated of such scenario in later modules. For now, let's take a glimpse of how this can be done.
Let's say from an event, there is a need to trigger another event. For example,
<!DOCTYPE html>
<html>
<head>
<title>Triggering DOM Events Programatically</title>
</head>
<body>
<button id="know-me">Click to know me!</button>
<button id="trigger">I click another buttons</button>
<script>
const knowMeBtn = document.getElementById('know-me');
const triggerBtn = document.getElementById('trigger');
knowMeBtn.addEventListener('click', () => {
console.log("Hello! My name is Foo and I work at bar ;D");
});
triggerBtn.addEventListener('click', () => {
console.log('Hehehehe! I click other buttons...');
knowMeBtn.click();
});
</script>
</body>
</html>
It has two buttons - "Click to know me!" and "I click other buttons" - and a script that adds an event listener to each button. When the "Click to know me!" button is clicked, it logs a message to the console. When the "I click other buttons" button is clicked, it logs a different message to the console and then programmatically triggers a click event on the "Click to know me!" button using the click()
method. Overall, the code works as expected.
When an event is triggered and an event handler function is called, the this
keyword inside the function refers to the DOM element that the event was triggered on. This is useful when we want to access or manipulate properties of the element inside the event handler.
For example, suppose we have a button element with an event listener that changes its background color to blue when clicked:
<button id="myButton">Click me!</button>
<script>
const myButton = document.getElementById('myButton');
myButton.addEventListener('click', function() {
this.style.backgroundColor = 'blue';
});
</script>
In this example, the this
keyword inside the event handler function refers to the button element, so we can access and manipulate its style
property to change its background color.
In case of nested DOM elements, the value of this
inside an event handler function will depend on how the function is called. If the event handler function is bound to the innermost element, this
will refer to that element. If it is bound to an ancestor element that contains the nested elements, this
will refer to the ancestor element.
For example, consider the following HTML structure:
<div class="outer">
<div class="inner"></div>
</div>
If an event handler is bound to the div.outer
element, and the user clicks on the div.inner
element, the value of this
inside the event handler will still refer to the div.outer
element. If the event handler is bound to the div.inner
element, this
will refer to the div.inner
element.
It is important to keep in mind the event bubbling and capturing phase as well when considering the value of this
in nested elements.
It's important to note that when using arrow functions as event handler functions, the this
keyword behaves differently. Arrow functions inherit the this
value of the enclosing lexical scope, which is often the global object or the object that the arrow function is defined in. Therefore, if we use an arrow function as an event handler, the this
keyword will not refer to the DOM element that the event was triggered on.
-
To make elements draggable:
-
Listen to the
dragstart
event on the draggable element, which is triggered when a user starts dragging the element.- In the event listener, you can interact with the event object to describe the drag operation, such as copying or moving.
- You can also append data to the event to make sure that the drag and drop operation works together.
-
To mark the areas where an item can be dropped, add an event listener to the element where the other element can be dropped:
- Add a listener to the
dragenter
anddragover
events. - Call
preventDefault()
in the event listeners to allow a drop operation. - Optionally listen to the
dragleave
event if you want to update the UI.
- Add a listener to the
-
Listen to the
drop
event on the same element where you listened todragenter
anddragover
.- The drop event is only triggered if you prevented the default in dragenter and dragover, and the item is then dropped onto that same element.
- In the
drop
event, you can do whatever you want to do upon a drop.
-
Optionally, listen to the
dragend
event on the dragged element itself, where you could update the UI or some data.- The
dragend
event is always fired even if the drop is cancelled. - You will get some property on the event object which tells you whether the drop was successful or not.
- The
You can check whether a drop
operation succeeded or not by listening to the drop
event and checking the dataTransfer.dropEffect
property of the event object.
When the drop event fires, the dataTransfer.dropEffect
property will contain one of the following string values:
- "none" if the drop operation was not allowed on the drop target
- "copy" if the dragged item was copied to the drop target
- "move" if the dragged item was moved to the drop target
- "link" if the dragged item was linked to the drop target
You can use this value to determine whether the drop operation succeeded or not and take the appropriate action in your code.
Some Useful Attributes and Methods:
Readings: