Skip to content

Instantly share code, notes, and snippets.

@WebReflection
Last active November 20, 2023 19:25
Show Gist options
  • Star 24 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save WebReflection/b404c36f46371e3b1173bf5492acc944 to your computer and use it in GitHub Desktop.
Save WebReflection/b404c36f46371e3b1173bf5492acc944 to your computer and use it in GitHub Desktop.

This gist is a simple no-brainer description of the 3 ways (actually 2.5) the Web handle events.

<tag onclick />

The declarative inline HTML event listener is mostly an indirection of DOM Level 0 events, meaning this simply uses the equivalent of tag.onclick = listener behind the scene.

Example

<button onclick="console.log(event, 1)">click me</button>

Advantages

  • it's declarative on the template
  • it's great for RAD and prototypes development
  • it exposes a this reference to the current node out of the box
  • unless overridden or dropped, it can prevent any other bubbling listener if event.stopImmediatePropagation() is called

Disadvantages

  • it's an indirect evaluation that trust the global context and its leaked/exposed listeners
  • it uses a somehow deprecated temporary runtime global event reference out of evaluation
  • it can be easily replaced via button.onclick = ... so it doesn't guarantee at all it will be executed
  • it's limited as it cannot have options such as { once: true } or { passive: true } or even true to signal capturing instead of bubbling
  • it's limited as it doesn't allow at all { handleEvent(){} } use cases
  • it might be entirely disabled or throw errors out of CSP - Content Security Policy features (note this is not true for custom defined events such as lib:click="..." and friends, as these are not natively understood by the browser)

tag.onclick = listener

The DOM Level 0 accessor / listener easily allows one-off listeners to be attached, replaced, or detached (pass null) to any element but it's mostly as limited as the declarative approach.

Example

button.onclick = event => console.log(event, 2);

Advantages

  • it's great for RAD and prototypes development
  • it exposes a this reference to the current node out of the box, unless the listener is an arrow function (or a previously bound one)
  • it's great to replace or drop listeners on the fly
  • it never requires a refrence to the current listener to be removed or replaced
  • it can override the declerative HTML template approach in a snap
  • unless overridden or dropped, it can prevent any other bubbling listener if event.stopImmediatePropagation() is called

Disadvantages

  • it can be easily replaced via button.onclick = ... so it doesn't guarantee at all it will be executed
  • it's limited as it cannot have options such as { once: true } or { passive: true } or even true to signal capturing instead of bubbling
  • it's limited as it doesn't allow at all { handleEvent(){} } use cases and it silently fails if an object with handleEvent is assigned

tag.addEventListener('click', listener[, options])

The suggested and most updated DOM Level 3 event allows features otherwise impossible to obtain with any previous alternative.

Example

button.addEventListener('click', event => {
  console.log(event, 3);
});

button.addEventListener('click', {
  some: 'value',
  handleEvent(event) {
    console.log(event, 4);
    console.log(this.some); // "value"
  }
});

Advantages

  • it exposes a currentTarget reference to the current node out of the event but it also exposes a this reference to the handleEvent(){} listener, if any, falling back to the current node otherwise (if the listener is not an arrow function or an already bound callback)
  • it allows an extra options argument to enable { once: true }, { passive: true }, or any other extra feature available today and tomorrow
  • it allows handleEvent pattern for instances and object literals
  • it guarantees the listener is executed, unless the listener reference is explicitly removed or it's in the bubbling phase and DOM Level 0 events prevented it
  • if specified as capturing phase ({ capture: true } or just true as option) instead of bubbling (default), it can prevent any DOM Level 0 event to trigger
  • if specified for the capturing phase you can capture any event, even those that don't bubble up such as focus, at the document level, or at any level you desire.

Disadvantages

  • it won't implicitly replace or override DOM Level 0 events attached via HTML template or via direct accessor such as button.onclick = ..., unless capture phase is specified and event.stopImmediatePropagation() is invoked
  • if not well orchestrated, there's no way to remove a previously added listener as it must be the same reference or unless the signal option is passed
@pp-koch
Copy link

pp-koch commented Oct 5, 2023

if specified for the capturing phase you can capture any event, even those that don't bubble up such as focus, at the document level, or at any level you desire.

@WebReflection
Copy link
Author

WebReflection commented Oct 5, 2023

if specified for the capturing phase you can capture any event, even those that don't bubble up such as focus, at the document level, or at any level you desire.

gists are hard for reviews ... if your amend was for a specific "bullet point", please help me editing it in the right place, with the right markdown and wording, thank you a lot!

edit I think you instead wanted to add a bullet point to the advantages of DOM Level 3 events, am I right? if that's the case, I can copy/paste your comment and add it there, thanks!

@ondras
Copy link

ondras commented Oct 5, 2023

if not well orchestrated, there's no way to remove a previously added listener as it must be the same reference

AbortController can be used to remove without the need to have the same reference.

@WebReflection
Copy link
Author

@ondras that's a good one, I feel like advantages are just piling up but from an entry-point user perspective, that's a bit too late in the learning process, imho, and likely not even supported all over the place ... AbortController though is one of the options, so maybe I can just link to MDN around options there, and amend the reference part saying that "unless you pass an AbortController signal"?

@WebReflection
Copy link
Author

@ondras I've updated the disadvantage point with a link to MDN for your suggestion ... I hope that's fine/cool/OK

@WebReflection
Copy link
Author

@pp-koch I've updated the bullet list with your latest point, assuming that's what you meant by adding it or amending the list ... feel free to help me out fixing mistakes, if any, in the list, thank you!

@ondras
Copy link

ondras commented Oct 5, 2023

@ondras I've updated the disadvantage point with a link to MDN for your suggestion ... I hope that's fine/cool/OK

Yeah, thanks a lot!

@peerreynders
Copy link

It could be useful in user-content-disadvantages to link to Unsafe Names for HTML Form Controls to illustrate the footguns that are hiding inside the augmented scope chain.

This further expands on MDN's warning against inline event handlers.

And then of course there is the fact that CSP could block execution of inline code which includes inline event handlers.

@WebReflection
Copy link
Author

WebReflection commented Oct 6, 2023

@peerreynders I think you did a great job commenting further details but if this touches all Web internals is not a "no-brainer" anymore, although your CSP issue is worth mentioning, if not just to make people more aware of CSP constraints, thanks!

updated https://gist.github.com/WebReflection/b404c36f46371e3b1173bf5492acc944#disadvantages

@peerreynders
Copy link

peerreynders commented Oct 8, 2023

is not a "no-brainer" anymore

As you've said, it's in the comments now for those who wish to dig deeper. 👍

@mems
Copy link

mems commented Oct 10, 2023

DOM 0 event listeners have a special scope:

<img src="about:error" onerror="console.log({'this':this,onerror,body,src})">
<!-- Will log: {this: img, body: body, src: 'about:error', onerror: ƒ onerror(event)} -->

calepin/Development/JavaScript/JavaScript.md at main · mems/calepin

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment