Events

Other topics

Remarks:

Origin of events

Events dont start at the thing you trigger the event on.

Events dont start at the thing you trigger the event on (a button for example).

Instead

It touches every element in its path and it inform every element that an event is happening. Events also go back up after they reach their destination, informing the elements again of its occurrence.

Capturing & Bubbling

As we learned, events start from the top of DOM tree, informs every node in its path down to its destination, then goes back up when it reaches its destination, also informing every element it touches on its way up about its occurrence.

Events going down the DOM tree are in the capturing phase, events going up the DOM tree are in the bubbling phase.

By default events are listened to in the bubbling phase. To change this you can specify which phase the event gets listened to by specifying the third parameter in the addEventListener function. (code example in the capture section)

Introduction

Definition:

In computing, an event is an action or occurrence recognized by software that may be handled by the software. Computer events can be generated or triggered by the system, by the user or in other ways. Definition Source

enter image description here

HTML events are "things" that happen to HTML elements. JavaScript can "react" on these events. via Event Listeners. Additionally, custom events can be triggered using dispatchEvent. But this is only an introduction, so lets get started!

Basic Event Listener

To listen to events, you call target.addEventListener(type, listener);

function loadImage() {
  console.log('image code here!');
}
var myButton = document.querySelector('#my-button');
myButton.addEventListener('click', loadImage);

This will trigger loadImage every time my-button is clicked.

Event listeners can be attached to any node in the DOM tree. to see a full list of all the events natively triggered in the browser: go here MDN link for full event list

Removing event listeners

The removeEventListener() method removes event handlers that have been attached with the addEventListener() method:

element.removeEventListener("mousemove", myFunction);

Everything (eventname, function, and options) in the removeEventListener must match the one set when adding the event listener to the element.

.bind with removeListener

using .bind on the function when adding an event listener will prevent the function from being removed, to actually remove the eventListener you can write:

function onEvent() {
   console.log(this.name);
}

var bindingOnEvent = onEvent.bind(this);

document.addEventListener('click', bindingOnEvent);

...

document.removeEventListener('click', bindingOnEvent);

listen to an event only once

Until once option is widely supported, we have to manually remove the even listener once the event is triggered for the first time.

This small helper will help us achieve this:

Object.prototype.listenOnce = Object.prototype.listenOnce ||
  function listenOnce(eventName, eventHandler, options) {
      var target = this;
      target.addEventListener(eventName, function(e) {
          eventHandler(e);
          target.removeEventListener(eventName, eventHandler, options);
      }, options);
  }

var target = document.querySelector('#parent');
target.listenOnce("click", clickFunction, false);

*It is not a best practice to attach functions to the Object prototype, hence you can remove the first line of this code and add a target to it as a first param.

Waiting for the document to load

One of the most commonly used events is waiting for the document to have loaded, including both script files and images. The load event on document is used for this.

document.addEventListener('load', function() {
   console.log("Everything has now loaded!");
});

Sometimes you try to access a DOM object before it is loaded, causing null pointers. These are really tough to debug. To avoid this use document's DOMContentLoaded event instead. DOMContentLoaded ensures that the HTML content has been loaded and initialized without waiting for other external resources.

document.addEventListener('DOMContentLoaded', function() {
   console.log("The document contents are now available!");
});

Event Object

To access the event object, include an event parameter in the event listener callback function:

var foo = document.getElementById("foo");
foo.addEventListener("click", onClick);

function onClick(event) {
  // the `event` parameter is the event object
  // e.g. `event.type` would be "click" in this case
};

e.stopPropagation();

HTML:

<div id="parent">
   <div id="child"></div>
</div>

Javascript:

var parent = document.querySelector('#parent');
var child = document.querySelector('#child');

child.addEventListener('click', function(e) {
   e.stopPropagation();
   alert('child clicked!');
});

parent.addEventListener('click', function(e) {
   alert('parent clicked!');
});

since the child stops the event propagation, and the events are listened to during bubbling phase, clicking on the child will only trigger the child. without stopping the propagation both events will be triggered.


e.preventDefault();

The event.preventDefault() method stops the default action of an element from happening.

For example:

  • Prevent a submit button from submitting a form
  • Prevent a link from following the URL
var allAnchorTags = document.querySelector('a');

allAnchorTags.addEventListener('click', function(e){
    e.preventDefault();
    console.log('anchor tags are useless now! *evil laugh*');
});

e.target vs e.currentTarget

e.currentTarget Identifies the current target for the event, as the event traverses the DOM. It always refers to the element the event handler has been attached to as opposed to event.target which identifies the element on which the event occurred.

in other words

e.target will return what triggers the event dispatcher to trigger

e.currentTarget will return what you assigned your listener to.

HTML:

<body>
   <button id="my-button"></button>
</body>

Javascript:

var body = document.body;
body.addEventListener( 'click', function(e) {
    console.log('e.target', e.target);
    console.log('e.currentTarget', e.currentTarget);
});

if you click my-button,

  • e.target will be my-button
  • e.currentTarget will be body

Event Bubbling and Capturing

Events fired on DOM elements don't just affect the element they're targeting. Any of the target's ancestors in the DOM may also have a chance to react to the event. Consider the following document:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
  <p id="paragraph">
    <span id="text">Hello World</span>
  </p>
</body>
</html>

If we just add listeners to each element without any options, then trigger a click on the span...

document.body.addEventListener('click', function(event) {
  console.log("Body clicked!");
});
window.paragraph.addEventListener('click', function(event) {
  console.log("Paragraph clicked!");
});
window.text.addEventListener('click', function(event) {
  console.log("Text clicked!");
});

window.text.click();

...then the event will bubble up through each ancestor, triggering each click handler on the way:

Text clicked!
Paragraph clicked!
Body clicked!

If you want one of your handlers to stop the event from triggering any more handlers, it can call the event.stopPropagation() method. For example, if we replace our second event handler with this:

window.paragraph.addEventListener('click', function(event) {
  console.log("Paragraph clicked, and that's it!");
  event.stopPropagation();
});

We would see the following output, with body's click handler never triggered:

Text clicked!
Paragraph clicked, and that's it!

Finally, we have the option to add event listeners that trigger during "capture" instead of bubbling. Before an event bubbles up from an element through its ancestors, it's first "captured" down to the element through its ancestors. A capturing listener is added by specifying true or {capture: true} as the optional third argument to addEventListener. If we add the following listeners to our first example above:

document.body.addEventListener('click', function(event) {
  console.log("Body click captured!");
}, true);
window.paragraph.addEventListener('click', function(event) {
  console.log("Paragraph click captured!");
}, true);
window.text.addEventListener('click', function(event) {
  console.log("Text click captured!");
}, true);

We'll get the following output:

Body click captured!
Paragraph click captured!
Text click captured!
Text clicked!
Paragraph clicked!
Body clicked!

By default events are listened to in the bubbling phase. To change this you can specify which phase the event gets listened to by specifying the third parameter in the addEventListener function. (To learn about capturing and bubbling, check remarks)

element.addEventListener(eventName, eventHandler, useCapture)

useCapture: true means listen to event when its going down the DOM tree. false means listen to the event while its going up the DOM tree.

window.addEventListener("click", function(){alert('1: on bubble')}, false);
window.addEventListener("click", function(){alert('2: on capture')}, true);

The alert boxes will pop up in this order:

  • 2: on capture
  • 1: on bubble

Real-world use cases

Capture Event will be dispatch before Bubble Event, hence you can ensure than an event is listened to first if you listen to it in its capture phase.

if you are listening to a click event on a parent element, and another on its child, you can listen to the child first or the parent first, depending on how you change the useCapture parameter.

in bubbling, child event gets called first, in capture, parent first

in bubbling, child event gets called first, in capture, parent first

HTML:

<div id="parent">
   <div id="child"></div>
</div>

Javascript:

child.addEventListener('click', function(e) {
   alert('child clicked!');
});

parent.addEventListener('click', function(e) {
   alert('parent clicked!');
}, true);

Setting true to the parent eventListener will trigger the parent listener first.

Combined with e.stopPropagation() you can prevent the event from triggering the child event listener / or the parent. (more about that in the next example)

Event Delegation

Event delegation is a process which allow us to avoid adding event listeners to specific nodes; instead, the event listener is added to parent node. This mechanism utilizes the event propagation/bubbling to handle an event at a higher level element/node in the DOM instead of using the element on which the event was originated. For example, think we need to add events for the following list elements:

<ul id="container">
    <li id="item-1" class="new">Item 1</li>
    <li id="item-2">Item 2</li>
    <li id="item-3">Item 3</li>
</ul>

We need to add click handlers and basically, we can add listeners to each element using a loop but imagine that, we want to add elements dynamically. So, we register all the event handlers when the DOM is loaded and after the DOM initializes and registers all the event handlers for each element, the newly inserted element into the above UL will not respond on click because that element was not present in the DOM when we've registered the click event listeners.

So, to overcome this problem, we may leverage the event delegation. Which means, instead of registering the listeners to each li elements themselves, we can bind the event listener to it's parent UL element for example:

document.getElementById("container").addEventListener("click", function(e) {
    console.log("List item " e.target.id, " was clicked!");
});

Since, the event propagates (bubbles upwards) by default, then clicking on any LI element will make the UL element to fire the same event as well. In this case, we can use the e parameter in the function, which is actually the event object and it carries helpful information about the event including the original element, which initiated the event. So, for example, we can use something like the following:

document.getElementById("container").addEventListener("click", function(e) {

    // If UL itself then no action is require
    if(e.target.nodeName == 'UL') return false;

    if(e.target.classList.contains('new')) {
        console.log("List item " e.target.id, " was clicked and it's new!");
    }
});

So, it's obvious that, e (Event Object) allow us to examine the source element (e.target) and we can easily inject new elements to the UL after DOM is loaded and the only one delegated event handler will handle all the click events within the parent UL which is also less memory consuming because we declared only one function for all the elements.

Triggering custom events

The CustomEvent API allows developers to create custom events and trigger them on DOM nodes, passing data along the way.

event = new CustomEvent(typeArg, customEventInit);

typeArg - DOMString representing the name of the event.

customEventInit - is optional parameters (that will be passed as e in following example).

You can attach eventListeners to document or any HTML element.

Once custom event has been added and bound to element (or document) one might want to manually fire it from javascript.

document.addEventListener("event-name", function(e) {
  console.log(e.detail); // logs custom object passed from the event.
});

var event = new CustomEvent("event-name", { "param-name": "param-value" });
document.dispatchEvent(event);

Parameters:

ParameterDescription
typeString defines the name of the event to listen to.
listenerFunction triggers when the event occurs.
optionsBoolean to set capture, if Object you can set the following properties on it, notice that the object option is weakly supported.
1. captureA Boolean that indicates that events of this type will be dispatched to the registered listener before being dispatched to any EventTarget beneath it in the DOM tree.
2. onceA Boolean indicating that the listener should be invoked at most once after being added. If it is true, the listener would be removed automatically when it is invoked.
3. passiveA Boolean indicating that the listener will never call preventDefault(). If it does, the user agent should ignore it and generate a console warning.

Contributors

Topic Id: 5388

Example Ids: 1033,1034,1035,1247,1344,12614,13071

This site is not affiliated with any of the contributors.