Dylan Barnard    About    Archive    Projects

5 Questions Towards a Crystal Clear Understanding of the JS Event Object

A clear crystal 1 I need to ask a lot of questions to really understand something

The ability to execute code in a browser environment has made JavaScript one of the most important programming languages of the day. One interesting thing about application code on the browser is that it usually needs to wait for something to transpire first - such as a user’s click. An event object represents information related to how, what, and when this occurrence happened. When I ran into the concept of events I wanted to be sure I was pretty clear on the principles that underly JavaScript’s ability to create, register, listen, and execute event-based code.

Below are some questions, and hopefully answers, that mirror some of the confusions I had about JavaScript events.

1) How exactly does the target property get set?

Here is the definition of the target property from the MDN documentation.

A reference to the object that dispatched the event

Ok, seems straightforward. You click on something and that’s the element which dispatches the event. My follow up question was - if I add an event listener to a parent element, will the inner element or the parent element dispatch the event?

A visual representation usually helps me, so I made the simplest one I could. In this example, I attached an event listener to the div.button-container element, outlined in red.

document.addEventListener('DOMContentLoaded', function() {
  document.querySelector('.button-container').addEventListener('click', logger);
});

The event listener is listening for the built-in click event, so try clicking on both the button and div to see what happens.

See the Pen Bound container - target by Dylan (@dylankb) on CodePen.

You’ll notice that the target element changes depending on which of the two elements you click, regardless of whether it is nested or not.

Referencing a description for the click event’s target property helped me think about how the target property is actually determined.

The event target (the topmost target in the DOM tree).

A small side quibble. Most representations of DOM “upside-down”, in that nested elements make up the branches that extend downwards. Here’s a visual of what I’m talking about.

DOM tree node visual using button and container from post 2

Note that the button element is “lower” than the div. Based on how this tree is represented, I’m interpreting “topmost target” to actually mean “bottom-most”, as that would make sense - the button element is the farthest down and is the one that is being set to target on button click. “Top” or “above” usually means towards the “root”. Anyways, quibble out.

Another way of describing how the target property is determined is on this page comparing different event targets.

The DOM element on the left-hand side of the call that triggered this event, eg: element.dispatchEvent(event)`

There may be some further implementation details that could help us determine which element calls the dispatchEvent, but that may be a bit too low level for the purposes of application development.

TLDR; In the example of the click event, the element you click on should reliably be the target property.

2) How do the target and currentTarget properties vary under different settings?

First, let’s look into how the currentTarget is determined. Here’s the definition from the docs:

… It always refers to the element to which the event handler has been attached, as opposed to event.target which identifies the element on which the event occurred.

The definition is pretty straight forward, but a coded visual always helps:

See the Pen Bound container - targets by Dylan (@dylankb) on CodePen.

Whether we click on the button or the div, the currentTarget is always the div. That makes sense, as the div is “the element to which the event handler” has been attached.

Going back to the definition of currentTarget, here’s a preceding sentence that I left out earlier:

Identifies the current target for the event, as the event traverses the DOM.

Ah, interesting. More information begs more questions…

3) What are the implications of event DOM traversal?

So, let’s briefly overview what capturing and bubbling are to make sure we’re on the same page.

There are many overviews of DOM event traversal, but this one is mine

Elements, windows, documents and more all implement the EventTarget interface which allows developers to add EventListeners (usually) via the addEventListener method which specifies an event type and a callback. When an element dispatches an event all parent elements have the event fire in a capturing phase, the target fires in the target phase, and parent elements again fire in the bubbling phase. The listener by default only executes the event handler code in the target and bubbling stages.

Ok, so with that in mind, let’s create a second separate event handler function buttonLogger that we’ll attach to our button element. The functionality is entirely the same - all that’s different is it prepends “INNER” to all our previous output.

See the Pen Bound container - bubbling by Dylan (@dylankb) on CodePen.

Hmm, when clicking on button our output appears to be the same as before - it’s as if nothing seems to have changed. What’s happened is we’ve created a subtle bug. Our buttonLogger event handler is in fact firing, the problem is that our logger event handler ALSO fires as the click event travels up through each successive parent element until reaching the window.

In this case, stopping the continued propagation of the click event is the solution. Comment out line 2 in the Codepen to see a working solution.

4) Is this equal to currentTarget or target inside event handler callback functions?

The answer is by default - sometimes both, but almost always this is the same as currentTarget.

Let’s talk about why this is. This page contains the first explanation on how the this binding is set.

When the event handler is invoked, the this keyword inside the handler is set to the DOM element on which the handler is registered.

The is explanation is fairly understandable; this will be set to the element with the attached event listener. When using default bubbling, sometimes the element with the attached listener also dispatches the event to become the target (i.e clicking on the container div), and sometimes a nested element dispatches the event (i.e the button). That’s why the answer is “sometimes both”.

Let’s log out the this context to confirm our understanding.

See the Pen Bound container - this by Dylan (@dylankb) on CodePen.

Confirmed. In this case, the event listener was attached to the div. Therefore within the callback, div is both the currentTarget and the this context.

With all that said, the provided explanation may be slightly superficial, but we’ll revisit why in just a bit.

5) Does event delegation affect event object properties and context differently?

Event delegation lets you avoid adding individual event listeners to each element, and instead control which child events fire through attaching an event listener to a parent and adding additional code.

We could change our code to take advantage of event delegation through making changes like:

  • Attaching our event listener to the document, and avoid the DOMContentLoaded event to fire
  • Add additional nested elements inside the button-container and have them not fire event listeners, behave differently, or whatever we want.

We’re not going to make those changes right now. Instead, we’ll do a somewhat contrived, but very simple, implementation of event delegation. We’ll wrap our logger code in a small conditional.

function logger() {
  if (e.target.tagName === "BUTTON") {
    // Leave existing logger code
}

The main body of our logger code will now only fire when the button is clicked, and not when the outer button-container is clicked.

See the Pen Bound container - delegation by Dylan (@dylankb) on CodePen.

As you can see, the other event properties are exactly the same as before event delegation. The currentTarget is still the div, the target remains button when clicking on the button, etc.

The story is a bit different with jQuery, however.

Understanding jQuery delegation principles

First, I want to go back to an issue I said I had about an excerpt from the this binding documentation. The excerpt says this is “… set to the DOM element on which the handler is registered”, which works fine for plain JavaScript. However, I prefer this definition from a different part of the MDN docs covering this.

When a function is used as an event handler, its this is set to the element the event fired from

The reason why is that in jQuery the on method allows for an optional selector parameter.

$('.button-container').on('click', '.button', logger);

Using jQuery parlance, up to now we’ve been directly binding elements (event targets) to their event listeners. Delegated events in jQuery have slightly different rules, as explained here

The handler is not called when the event occurs directly on the bound element, but only for descendants (inner elements) that match the selector.

To see the implications, click on the button below:

See the Pen Delegated container - jQuery by Dylan (@dylankb) on CodePen.

Events are still attached to div, however, the currentTarget and this context are now both button. To rephrase the jQuery documentation above, this is because if a selector is provided, the event occurs not on the element the event listener was added to, but on the element(s) matching the selector within the parent. With our additional selector of .button, elements matching the selector are now the ones that call event listeners, and NOT the parent with the attached event.

Further reading:

  1. MDN - Introduction to Events
  2. MDN - Overview of Events and Handlers
  1. Photo / CC BY 

  2. Screenshot taken using https://dom-viewer.herokuapp.com/