Mastering the Javascript event system (Part 3)

This is the third post in a series about working with Javascript events and using a single event listener to handle related elements.


Extra examples addendum

Here are some additional examples to demonstrate how to take advantage of a single event listener.

Lists

A common pattern is to have a list of items that can be interacted with, where new items are inserted dynamically with Javascript. If you have event listeners attached to each item, then your code has to deal with event listeners every time you generate a new element.

<div id="buttons-container"></div>

<button id="add">Add new button</button>
let buttonCounter = 0;
document.getElementById('add').addEventListener('click', evt => {
    const newButton = document.createElement('button');
    newButton.dataset.number = buttonCounter;
    // Make a new event listener every time "Add new button" is clicked
    newButton.addEventListener('click', evt => {
        // When clicked, log the clicked button's number.
        console.log(`Clicked button #${newButton.dataset.number}`);
    });
    buttonCounter++;

    const container = document.getElementById('buttons-container');
    container.appendChild(newButton);
});

By taking advantage of bubbling, you can just have a single event listener on the container. If you create many elements in your app, this reduces the number of listeners from n to 2.

let buttonCounter = 0;
document.getElementById('add').addEventListener('click', evt => {
    const newButton = document.createElement('button');
    newButton.dataset.number = buttonCounter;
    buttonCounter++;

    const container = document.getElementById('buttons-container');
    container.appendChild(newButton);
});
document.getElementById('buttons-container').addEventListener('click', evt => {
    const clickedButton = evt.target.closest('button');
    if (clickedButton != null) {
        // When clicked, log the clicked button's number.
        console.log(`Clicked button #${clickedButton.dataset.number}`);
    }
});

Forms

Perhaps you have a form with lots of inputs, and you want to collect all the user responses into a single object.

<form>
    <label>Name: <input name="name" type="text"/></label>
    <label>Email: <input name="email" type="email"/></label>
    <label>Password: <input name="password" type="password"/></label>
</form>
let responses = {
    name: '',
    email: '',
    password: ''
};

document
	.querySelector('input[name="name"]')
	.addEventListener('change', evt => {
		const inputElement = document.querySelector('input[name="name"]');
		responses.name = inputElement.value;
	});
document
    .querySelector('input[name="email"]')
    .addEventListener('change', evt => {
        const inputElement = document.querySelector('input[name="email"]');
        responses.email = inputElement.value;
    });
document
    .querySelector('input[name="password"]')
    .addEventListener('change', evt => {
        const inputElement = document.querySelector('input[name="password"]');
        responses.password = inputElement.value;
    });

Let’s switch to a single listener on the parent <form> element instead.

let responses = {
    name: '',
    email: '',
    password: ''
};

document.querySelector('form').addEventListener('change', evt => {
    responses[evt.target.name] = evt.target.value;
});