Браузерные события, часть 5 из 5. Генерация пользовательских событий

29.04.2022

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

Можно не только назначать обработчики, но и генерировать события из js-кода. Например, корневой элемент меню, реализованного с помощью javascript, может генерировать события, относящиеся к этому меню: open (меню раскрыто), select (выбран пункт меню) и т.п. А другой код может слушать эти события и узнавать, что происходит с меню.

Можно генерировать не только совершенно новые, придуманные нами события, но и встроенные, такие как click, mousedown и другие. Это бывает полезно для автоматического тестирования.

Конструктор Event

Встроенные классы для событий формируют иерархию аналогично классам для DOM-элементов. Её корнем является встроенный класс Event. Событие встроенного класса Event можно создать так:

let event = new Event(type[, options]);

Здесь

  • type — тип события, строка, например click или же любой придуманный нами
  • options — объект с тремя необязательными свойствами
    • bubbles — если true, тогда событие всплывает
    • cancelable — если true, тогда можно отменить действие по умолчанию
    • composed — если true, тогда событие будет всплывать наружу за пределы Shadow DOM

По умолчанию все три свойства установлены в false: {bubbles:false, cancelable:false, composed:false}.

Метод dispatchEvent

После того, как объект события создан, мы должны запустить его на элементе, вызвав метод elem.dispatchEvent(event). Затем обработчики отреагируют на него, как будто это обычное браузерное событие. Если при создании объекта события указан флаг bubbles, то событие будет всплывать.

В примере ниже событие click инициируется javascript-кодом так, как будто кликнули по кнопке:

<button id="elem" onclick="alert('Клик!');">Автоклик</button>

<script>
    let event = new Event('click');
    elem.dispatchEvent(event);
</script>
Можно легко отличить «настоящее» событие от сгенерированного кодом. Свойство event.isTrusted принимает значение true для событий, порождаемых реальными действиями пользователя, и false — для генерируемых кодом.

Пример всплытия

Мы можем создать всплывающее событие с именем hello и поймать его на document. Всё, что нужно сделать — это установить флаг bubbles в значение true.

<h1 id="elem">Привет из кода!</h1>

<script>
    // ловим событие на document
    document.addEventListener('hello', function(event) {
        alert('Привет от ' + event.target.tagName);
    });

    // запуск события на элементе
    let event = new Event('hello', {bubbles: true});
    elem.dispatchEvent(event);
</script>

Обратите внимание:

  1. Мы должны использовать addEventListener для наших собственных событий, т.к. on{event}-свойства существуют только для встроенных событий, то есть document.onhello не сработает.
  2. Мы обязаны передать флаг bubbles:true, иначе наше событие не будет всплывать.

Механизм всплытия идентичен как для встроенного события (click), так и для пользовательского события (hello). Также одинакова работа фаз всплытия и погружения.

MouseEvent, KeyboardEvent и другие

Для некоторых конкретных типов событий есть свои специфические конструкторы. Вот небольшой список конструкторов для различных событий пользовательского интерфейса, которые можно найти в спецификации UI Event:

  • UIEvent
  • FocusEvent
  • MouseEvent
  • WheelEvent
  • KeyboardEvent
  • ..........

Стоит использовать их вместо Event, если мы хотим создавать такие события, например — MouseEvent("click"). Специфический конструктор позволяет указать стандартные свойства для данного типа события.

// clientX/clientY для события мыши
let event = new MouseEvent('click', {
    bubbles: true,
    cancelable: true,
    clientX: 100,
    clientY: 100
});

alert(event.clientX); // 100

Обратите внимание — этого нельзя было бы сделать с обычным конструктором Event.

Впрочем, использование конкретного конструктора не является обязательным, можно обойтись Event, а свойства записать в объект отдельно, после создания, вот так event.clientX=100. Здесь это скорее вопрос удобства и желания следовать правилам.

Пользовательские события

Для генерации событий совершенно новых типов, таких как hello, следует использовать конструктор CustomEvent. Технически CustomEvent абсолютно идентичен Event за исключением одной небольшой детали. У второго аргумента-объекта есть дополнительное свойство detail, в котором можно указывать информацию для передачи в событие.

<h1 id="elem">Привет для Васи!</h1>

<script>
    elem.addEventListener('hello', function(event) {
        alert(event.detail.name); // Вася
    });

    elem.dispatchEvent(new CustomEvent('hello', {
        detail: { name: 'Вася' }
    }));
</script>

Свойство detail может содержать любые данные. Надо сказать, что никто не мешает и в обычное Event записать любые свойства. Но CustomEvent предоставляет специальное поле detail во избежание конфликтов с другими свойствами события.

Кроме того, класс события описывает, что это за событие, и если оно не браузерное, а пользовательское, то лучше использовать CustomEvent, чтобы явно об этом сказать.

event.preventDefault()

Для пользовательского события «действия по умолчанию» нет, но код, который генерирует такое событие, может предусматривать какие-то свои действия после события. Вызов event.preventDefault() является возможностью для обработчика события сообщить в сгенерировавший событие код, что эти действия надо отменить. Тогда вызов elem.dispatchEvent(event) возвратит false. И код, сгенерировавший событие, узнает, что продолжать не нужно.

<ul id="menu">
    <li><a href="/html">HTML</a></li>
    <li><a href="/javascript">JavaScript</a></li>
    <li><a href="/css">CSS</a></li>
</ul>
<button id="hide">Hide</button>

<script>
    // при клике на кнопке генерируем событие hide-menu на элементе button
    hide.onclick = function() {
        let event = new CustomEvent('hide-menu', {
            cancelable: true // без этого флага preventDefault не сработает
        });
        if (!menu.dispatchEvent(event)) {
            alert('Действие отменено обработчиком');
        } else {
            menu.hidden = true;
        }
    }

    menu.addEventListener('hide-menu', function(event) {
        if (confirm('Вызвать preventDefault?')) {
            event.preventDefault();
        }
    });
</script>

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

Каталог оборудования
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.