В целом я сторонник подробного изучения конкретного шаблона или инноваций, поскольку это позволяет не только видеть, какие трудности реализации могут встретиться, но и тестировать себя в определенной роли создателя этого шаблона. Конечно, вам нужно попробовать в изоляции со всех структур, так как используя чистые JS, вы идете до самого низкого уровня абстракции.
Это может не очень хорошее сравнение, но я думаю, что рамки в JS высокого уровня (поскольку они скрывают много проблем и дают для каждого решения, иногда страдают авторами. Вы понимаете, вот как метод реализации, так и капризность и особенность браузеров). Но чистые JS – ассемблер в веб-стране. Здесь вы столкнулись со всеми проблемами в лбу. И только твой мозг, навыки и стоги и стоги могут помочь вам 😂.
Итак, пришло время поговорить о чистых JS и простейшей реализации Делегация событий шаблон.
Идея
Перед началом реализации шаблона я хотел бы фантазировать и попробовать что-то интересное, что покажет, как использовать некоторые виды методов API DOM, которые могут значительно помочь в упрощении вашей задачи. И после небольшой мысли я решил, что нужно было показать возможности шаблонов и создать такой простой аналог реагирования с использованием шаблона делегирования событий. Это то, что мы будем делать дальше!
Шаблон
Что уже существует из готового и простого решения в браузере DOM для нашего мини-реагирования?
Познакомьтесь с тегом
<Шаблон> – Это механизм для отложить Рендеринг контента клиента, который не отображается во время загрузки страницы, но может быть инициализирован с помощью JavaScript.
Шаблон можно рассматривать как часть контента, сохраненного для последующего использования в документе. Хотя парсер обрабатывает содержание <Шаблон> Элемент на момент загрузки страницы, это только для того, чтобы убедиться, что контент действителен; Сам содержимое не отображается.
https://developer.mozilla.org/ru/docs/Web/HTML/Element/template
Отлично! Это то, что вам нужно!
Требования к компонентам
Теперь давайте решим, какие наши компоненты смогут сделать?
Уметь обрабатывать прикрепленные обработчики событий через атрибуты и отвечать на них, используя
Делегация событийпринципБыть в состоянии использовать данные из свойств первого уровня объекта данных. Это использование прилагаемого свойства типа
Property.subPropertyнедоступно. Там будет единственная возможность указатьимуществовместоProperty.subproperty..В шаблоне свойства объекта данных и имена обработчиков событий должны быть заключены в фигурные кронштейны, например, как этот
{недвижимость}или{handverofsomeevent}Если во время
ДомРазбор, компоненты, которые не принадлежат стандарту, набор из HTMLДомНайдены, тогда вам нужно попытаться найти зарегистрированного компонента и заменить нестандартныйДомУзел с компонентным шаблоном. Как и вРеагироватьОтказКомпонент должен быть объявлен следующим образом:
<Шаблон>
Где Имя Свойство будет использоваться для имени компонента. И это будет использовать содержимое <Шаблон> Тег как разметка компонентов.
- Чтобы объявить компонент в DOM, используйте просмотр конструкции формы
Отказ
На что мы проверим нашу реализацию?
Тогда для меня возникла простая мысль. Реализовать простой Список Todo Отказ
Функционал:
На самом деле, вы можете добавить текстовые задачи в список, заканчивая записью с
Введитеключ или нажав наДобавитькнопка. В этом случае текстовое поле будет очищено, аДобавитьКнопка будет отключена.Если задача завершена, вы нажимаете кнопку с помощью
хВ соответствующем элементе в списке и эта задача будет постоянно удалена из списка.
Технология нашего мини-реагирования
Первая фаза проходит через
Домв поисках<Шаблон>и пользовательские элементы (HTML-теги). Регистрация компонентов также входит в этот этап – это ключевой момент, когда мы будем применятьДелегация событийОтказРегистрация глобальных обработчиков событий На элементе документа + Связывание процессора событий с каждым из глобальных обработчиков.
Второй этап – замена пользовательских тегов с зарегистрированными компонентами из
<Шаблон>Теги.
И теперь ниже мы посмотрим на все эти фазы в свою очередь.
Фаза один (пройти через DOM и искать объявления компонентов)
Здесь вы проходите через элементы DOM HTML-документа. В HTML DOM у нас уже есть необходимый инструмент, который позволяет нам легко пройти все элементы, которые интересуют нас.
И этот инструмент это Document.CreateReewalker Отказ Бородатые 🧔🧔🏾, ребята, написали этот метод API для уочтения элементов HTML. В этом случае вы можете указать опцию фильтрации для узлов HTML-элементов. Для нашего дела мы будем использовать Nodefilter. Show_Element , нам не понадобится текстовые узлы, поскольку мы можем пройти через текст узлы себя, внутри определенного элемента.
Код для этого этапа концентрирован здесь:
Для начала, как вы можете видеть, мы создаем объект итератора над Дом элементы. И как корневой элемент, из которого путешествие вместе с Дом Начинается, мы указываем Документ. Тебе Отказ
Затем мы указываем параметр фильтра Nodefilter. Show_Element Отказ После параметра фильтрации мы указываем Acceptnode Обработчик, в котором, если хотите, вы можете добавить дополнительные условия фильтрации для Дом элементы. Этот обработчик должен вернуть Nodefilter. Filter_accept для необходимых узлов, а для пропуска Nodefilter. Filter_reject. . В нашем случае, например, мы всегда возвращаем Nodefilter. Filter_accept , так как Nodefilter. Show_Element Флаг подходит нам.
После создания итератора над Дом , используя NextNode () метод и пока цикла.
Внутри петли мы собираем нестандартный дом элементы. Чтобы сделать это, проверьте название конструктора Дом Узел и для нестандартных компонентов, имя конструктора будет соответствовать HTMLUNKNOWESELEMET. . Найденные элементы записываются в массив для последующей обработки.
Второй шаг, мы проверяем имя узла для соответствия Шаблон Отказ Это объявление наших элементов. И каждый узел нашел, мы отправляем на Регистрация процедура.
Далее мы увидим, как работает этап регистрации компонентов.
Первая фаза (регистрация шаблонов компонентов)
Вот процедура регистрации:
Во-первых, мы копируем содержимое узла шаблона, используя
node.content.colonenode (true)Отказ Вы можете узнать больше о клонировании здесь Отказ Клонирование необходимо для того, чтобы оставить оригинальный шаблон без изменений.В качестве следующего шага мы должны пройти через содержимое шаблона, определить текстовые переменные, которые будут вставлены, а также получить обработчики событий.
Представление элемента в нашей коллекции будет выглядеть так:
{ элемент, обработчики: {}, Текстовые панели: {} };
Элемент– Это компонентный узел шаблона, как это без обработки.обработчики– Это коллекцияКлюч - функциягде все обработчики для конкретного элемента будут храниться.Текстирки– Это коллекцияИндекс - значениеОтказ Здесь положение сохраняется в доме с содержимым текущего узла (естественно, не удерживая внимание способностью меняться, представить, что в нашем случае текстовые узлы не будут изменять позиции, и это делается для упрощения).
- Внутри петли через элементы шаблона генерируется уникальный идентификатор элемента шаблона. Он состоит из следующих частей:
const indexedtemplateName = `$ {templatename}: $ {ID}: $ {node.nodename}`;
От Имя Шаблона + Номер индекса + Название узла . Это достаточно для нас, чтобы определить элемент.
- Добавление
Шаблон данныхатрибут, который содержит уникальный идентификатор. - Мы проходим через коллекцию атрибутов текущего узла и определите соответствующие обработчики событий. Они начинаются с
напрефикс. А также мы вынуждены немедленно удалить атрибут события текущего узла, чтобы при вступлении в DOM мы не путаем браузер. - Мы проходим через первый уровень вложенности дочерних элементов и заполните
текстовые панелиДля текущего элемента.
Регистрация глобальных обработчиков событий и привязка процессора событий
Это позвоночник Делегация событий , ядро обработки событий на разных элементах.
Процессор событий выглядит так:
А также, чтобы это работать, вам нужно связать это с событиями на документ элемент.
Таким образом, теперь мы можем ответить на три необходимых события.
Как мы будем различать, для чего Компонент который обработчик звонить? И это очень просто, раньше мы отмечали каждый элемент специальным атрибут шаблона данных , в котором мы поставили необходимую информацию. Таким образом, разделение строки идентификатора на символ : , мы можем:
Найти зарегистрированный компонент
Получите обработчик событий по полной первоначальному значению
Шаблон данных+E.TYPEатрибут.Проверьте, соответствует ли он наш шаблон
{nameofeventhandler}Если все в порядке, выберите имя
nameofeventhandlerот{nameofeventhandler}и найти функцию обработчика вGlobalEventhandlersКоллекция с использованием этого ключаИ, наконец, выполните обработчик, передавая его объект события.
Вторая фаза – рендеринг меток Custom Dom
Эта часть рабочей фазы нашей простой версии Реагировать состоит из двух методов:
Phasetwo.
AnderTemplate
Phasetwo – проходит через коллекцию пользовательских элементов, обнаруженных на предыдущем этапе и используя Node.replacechild Способ заменяет узел с шаблоном компонента.
AnderTemplate – Вставляет данные из прошедших объектов параметров в шаблон элемента HTML в соответствии с Текстирки и возвращает обработанный HTML Узел, готов к вставке в Дом Отказ
Точка входа
Это кусок кода, который вдохнет жизнь в наше простое приложение, которое использует Делегация событий Отказ
После RunApp Процедура запущена, фазированное исполнение Фаза 1 а потом Фаза 2 немедленно начнется. А также настройки доступности для кнопки ADD, учитывая состояние текстового поля.
Анализ результатов
Во-первых, давайте посмотрим, как наш HTML «До» и «После» изменения.
Вот оригинальный HTML:
Как вы можете видеть, внутри
Div # приложениеВместо пользовательских элементов отображаются элементы из определенных шаблонов. Вы также можете заметить обилиеШаблон данныхс идентификаторами для каждого узла шаблона.Вы также можете заметить отсутствие
onclickи другие события за пределами<Шаблон>Теги.Внутри
<Шаблон>Теги, всеHTMLУзлы остаются, как они. Таким образом, вы определенно можете проверить, какие события, с которыми вы указали обработчики.
Давайте проверим сейчас, если мы действительно используем Делегация событий Отказ
Во-первых, давайте посмотрим на кнопку Добавить
- Мы наблюдаем наличие
ПроцепцияОбработчик для мероприятия CLICK на уровне документа, нет локальных обработчиков, нет обработчиков уровня элементов.
Теперь проверьте текстовое поле
А для текстового поля входное событие, мы также наблюдаем наличие глобального Процепция обработчик.
Как дела с кнопками, чтобы удалить элемент TODO из списка
Добавьте несколько Todos, а затем проверьте обработчики событий:
И снова мы видим из длинного списка, отсутствие местных обработчиков событий. Только глобал!
Приложение отлично отображается TODO и позволяет удалить TODOS из списка!
А где я могу видеть в действии?
// Import stylesheets
import './style.css';function createTodo(text) {
return applyTemplate('todoitem', { text })
}function removeTodo(todoEl) {
window.todo_list.removeChild(todoEl);
}function addTodo(text) {
window.todo_list.appendChild(createTodo(text));
}function clearInput() {
window.todo_text.value = '';
}function isInputEmpty() {
return window.todo_text.value.trim() === '';
}function setAvailabilityOfAddButton() {
const todoTextEl = window.todo_text;
const todoBtn = window.add_todo_btn;
const isEmpty = todoTextEl.value.trim() === '';
if (isEmpty) {
todoBtn.setAttribute('disabled', 'disabled');
} else {
todoBtn.removeAttribute('disabled');
}
}function getFormData(target) {
const form = new FormData(target);
const { done, value } = form.entries().next();
if (value) {
const data = {};
data[value[0]] = value[1];
return data;
}
return null;
}const globalEventHandlers = {
removeTodoItem(e) {
removeTodo(e.target.parentNode);
},
formTodoTextInput(e) {
setAvailabilityOfAddButton();
},
formButtonSubmit(e) {
setAvailabilityOfAddButton();
},
formSubmit(e) {
e.preventDefault();
const data = getFormData(e.target);
if (data) {
addTodo(data.todo_text);
clearInput();
}
setAvailabilityOfAddButton();
}
};function processEvent(e) {
const id = e.target.getAttribute('data-template');
if (!id) return;
const [tempalteName] = id.split(':')
const template = templates[tempalteName];
if (template) {
const handlerKey = ${id}:on${e.type};
const handlerFnName = template.handlers[handlerKey];
if (handlerFnName && handlerFnName.match(/{(.)}/)) {
const eventHandlerName = handlerFnName.match(/{(.)}/)[1];
const eventHandler =
globalEventHandlers[eventHandlerName];
if (typeof eventHandler === 'function') {
eventHandler(e);
}
}
}
}const templates = {};
let id = 1;function registerTemplate(node) {
const element = node.content.cloneNode(true);
const templateTreeWalker = document.createTreeWalker(
element,
NodeFilter.SHOW_ELEMENT,
);
const TemplateName = node.getAttribute('name').toLowerCase();
templates[TemplateName] = {
element,
handlers: {},
textVars: {}
};const currentTemplate = templates[TemplateName];
while(templateTreeWalker.nextNode()) {
const node = templateTreeWalker.currentNode;
const indexedTemplateName =
${TemplateName}:${id}:${node.nodeName};
node.setAttribute('data-template', indexedTemplateName);
Array.from(node.attributes).forEach(a => {
if (a.nodeName.startsWith('on') && a.nodeValue.match(/\
{.}/)) {currentTemplate.handlers[${indexedTemplateName}:${a.nodeName}] = a.nodeValue;
node.removeAttribute(a.nodeName);
}
});
Array.from(node.childNodes).forEach((el, index) => {
if (el.nodeName === '#text' && el.wholeText.match(/\
{.}/)) {
currentTemplate.textVars[index] = el.nodeValue;
}
});
id += 1;
}
}function applyTemplate(templateName, options) { const template = templates[templateName]; const html = template.element.cloneNode(true); const topElement = html.children[0]; Object.entries(template.textVars).forEach(([index, name]) => { const nameMatch = name.match(/\{(.*)\}/); const propName = nameMatch && nameMatch[1]; topElement.childNodes[index].nodeValue = topElement.childNodes[index].nodeValue.replace(newRegExp(name, 'ig'), options[propName])
});
return html;
}const NotStandardElements = [];
function phaseOne() {
const treeWalker = document.createTreeWalker(
document.body,
NodeFilter.SHOW_ELEMENT,
{
acceptNode(node) { return NodeFilter.FILTER_ACCEPT; }
}
);while(treeWalker.nextNode()) {
if (treeWalker.currentNode.constructor.name === 'HTMLUnknownElement') {
NotStandardElements.push(treeWalker.currentNode);
}
if (treeWalker.currentNode.nodeName === 'TEMPLATE') {
registerTemplate(treeWalker.currentNode);
}
}
}function phaseTwo() {
const app = window.app;
NotStandardElements.forEach(oldEl =>app.replaceChild(applyTemplate(oldEl.nodeName.toLowerCase(), {}),
oldEl));
}// GLOBAL EVENT DELEGATION METHODS
['oninput', 'onclick', 'onsubmit'].forEach(event =>
document[event] = processEvent);function runApp() {
phaseOne();
phaseTwo();
setAvailabilityOfAddButton();
}// entry point
runApp();
Вывод
Из вышеперечисленного мы можем сделать вывод, что мы успешно применили принципы и возможности делегирования событий, а также реализовали самую простейшую версию «реагировать» для образовательных и исследовательских целей.
Самое главное, теперь если вы решите написать ваше приложение в чистое JS, то Делегация событий Подход может:
Сохраните вас от необходимости беспокоиться о очистке событий на элементе, прежде чем удалять его.
Помогите организовать централизованные события.
Помогите концентрировать весь код в одном месте, с возможностью разделить логику в модули.
Избавьтесь от обработчиков событий ада.
Устранить необходимость вставки кусочков кода JS в шаблонах.
Большое спасибо за чтение! Я надеюсь, что вам понравилось!
Ранее опубликовано в Maddevs.io.
Оригинал: “https://dev.to/maddevs/a-bit-about-event-delegation-in-pure-js-2mk7”