ikerhurtado.com
You're in
Iker Hurtado's pro blog
Developer | Entrepreneur | Investor
Software engineer (entrepreneur and investor at times). These days doing performant frontend and graphics on the web platform at Barcelona Supercomputing Center

Notes on browser events

4 Apr 2016   |   iker hurtado  
Share on Twitter Share on Google+ Share on Facebook

The addEventListener() method

The modern way to handle browser events is using addEventListener via elements or objects. Typically, those will be a DOM elements, but they can also be the document, window, or any other object that just happens to fire events.

In addition to be called, the event listener also provides access to the underlying event object as part of its arguments. To access this event object, we use this method signature (the e argument matters).

function myEventHandler(e) {
    // event handlery stuff
}

I extract this good explanation about the event argument from this post:

The event argument points to an event object, and this object is passed in as part of the event firing (...) This event object contains properties that are relevant to the event that was fired. An event triggered by a mouse click will have different properties when compared to an event triggered by your keyboard key press, a page load, an animation, and a whole lot more. Most events will have their own specialized behavior that you will rely on, and the event object is your window into all of that uniqueness.

Despite the variety of events and resulting event objects you can get, there are certain properties that are common. This commonality is made possible because all event objects are derived from a base Event type (technically, an Interface). Some of the popular properties from the Event type that you will use are: currentTarget, target, preventDefault, stopPropagation and type.

When attached a handler function to an element, the value of this inside the handler is a reference to the element and the same as the value of the currentTarget property of the event argument.

It's worthy to note at this point that the element listening for an event doesn't need to be the target element (innermost element in DOM where the event was fired), but it's possible and very useful to get it through the event object (event.target property).

Event Capturing and Bubbling

For this key section (a deep understanding is necessary for creating advanced components) I extract almost everything from this awesome explanation:

Your click event (just like almost every other JavaScript event) does not actually originate at the element that you interacted with.

(...)

As shown in the diagram, the path your event takes is direct, but it does obnoxiously notify every element along that path. This means that if you were to listen for a click event on body, one_a, two, or three_a, the associated event handler will get fired.

Now, once your event reaches its target, it doesn't stop. (...) the event keeps going by retracing its steps and returning back to the root

(...)

One of the main things to note is that it doesn't matter where in your DOM you initiate an event. The event always starts at the root, goes down until it hits the target, and then goes back up to the root. This entire journey is very formally defined

Specifying a Phase of capturing

It's possible to chose programmatically in which phase the element listener is fired: the third addEventListener parameter (boolean) is for that. A true value indicates that the event will be fired during the capturing phase. The default behavior (the parameter is not specified) is to listen to your event during the bubbling phase(equivalent to passing in a false value).

item.addEventListener("click", doSomething, true);

We have the stopPropagation() method on the Event object to end the life of an event (will stop the capture and bubble event flow phases).

It's worthy to note that any events directly attached to the node or object will still be invoked.

The preventDefault() function

A little context on the topic:

Many HTML elements exhibit a default behavior when you interact with them. For example, clicking in a textbox gives that textbox focus with a little blinking text cursor appearing. Using your mouse wheel in a scrollable area will scroll in the direction you are scrolling. Clicking on a checkbox will toggle the checked state on or off. All of these are examples of built-in reactions to events your browser instinctively knows what to do about. If you want to turn off this default behavior, you can call the preventDefault function. This function needs to be called when reacting to an event on the element whose default reaction you want to ignore.

We don't have to mix or confuse with the stopPropagation function:

preventDefault doesn't stop further propagation of the event through the DOM. event.stopPropagation should be used for that.

More formal info: Event.preventDefault() - Web APIs | MDN

Practical example: Handling Events for Many Elements

I had problems by setting event listeners on elements (live collection) returned by the helper methods of type getElementsBy*(). It's an inefficient and dangerous practice.

The right way to achieve the same is very good explained here: Handling Events for Many Elements | kirupa.com

I extract the key paragraph about the reason for using this solution:

The other reason is that each of these elements now has their addEventListener property set. This is not a big deal for five elements. It starts to become a big deal when you have dozens or hundreds of elements each taking up a small amount of memory. The OTHER reason is that your number of elements, depending on how adapative or dynamic your UI really is, can vary.

The implementation is quite simple; the steps are:

  • Create a single event listener on the parent element.
  • When any of the children are clicked, rely on the propagation behavior that events possess and intercept them when they hit the parent element.
  • Stop the event propagation at the parent element just to avoid having to deal with the event obnoxiously running up and down the DOM tree.

In code:

theParent.addEventListener("click", doSomething, false);
 
function doSomething(e) {
    if (e.target !== e.currentTarget) { 
    // If the event source element !== the parent (currentTarget)
        var clickedItem = e.target.id;
        alert("Hello " + clickedItem);
    }
    e.stopPropagation();
}

Creating and triggering events

We are not limited to the predefined event types. It's possible to attach and invoke a custom event, using the addEventListener() method like normal in combination with document.createEvent(), initCustomEvent() , and dispatchEvent() methods. (More info about it: DOM Enlightenment - 11.12 Custom events).

Simulating an event is possible and much like creating a custom event. (More info about it: DOM Enlightenment - 11.13 Simulating/Triggering mouse events).

Concurrency model, Event Loop and Events

I liked this article about the browser Concurrency model and its relationship with event handling: Concurrency model and Event Loop - JavaScript | MDN. I extract the key part:

Each message is processed completely before any other message is processed. This offers some nice properties when reasoning about your program, including the fact that whenever a function runs, it cannot be pre-empted and will run entirely before any other code runs (and can modify data the function manipulates). This differs from C, for instance, where if a function runs in a thread, it can be stopped at any point to run some other code in another thread.

A downside of this model is that if a message takes too long to complete, the web application is unable to process user interactions like click or scroll. The browser mitigates this with the "a script is taking too long to run" dialog. A good practice to follow is to make message processing short and if possible cut down one message into several messages. Adding messages

In web browsers, messages are added any time an event occurs and there is an event listener attached to it. If there is no listener, the event is lost. So a click on an element with a click event handler will add a message--likewise with any other event.

Calling setTimeout will add a message to the queue after the time passed as second argument. If there is no other message in the queue, the message is processed right away; however, if there are messages, the setTimeout message will have to wait for other messages to be processed. For that reason the second argument indicates a minimum time and not a guaranteed time.


POST A COMMENT: