Браузерные события, часть 3 из 5. Делегирование

25.04.2022

Теги: DOMFrontendHTMLJavaScriptWeb-разработкаСобытиеТеория

Делегирование возможно благодаря тому, что события всплывают. А это означает, что оно возникает не только на самом элементе, но затем на его родителе, потом на родителе его родителя и так далее. Таким образом, при добавлении обработчика одному из предков, он будет выполняться всякий раз, когда это событие будет происходит на любом из его потомков. В обработчике получить элемент, который инициировал это событие, можно посредством свойства target объекта event.

<ul id="list">
    <li>Раз</li>
    <li>Два</li>
    <li>Три</li>
</ul>

Предположим, что при нажатии на каждый элемент списка должно что-то произойти. Мы можем добавить отдельный обработчик для каждого <li>. Но что, если у нас элементы <li> динамически добавляются и удаляются из этого списка? В этом случае лучшим решением будет добавить обработчик к родительскому элементу <ul>.

document.querySelector('#list').addEventListener('click', function (event) {
    if (event.target && event.target.nodeName === 'LI') {
        alert(event.target.textContent);
    }
});

Пример делегирования

Есть и другие применения делегирования. Например, нам нужно сделать меню с разными кнопками: «Сохранить», «Загрузить», «Поиск» и т.д. И есть объект с соответствующими методами save, load, search. Как их состыковать?

Первое, что может прийти в голову — это найти каждую кнопку и назначить ей свой обработчик среди методов объекта. Но существует более элегантное решение. Мы можем добавить один обработчик для всего меню и атрибуты data-action для каждой кнопки в соответствии с методами, которые они вызывают.

<div id="menu">
    <button data-action="save">Сохранить</button>
    <button data-action="load">Загрузить</button>
    <button data-action="search">Поиск</button>
</div>

<script>
    class Menu {
        constructor(elem) {
            this._elem = elem;
            elem.onclick = this.onClick.bind(this);
        }

        save() {
            alert('Сохранить');
        }

        load() {
            alert('Загрузить');
        }

        search() {
            alert('Поиск');
        }

        onClick(event) {
            let action = event.target.dataset.action;
            if (action) {
                this[action]();
            }
        }
    }

    new Menu(document.querySelector('#menu'));
</script>

Обратите внимание, что метод this.onClick в конструктору привязывается к контексту текущего объекта this. Это важно, т.к. иначе this внутри него будет ссылаться на DOM-элемент elem, а не на объект Menu, и this[action] будет не тем, что нам нужно.

Поведение элементов

Делегирование событий можно использовать для добавления элементам «поведения» (behavior), декларативно задавая хитрые обработчики установкой специальных html-атрибутов и классов. Для этого на документ устанавливается обработчик, который ловит все клики (или другие события) и, если элемент имеет нужный атрибут, производит соответствующее действие.

1. Поведение «Счётчик»

Например, здесь html-атрибут data-counter добавляет кнопкам поведение — увеличить значение при клике:

Счетчик раз <input type="button" value="1" data-counter>
Счетчик два <input type="button" value="2" data-counter>
Счетчик три <input type="button" value="3" data-counter>

<script>
    document.addEventListener('click', function(event) {
        if (event.target.dataset.counter !== undefined) {
            event.target.value++;
        }
    });
</script>

Элементов с атрибутом data-counter может быть сколько угодно, новые могут добавляться в html-код в любой момент. При помощи делегирования мы фактически добавили новый «псевдостандартный» атрибут в HTML, который добавляет элементу новую возможность («поведение»).

Когда мы устанавливаем обработчик событий на объект document, мы всегда должны использовать метод addEventListener, а не document.onclick, т.к. в случае последнего могут возникать конфликты — новые обработчики будут перезаписывать уже существующие.

2. Поведение «Переключатель»

Сделаем так, что при клике на элемент с атрибутом data-toggle-id будет скрываться/показываться элемент с заданным id:

<button data-toggle-id="subscribe-mail">
    Показать форму подписки
</button>

<form id="subscribe-mail" hidden>
    Ваша почта: <input type="email" name="email" value="">
</form>

<script>
    document.addEventListener('click', function(event) {
        let id = event.target.dataset.toggleId;
        if (id) {
            let elem = document.getElementById(id);
            elem.hidden = !elem.hidden;
        }
    });
</script>

Теперь для того, чтобы добавить скрытие-раскрытие любому элементу, даже не надо знать JavaScript, можно просто написать атрибут data-toggle-id. Это очень удобно — не нужно писать js-код для каждого элемента, который должен так себя вести, просто используем поведение. Обработчики на уровне документа сделают это возможным для элемента в любом месте страницы.

Поиск: DOM • Frontend • HTML • JavaScript • Web-разработка • Событие • Теория • Event

Каталог оборудования
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Производители
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Функциональные группы
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.