16

Consider this button:

<button id="the_button" onclick="RunOnClick();">Click</button>

This button has an inline onclick event handler. The contents of the RunOnClick function don't matter for this question.

If I ALSO attach another click event listener like so:

var btn = document.getElementById('the_button');

btn.addEventListener("click", function(){
    // Do Something
});

The handler registered with addEventListener always seems to run after the inline onclick="" one.

Can I always rely on the single inline onclick handler to fire first and the addEventListener handler to fire later?

Is this a fluke? Or is it actually designed that way and part of one of the ECMAScript specifications?

This feels like a back to the basics quality of question, but I don't know the answer.

MarredCheese
  • 17,541
  • 8
  • 92
  • 91
Captainlonate
  • 4,878
  • 4
  • 25
  • 35
  • 1
    Possible duplicate of [The Order of Multiple Event Listeners](https://stackoverflow.com/questions/9512551/the-order-of-multiple-event-listeners) In T.J. Crowder's answer: _"The vast majority of browser implementations (Chrome, Firefox, Opera, etc.), including IE9, fire the handlers in the order in which they were attached. IE8 and earlier do it the other way around."_ – blex Apr 12 '18 at 21:15
  • @blex, IE8 and earlier do it in reverse order of which they were attached? – yaakov Apr 12 '18 at 21:27
  • @YaakovAinspan I cannot test it, but that is what T.J. Crowder claims in his answer (and I trust this man) – blex Apr 12 '18 at 21:29
  • 1
    Not a duplicate. This question is asking about event handlers attached via an inline attribute vs attached via a call to addEventListener. That's not explicitly covered in the linked question – ChrisM Apr 12 '18 at 21:36
  • @ChrisM Right, it's not explicit. However, this rule applies for both inline event listeners and JS ones: https://jsfiddle.net/a0ffsq58/ – blex Apr 12 '18 at 21:43
  • This is a question worthy of discussion in its own right, as an extension of the behaviour demonstrated there. This is evidenced by the fact it is in fact covered specifications as I am showing in the answer I am writing. – ChrisM Apr 12 '18 at 21:52
  • This issue is avoided by _**never**_ using inline event handlers. HTML is your _View_, while Javascript is your _Controller_. [Separation of Concerns](https://en.wikipedia.org/wiki/Separation_of_concerns) says you shouldn't mix your M, V, and C; so you should normally `addEventListener` from a _separate_ `.js` file, then you also don't rely on unspecified behavior. – Stephen P Apr 12 '18 at 22:34
  • @StephenP you can use `.onclick=function(){...}` in JS, too, without affecting your view. – Lee Kowalkowski Apr 12 '18 at 22:55
  • 1
    Question wasn't really about preferred design patterns. I wanted to know why the repeatable behavior I'm observing is happening, even though the DOM Events 2 document I read said there is no specified order, but all 3 of my browsers exhibit the same behavior. Besides, I could have also registered the handler in another .js file using "element.onclick=function(){}" and I'd still be asking the same question but would have fulfilled the requirements of MVC. – Captainlonate Apr 12 '18 at 22:56
  • @blex, except you cannot have multiple inline event handlers like you can if you attach with `addEventListener` – Lee Kowalkowski Apr 12 '18 at 22:56
  • If you have multiple events handlers associated with an element, yet they're sensitive to the order in which they're called, I would argue your solution is problematic and reconsider associating multiple event handlers to the same element if they cannot execute independently. (Principles of event driven programming) – Lee Kowalkowski Apr 12 '18 at 23:04

3 Answers3

17

The answer is, yes, this is actually covered in the specification but not the ECMAScript spec per se, which only strictly deals with ECMAScript independent of implementation.

Firstly, the ordering of DOM events. I refer you to this relevent SO question.

Are event handlers in JavaScript called in order?

As this states, previously it was unspecifed but as of the DOM level 3 spec it does specifically state they should be executed in the order that they are registered.

But what about inline event handlers defined like in your example?

For this we can turn to the HTML 5 spec which says:

An event handler content attribute is a content attribute for a specific event handler. The name of the content attribute is the same as the name of the event handler.

[...]

When an event handler H of an element or object T implementing the EventTarget interface is first set to a non-null value, the user agent must append an event listener to the list of event listeners associated with T with type set to the event handler event type corresponding to H and callback set to the event handler processing algorithm defined below.

Unpacking this and the subsequent notes a bit the key things to take away here are:

  • The act of adding an 'event handler content attribute' (e.g. your onclick attribute) will cause an event listener to be added to the list referred to earlier in the DOM spec.
  • What is actually happening behind the scenes is that this event listener is not strictly the code you specified in the onclick attribute but an internal event handler processing algorithm for evaluating the attribute and executing the appropriate callback.
  • Importantly when the attribute changes or is set to null then the event listener, and it's position in the list, is not changed. Only the outcome of the event handler processing algorithm is changed (because you have changed the input).

This might be a bit confusing. It might help to read and digest the notes on the spec yourself, but I will also try to cover the key implications below.

Firstly, yes, the behaviour you see is effectively defined by the spec as a logical result of what the spec tells us. When a document is being parsed and encounters an inline event handler attribute then this internal algorithm is added to the list of event listeners immediately. According to the spec then this will mean that the first event listener will be the one corresponding to your event handler attribute. Since this has to have been set before any calls to addEventListener since it wouldn't be possible to call addEventListener on a element that didn't exist then. In these circumstances it will always execute first.

The interesting stuff happens when we start messing with the inline attribute after the initial parsing. Here's an example from the HTML5 spec itself that appears right after the bit I quoted above:

EXAMPLE 8

This example demonstrates the order in which event listeners are invoked. If the button in this example is clicked by the user, the page will show four alerts, with the text "ONE", "TWO", "THREE", and "FOUR" respectively.

 <button id='test'>Start Demo</button>
    <script>
    var button = document.getElementById('test');
    button.addEventListener('click', function () { alert('ONE') }, false);
    button.setAttribute('onclick', "alert('NOT CALLED')"); // event handler listener is registered here
    button.addEventListener('click', function () { alert('THREE') }, false);
    button.onclick = function () { alert('TWO'); };
    button.addEventListener('click', function () { alert('FOUR') }, false);
    </script>

As we can see then the initial value of the onclick attribute is overriden, but the new onclick handler still executes between the first and the second listener set with addEventListener. The reason for this is that the inline event handler will effectively always be in the list of listeners at the same point it was when it was first added to the element. This is because technically, as stated earlier, the actual event listener is not the callback we have specified in the attribute content, but an internal algorithm that takes the attribute contents as it's input.

I have created a JSFiddle to test this is the case and I can confirm this is the behaviour I see in both Firefox and Chrome.


So to summarise, in practical terms:

  1. Event handler attributes, encountered in a documents source when first loading, will always execute first since they could not have had an event listener added before.
  2. For event handler attributes added later with e.g setAttribute, then they will respect the order in which they were added respective to earlier and later calls to addEventListener.
  3. However changing or unsetting the value of an event handler attribute will not change it's position in the list of event listeners.

Hope that clears things up!

ChrisM
  • 1,565
  • 14
  • 24
0

It is not a fluke and you can count on it to behave that way...in the browswer you're using. However, it may behave differently in other browsers! This is based on how the browser implements the event spec, and the spec does not require any particular order.

Per the comment below, clarifying: I intended to point out that it will probably behave the same way every time in the browser you're using today. Until you get an update, or something else changes that you're not expecting. Regardless it is a VERY bad idea to count on something that is not in the spec.

Best solution if you need guaranteed order is to chain your event handlers and only trigger one with the related event.

Daniel
  • 3,312
  • 1
  • 14
  • 31
  • 2
    Meaning that you can't rely on it - even in the same browser. That is what it means to be not in the spec. The order is not defined. – jojois74 Apr 12 '18 at 21:39
  • This works for me. Modern browsers choose to call them in the order they were registered, but that is not by spec and they don't have to. Since inline event listeners are registered first, before a deferred javascript file is loaded, they happen to be the first registered. So, my question was, is this by design, and the answer is no, it's not part of any ecmascript standard, browsers just made a judgement call. Thanks. – Captainlonate Apr 12 '18 at 22:48
  • 1
    The relevant specifications do actually define the order of events to be executed. See my answer for details. – ChrisM Apr 12 '18 at 22:55
  • (although it was true it wasn't specified until DOM level 3 and, practically speaking, you may be correct that it is better not to rely on native ordering) – ChrisM Apr 12 '18 at 23:10
-1

While the behaviour may be repeatable in the browser you are using, there’s no guarantee that another browser - or even a different version of the same browser - will act the same way.

This is due to each rendering engine setting its own priority for triggering event handlers, usually in the order in which they were attached.

If you need to ensure a particular order of execution, you could set up an event queue that fires on a single handler.

Jeevan Takhar
  • 491
  • 5
  • 10