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>