Главная » Статьи » Пользовательские события в JavaScript: полное руководство

Пользовательские события в JavaScript: полное руководство

От автора: события присутствуют повсюду в веб-приложениях. От события DOMContentLoaded, которое запускается немедленно, когда браузер завершает загрузку и анализирует HTML, до события unload, которое запускается непосредственно перед тем, как пользователь покидает ваш сайт. Опыт использования веб-приложения, по сути, представляет собой просто серию событий. Для разработчиков эти события помогают определить, что только что произошло в приложении, какое состояние пользователя было в конкретное время и многое другое.

Иногда доступные события JavaScript неадекватно или неправильно описывают состояние приложения. Например, в случае сбоя входа пользователя в систему. И вы хотите, чтобы родительский компонент или элемент знал о сбое. Не существует события сбоя входа или чего-либо подобного, доступного для отправки.

К счастью, есть способ создавать пользовательские события JavaScript для вашего приложения, о чем мы расскажем в этом руководстве. Мы подробно рассмотрим следующее:

Как создать пользовательское событие в JavaScript

Использование конструктора событий

Использование конструктора CustomEvent

Отправка пользовательских событий в JavaScript

Как работают пользовательские события JavaScript?

Перетаскивание JavaScript

Как использовать деструктуризацию объекта в JavaScript

Чтобы следовать этому руководству, вы должны иметь базовые знания о:

HTML и CSS

JavaScript и ES6

DOM и манипуляции с DOM

Давайте начнем!

Как создать пользовательское событие в JavaScript

Пользовательские события можно создавать двумя способами:

Использование конструктора Event

Использование конструктора CustomEvent

Пользовательские события также могут быть созданы с помощью document.createEvent, но большинство методов предоставляемых объектом, возвращаемым из функции, устарели.

Использование конструктора Event

Пользовательское событие может быть создано с помощью конструктора Event, например:

const myEvent = new Event('myevent', { bubbles: true, cancelable: true, composed: false
})

В приведенном выше фрагменте мы создали событие myevent, передав имя события конструктору Event. Имена событий нечувствительны к регистру, поэтому myevent такое же, как myEvent и MyEvent и т.д. Конструктор события также принимает объект, который определяет некоторые важные свойства, касающиеся события.

bubbles

Свойство bubbles определяет, должно ли событие распространяться вверх к родительскому элементу. Установка значения true означает, что если событие отправляется в дочернем элементе, родительский элемент может прослушивать событие и выполнять действие на его основе. Это поведение большинства нативных событий DOM, но для пользовательских событий по умолчанию установлено false.

Если вы хотите, чтобы событие отправлялось только определенному элементу, вы можете остановить распространение события с помощью event.stopPropagation(). Это должно быть в обратном вызове, который прослушивает событие. Подробнее об этом позже.

cancelable

Как следует из названия, cancelable указывает, должно ли событие быть отменяемым. Нативные события DOM можно отменять по умолчанию, поэтому вы можете вызывать event.preventDefault(), что предотвратит действие события по умолчанию. Если для пользовательского события cancelable установлено значение false, при вызове event.preventDefault() не выполняется никаких действий.

composed

Свойство composed определяет, должно ли это событие всплывать из теневого DOM (созданные при использовании веб — компонентов) к реальному DOM. Если для bubbles установлено значение false, это не имеет значения, потому что вы явно указываете событию не всплывать вверх. Однако, если вы хотите отправить пользовательское событие в веб-компонент и прослушивать его на родительском элементе в реальной модели DOM, то для скомпонованного свойства необходимо установить значение true.

Недостатком использования этого метода является то, что вы не можете отправлять данные слушателю. Однако в большинстве приложений мы хотели бы иметь возможность отправлять данные от того места, где событие отправляется слушателю. Для этого мы можем использовать контроллер CustomEvent.

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

Использование конструктора CustomEvent

Пользовательское событие можно создать с помощью конструктора CustomEvent:

const myEvent = new CustomEvent("myevent", { detail: {}, bubbles: true, cancelable: true, composed: false,
});

Как показано выше, создание пользовательского события с помощью конструктора CustomEvent аналогично созданию события с помощью конструктора Event. Единственное отличие состоит в том, что объект передается в конструктор как второй параметр.

При создании событий с помощью конструктора Event мы были ограничены тем, что мы не можем передавать данные через событие слушателю. Здесь любые данные, которые необходимо передать слушателю, можно передать в свойстве detail, которое создается при инициализации события.

Отправка пользовательских событий в JavaScript

После создания событий у вас должна быть возможность отправлять их. События могут быть отправлены любому расширяющемуся объекту EventTarget, и они включают в себя все элементы HTML, документ, окно и т.д. Вы можете отправлять пользовательские события следующим образом:

const myEvent = new CustomEvent("myevent", { detail: {}, bubbles: true, cancelable: true, composed: false,
});
document.querySelector("#someElement").dispatchEvent(myEvent);

Чтобы прослушивать настраиваемое событие, добавьте прослушиватель событий к элементу, который вы хотите прослушивать, так же, как и с нативными событиями DOM.

document.querySelector("#someElement").addEventListener("myevent", (event) => { console.log("I'm listening on a custom event");
});

Как работают пользовательские события JavaScript?

Чтобы показать, как использовать пользовательские события в приложении JavaScript, мы создадим простое приложение, которое позволяет пользователям добавлять профиль и автоматически получать карточку профиля.

Создание пользовательского интерфейса

Создайте папку, назовите ее как хотите и создайте в папке файл index.html. Добавьте следующее в index.html:

<!DOCTYPE html>
<html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Custom Events Application</title> <link rel="stylesheet" href="style.css" /> </head> <body> <h1>Profile Card</h1> <main> <form class="form"> <h2>Enter Profile Details</h2> <section> Drag an Image into this section or <label> select an image <input type="file" id="file-input" accept="image/*" /> </label> </section> <div class="form-group"> <label for="name"> Enter Name </label> <input type="text" name="name" id="name" autofocus /> </div> <div class="form-group"> <label for="occupation"> Enter Occupation </label> <input type="text" name="occupation" id="occupation" /> </div> </form> <section class="profile-card"> <div class="img-container"> <img src="http://via.placeholder.com/200" alt="" /> </div> <span class="name">No Name Entered</span> <span class="occupation">No Occupation Entered</span> </section> </main> <script src="index.js"></script> </body>
</html>

Здесь мы добавляем разметку для страницы. Страница состоит из двух разделов. Первый раздел — это форма, которая позволяет пользователю делать следующее:

Загрузить изображение с помощью перетаскивания или вручную выберите файл изображения

Ввести имя

Ввести профессию

Данные, полученные из формы, будут отображаться во втором разделе — карточке профиля. Второй раздел просто содержит текст и изображения-заполнители. Данные, полученные из формы, перезапишут данные заполнителя содержимого. Создайте файл style.css и заполните его следующим образом:

* { box-sizing: border-box;
}
h1 { text-align: center;
}
main { display: flex; margin-top: 50px; justify-content: space-evenly;
}
.form { flex-basis: 500px; border: solid 1px #cccccc; padding: 10px 50px; box-shadow: 0 0 3px #cccccc; border-radius: 5px;
}
.form section { border: dashed 2px #aaaaaa; border-radius: 5px; box-shadow: 0 0 3px #aaaaaa; transition: all 0.2s; margin-bottom: 30px; padding: 50px; font-size: 1.1rem;
}
.form section:hover { box-shadow: 0 0 8px #aaaaaa; border-color: #888888;
}
.form section label { text-decoration: underline #000000; cursor: pointer;
}
.form-group { margin-bottom: 25px;
}
.form-group label { display: block; margin-bottom: 10px;
}
.form-group input { width: 100%; padding: 10px; border-radius: 5px; border: solid 1px #cccccc; box-shadow: 0 0 2px #cccccc;
}
#file-input { display: none;
}
.profile-card { flex-basis: 300px; border: solid 2px #cccccc; border-radius: 5px; box-shadow: 0 0 5px #cccccc; padding: 40px 35px; align-self: center; display: flex; flex-direction: column; justify-content: center; align-items: center;
}
.img-container { margin-bottom: 50px;
}
.img-container img { border-radius: 50%; width: 200px; height: 200px;
}
.profile-card .name { margin-bottom: 10px; font-size: 1.5rem;
}
.profile-card .occupation { font-size: 1.2rem;
}

Наконец, создайте файл, чтобы вы могли добавить функциональность в приложение index.js.

Перетаскивание в JavaScript

Первая функция, которую мы добавим в приложение, — это возможность загружать изображения. Для этого мы будем поддерживать перетаскивание, а также загрузку вручную. Добавьте в файл JavaScript:

const section = document.querySelector(".form section"); section.addEventListener("dragover", handleDragOver);
section.addEventListener("dragenter", handleDragEnter);
section.addEventListener("drop", handleDrop); /** * @param {DragEvent} event */
function handleDragOver(event) { // Only allow files to be dropped here. if (!event.dataTransfer.types.includes("Files")) { return; } event.preventDefault(); // Specify Drop Effect. event.dataTransfer.dropEffect = "copy";
} /** * @param {DragEvent} event */
function handleDragEnter(event) { // Only allow files to be dropped here. if (!event.dataTransfer.types.includes("Files")) { return; } event.preventDefault();
} /** * @param {DragEvent} event */
function handleDrop(event) { event.preventDefault(); // Get the first item here since we only want one image const file = event.dataTransfer.files[0]; // Check that file is an image. if (!file.type.startsWith("image/")) { alert("Only image files are allowed."); return; } handleFileUpload(file);
}

Здесь мы выбираем раздел из DOM. Это позволяет прослушивать соответствующие события, которые необходимы для обеспечения операции перетаскивания — а именно dragover, dragenter и drop.

В функции handleDragOver мы гарантируем, что перетаскиваемый элемент является файлом, и устанавливаем эффект перетаскивания на copy. handleDragEnter также выполняет аналогичную функцию, гарантируя, что мы обрабатываем только перетаскивание файлов.

Фактическая функциональность реализуется, когда файл удаляется, и мы обрабатываем это с помощью handleDrop. Во-первых, мы предотвращаем действие браузера по умолчанию, которое заключается в открытии файла перед его доставкой.

Мы подтверждаем, что файл является изображением. Если это не так, мы отправляем сообщение об ошибке, чтобы пользователь знал, что мы принимаем только файлы изображений. Если проверка прошла успешно, мы переходим к обработке файла в функции handleFileUpload, которую создадим далее. Обновите index.js:

/** * @param {File} file */
function handleFileUpload(file) { const fileReader = new FileReader(); fileReader.addEventListener("load", (event) => { // Dispatch an event to the profile card containing the updated profile. dispatchCardEvent({ image: event.target.result, }); }); fileReader.readAsDataURL(file);
} const profileCard = document.querySelector(".profile-card");
const CARD_UPDATE_EVENT_NAME = "cardupdate"; function dispatchCardEvent(data) { profileCard.dispatchEvent( new CustomEvent(CARD_UPDATE_EVENT_NAME, { detail: data, }) );
}

Функция handleFileUpload принимает файл в качестве параметра и пытается прочитать файл в качестве URL данных с помощью устройства чтения файла.

Конструктор FileReader простирается от EventTarget, что позволяет прослушивать события. Событие load запускается после загрузки изображения — в нашем случае как URL-адрес данных.

Вы также можете загружать изображения в других форматах. На MDN есть отличная документация по FileReader API, если вы хотите узнать больше о считывании файлов.

После загрузки изображения нам необходимо отобразить его в карточке профиля. Для этого мы отправим настраиваемое событие cardupdate в карточку профиля. dispatchCardEvent обрабатывает создание и отправку события в карточку профиля.

Если вы помните из раздела выше, у пользовательских событий есть свойство detail, которое можно использовать для передачи данных. В этом случае мы передаем объект, содержащий URL-адрес изображения, полученный от программы чтения файлов.

Затем нам нужна карточка профиля для прослушивания обновлений карточки и соответствующего обновления DOM.

profileCard.addEventListener(CARD_UPDATE_EVENT_NAME, handleCardUpdate);
/** * @param {CustomEvent} event */
function handleCardUpdate(event) { const { image } = event.detail; if (image) { profileCard.querySelector("img").src = image; }
}

Как показано выше, вы просто добавляете прослушиватель событий, как обычно, и вызываете функцию handleCardUpdate, когда событие запускается.

Как использовать деструктуризацию объекта в JavaScript

handleCardUpdate получает событие как параметр. Используя деструктуризацию объекта, вы можете получить свойство image из event.detail. Затем установите атрибут изображения src в карточке профиля как URL-адрес изображения, полученный из события. Чтобы разрешить пользователям загружать изображения через поле ввода:

const fileInput = document.querySelector("#file-input"); fileInput.addEventListener("change", (event) => { handleFileUpload(event.target.files[0]);
});

Когда пользователь выбирает изображение, событие изменения запускается при вводе файла. Мы можем обработать загрузку первого изображения, так как нам нужно только одно изображение для карточки профиля.

Теперь нам не нужно делать ничего нового, так как мы разработали весь функционал при добавлении поддержки перетаскивания.

Следующая функция, которую нужно добавить — это обновление имени и профессии:

const nameInput = document.querySelector("#name");
const occupationInput = document.querySelector("#occupation"); occupationInput.addEventListener("change", (event) => { dispatchCardEvent({ occupation: event.target.value, });
});
nameInput.addEventListener("change", (event) => { dispatchCardEvent({ name: event.target.value, });
});

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

/** * @param {CustomEvent} event */
function handleCardUpdate(event) { const { image, name, occupation } = event.detail; if (image) { profileCard.querySelector("img").src = image; } if (name) { profileCard.querySelector("span.name").textContent = name; } if (occupation) { profileCard.querySelector("span.occupation").textContent = occupation; }
}

Обновите функцию handleCardUpdate, чтобы она выглядела как в приведенном выше фрагменте. Здесь мы снова используем деструктуризацию объекта, чтобы получить изображение, имя и род занятий из event.detail. После получения данных мы отображаем их на карточке профиля.

Заключение

Иногда легче понять код, когда вы думаете о нем с точки зрения отправляемых событий — как пользовательских, так и нативных событий DOM. Пользовательские события JavaScript могут улучшить взаимодействие с пользователем вашего приложения при правильном использовании. Поэтому неудивительно, что они включены в некоторые из ведущих фреймворков JavaScript, таких как Vue.js (во Vue вы отправляете пользовательские события с помощью $emit). Код демонстрации, использованный в этом руководстве, доступен на GitHub.

Автор: James James

Источник: blog.logrocket.com

Редакция: Команда webformyself.