The EventListener interface
3 min read
Event listener code makes up a large portion of any web application. There exist two ways to attach an event listener; onX
handlers and addEventListener
:
button.onclick = () => alert("Clicked");
button.addEventListener("click", () => alert("Clicked"));
The onX
form only supports a function (or null/undefined). addEventListener
instead can be passed anything that matches the EventListener
interface:
type EventListener = null | ((e: Event) => void) | { handleEvent(e: Event): void };
That’s right. addEventListener
also supports object references which have a handleEvent
method.
Component-driven design
Read section Component-driven designOrdinarily when using the callback form, this
refers to the attached element. If you’re using handleEvent
, this
is always bound to the object you passed.
<button id="clickedTimes">Times clicked: 0</button>
<script>
const counter = {
count: 0,
handleEvent(e) {
this.count += 1;
e.currentTarget.textContent = `Times clicked: ${this.count}`;
},
};
clickedTimes.addEventListener("click", counter);
// To remove, pass counter instead of counter.handleEvent:
// clickedTimes.removeEventListener("click", counter);
</script>
Self-modifying code
Read section Self-modifying codehandleEvent
also makes it easier to have self-modifying code. The browser resolves handleEvent
every time the event is called. This makes it easier to change the underlying function.
<button id="currentMood">What's your mood?</button>
<script>
const ref = {};
const wordCallbacks = ["network", "hammer", "walking", "violently", "mediocre"].map((word) => (e) => {
e.currentTarget.textContent = `Current mood: ${word}`;
ref.handleEvent = wordCallbacks[Math.floor(Math.random() * (wordCallbacks.length - 1))];
});
ref.handleEvent = wordCallbacks[0];
currentMood.addEventListener("click", ref);
</script>
Should you ever do this? Of course not. In fact, in the example above, we are still making use of a closure anyway.
Web Components
Read section Web ComponentsCustom elements, often referred to as Web Components, are the web’s native solution for component driven design. For simple components that only attach event listeners to a single element, handleEvent
might not be a terrible idea after all.
<test-counter></test-counter>
<script>
class TestCounter extends HTMLElement {
clicked = 0;
connectedCallback() {
this.btn = document.createElement("button");
this.btn.addEventListener("click", this);
this.render();
this.append(this.btn);
}
disconnectedCallback() {
this.btn.removeEventListener(this);
}
render() {
this.btn.textContent = `Times clicked: ${this.clicked}`;
}
handleEvent(e) {
this.clicked += 1;
this.render();
}
}
customElements.define("test-counter", TestCounter);
</script>