Skip to content

Instantly share code, notes, and snippets.

@WebReflection
Last active November 20, 2023 19:25
Show Gist options
  • 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
@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