Давайте теперь поговорим о событиях.
Наверное, события — это то, ради чего мы работаем с DOM-элементами вообще.
Ведь все наши страницы реагируют на наши действия: формы реагируют на клик,
ссылки реагируют на клик, и мы переходим на новую страницу.
Товары в корзине реагируют на плюсик, и мы добавляем их количество.
На любых современных страницах множество событий, которые обрабатываются.
Давайте теперь подробно разберем, как добавлять события и как их обрабатывать.
Так или иначе практически все DOM-элементы могут реагировать на те или иные события.
Это может быть событие загрузки, на которое реагирует вся страница,
событие загрузки какого-то элемента, например, картинки с сервера,
событие загрузки вашего JavaScript.
Так или иначе на какие-то события могут реагировать все DOM-элементы.
Но некоторые DOM-элементы могут реагировать на какие-то события,
которых нет у других.
Например, событие отправки, submit есть только у формы.
С полным списком событий вы можете ознакомиться в документации.
Самый простой способ добавить обработчик события на какой-то
элемент — это явно прописать его в качестве атрибута у этого элемента.
Для каждого события есть атрибут с префиксом on,
который будет ему соответствовать: onsubmit у формы,
onclick, например, у баттона.
А далее вы указываете в качестве значения атрибута джаваскриптовую функцию,
которая будет вызвана в качестве обработчика этого события.
Это рабочий способ, и он будет работать даже в самых старых браузерах,
но я бы настоятельно не рекомендовал вам им пользоваться.
Во-первых, вы не можете навешать более одного обработчика на какое-то
конкретное событие.
Одному атрибуту, например, onclick,
будет соответствовать только один конкретный обработчик.
Ваш JS код и ваш HTML код становятся очень тесно связаны.
Внутри HTML кода в качестве атрибута вы указываете тип события,
которое вы будете обрабатывать, а в качестве значения атрибута — имя
функции из JavaScript, из глобальной области видимости,
которую вы хотите назначить в качестве обработчика.
И меняя HTML код или меняя JavaScript код, вам очень важно всегда помнить,
что вы не можете переименовать значение атрибута, вы не можете переименовать тип
атрибута, иначе это будет другое событие, и вы не можете переименовать
джаваскриптовую функцию, иначе обработчик перестанет работать.
При этом при любых изменениях в JavaScript или в HTML,
при любом рефакторинге вам всегда важно помнить,
что эти функции должны быть сохранены, и эти атрибуты должны быть сохранены.
И DOM-дерево не может поменяться, и JavaScript не может поменяться.
И наконец, вы не можете навешивать эти обработчики динамически.
Эти свойства нельзя создать и присвоить им какое-то значение.
Они должны присутствовать в изначальном теле HTML документа.
Для решения всех этих проблем разработчики стандартов
W3C создали метод addEventListener, который добавляет
слушателя на какое-то событие, которое может произойти на DOM-элементе.
В нашем случае мы добавляем для формы на событие submit,
которое указывается в качестве первого параметра, слушателя,
который будет являться джаваскриптовой функцией.
Это самый простой вариант: один элемент, одно событие, один обработчик.
Но обработчиков может быть значительно больше, и вы можете добавить их целый пул,
таким образом создать стек обработки событий и разбить вашу обработку,
ее логику на несколько отдельных, не связанных друг с другом функций.
В каждую из этих функций в качестве первого
параметра будет передан объект event, объект «событие».
О нем мы поговорим чуть позже.
Все обработчики всегда вызываются в том порядке,
в котором они добавляются в качестве слушателей.
То есть мы вызвали addEventListener несколько раз, и ровно в этом
же порядке будут впоследствии вызываться функции при срабатывании события.
Контекстом любой функции обработки события всегда будет являться DOM-элемент,
на котором это событие произошло,
и на котором был вызван addEventListener для того, чтобы его послушать.
Как я уже говорил, в любую функцию обработчика событий в качестве
первого параметра передается объект события.
Обычно эту переменную называют event для того, чтобы не путать со всеми остальными.
И у нее есть несколько свойств, которые для нас особенно важны.
Прежде всего это свойство target, которое указывает на фактический
DOM-узел — самый последний листок DOM-дерева, на котором произошло событие.
Свойства altKey, ctrlKey, shiftKey,
которые говорят о том, была ли нажата клавиша Ctrl,
Shift или Alt соответственно во время того, как произошло событие.
И значение type, которое указывает
строкой тип события, который фактически сейчас произошел.
Существует два различных, противоположных способа обработки событий.
Это Bubbling и Capturing.
Разберем каждый из них подробно.
Всплытие события или Bubbling.
Это значение, которое принимается по умолчанию в браузерах,
и тот вариант обработки событий, который наиболее распространен.
При нем событие как бы поднимается вверх, от самого глубокого
DOM-узла — к самому верхнему и заканчивается на объекте document,
который описывает весь наш HTML документ.
Предположим, что наш HTML документ состоит из трех вложенных друг в друга квадратов.
И если мы кликнем по красному квадрату,
то событие постепенно будет подниматься сначала к синему, потом к зеленому и т.д.
Конечной точкой обработчика событий станет объект document.
И все события поднимаются от самого низа к самому верху,
постепенно вверх по DOM-дереву.
Для того, чтобы событие всплывало,
в качестве последнего параметра в addEventListener нужно
передать false либо не передавать ничего, так как это значение по умолчанию.
Противоположным является перехват событий или Capturing.
При этом обработчике событий событие погружается от самого верхнего элемента
к самому глубокому, самому нижнему, на котором оно могло бы произойти,
и сначала срабатывает на объекте document и постепенно погружается вниз.
При наших же трех квадратах это бы выглядело так: при клике в центре сначала
клик обрабатывается на зеленом, потом на синем и только потом — на красном.
Для того, чтобы событие перехватывалось,
в качестве последнего параметра в addEventListener вам нужно передать true.
Тогда событие будет постепенно погружаться,
и вы будете слушать его именно в этом варианте.
По умолчанию, как я уже говорил, все события всплывают,
и значение третьего параметра — false.
Иногда вы хотите остановить событие на каком-то
конкретном участке и не хотите, чтобы оно всплывало или погружалось далее.
Например, в данном случае при клике по квадрату событие сработает
только на нем и не будет подниматься вверх к синему, к зеленому и т.д.
Для того, чтобы остановить всплытие,
существует метод stopPropagation, который вызывается у объекта «событие».
То же самое можно сделать с Capturing, но работает он здесь следующим образом:
событие всегда срабатывает на самом верхнем элементе и не погружается далее.
Для того, чтобы во время перехвата события остановить погружение,
мы точно так же вызываем у объекта event метод stopPropagation.
Как я уже говорил, множество событий могут быть назначены на одном элементе,
и тогда эти обработчики выстроятся в стек и будут вызываться постепенно,
друг за другом, в порядке их добавления.
Но если мы этого не хотим, если мы хотим остановить
все обработчики в рамках этого элемента,
а также не давать им всплывать или погружаться, то мы можем вызвать
метод stopImmediatePropagation на требуемом элементе.
Тогда стек будет опустошен, и событие перестанет всплывать
или погружаться в зависимости от вашего варианта обработки.
Но пользоваться этим методом нужно очень осторожно,
так как вы можете удалить обработчик, который был поставлен другим человеком.
Поэтому я бы рекомендовал вам пользоваться очень аккуратно и дважды подумать о том,
действительно ли вы хотите прервать все потенциальные обработчики события на
этом элементе.
У многих DOM-элементов есть так называемые действия по умолчанию.
Например, при клике на ссылки, мы переходим по ссылке,
при клике по кнопке мы отправляем форму.
При клике внутри imput'а мы фокусируемся на input'е, и там у нас появляется курсор.
Для того чтобы отменить действие по умолчанию,
существует метод preventDefault, который вызывается у объекта-события.
И, таким образом, в случае формы, например, мы можем отменить ее отправку,
обработать ее и что-то сделать после этого.
Например, мы хотим отправить наш логин и пароль на сервер, отвалидировать и указать
пользователю, ввел он их правильно или нет, без отправки и перезагрузки страницы.
Благодаря тому что события могут всплывать или погружаться,
появился подход, который получил название «делегирование событий».
Это метод обработки события на родительском элементе относительно того,
на котором оно произошло фактически.
Например, у нас есть список, состоящий из множества ссылок.
И эти ссылки могут в нем появляться динамически,
в зависимости от каких-то действий пользователя.
И по всем из них нам необходимо обработать клик, чтобы
произвести переход на другую страницу или, например, чтобы показать попап.
Мы бы могли получить все ссылки,
пойти по ним в цикле и на каждую ссылку назначить свой обработчик событий.
Но при добавлении любой новой нам бы приходилось каждый раз делать это заново,
а при удалении какого-то элемента нам нужно было бы удалять за ним и события.
Мы можем поступить иначе: назначить обработчик на весь наш
список и ловить конкретные события на конкретных элементах.
В нашем случае это делегирование можно было бы оформить следующим образом.
Мы начинаем слушать событие клика на объекте document,
для которого всплывают все события.
После этого мы отменяем действия по умолчанию,
то есть отменяем переход по ссылке.
Далее мы обращаемся к свойству event.target и получаем фактически
элемент, на котором произошло событие, то есть то, куда мы в итоге кликнули,
и проверяем, является ли он ссылкой — но мы же могли кликнуть в любое другое место.
А далее, с помощью метода classList проверяем,
есть ли у этой ссылки тот или иной класс.
И в зависимости от этого, либо открываем наш попап, либо переходим по ссылке.
Делегирование — достаточно мощный инструмент,
который позволяет навешивать вам всего один обработчик на один
тип события и получать их с множества различных узлов.
Эти узлы могут создаваться динамически, могут удаляться,
но при этом вам не надо переживать за то, добавили ли вы обработку событий на него
или нет — любое событие всплывет и может быть обработано на объекте
document как самой высшей инстанции для обработки события.
Единственное, что вам стоит при этом учитывать — это то,
что у объекта события нет методов для проверки того,
на каком фактически элементе оно произошло и есть ли у этого элемента
какой-то класс или применим ли к нему какой-то CSS-селектор.
И из-за этого приходится городить такие не очень аккуратные конструкции в
виде нескольких if'ов, проверять тип тега, проверять наличие класс явно.
Я надеюсь, что подобные методы в будущем появятся.
Сейчас я бы вам рекомендовал поступать следующим образом: получать event.target.
Так как это может быть глубокий вложенный элемент, всегда помнить об этом,
подниматься на определенное количество уровней выше до какого-то
элемента с каким-то классом, который вас гарантированно интересует.
Например, в нашем случае это мог бы быть тег le с элементом списка.
И уже на нем проверять события и навешивать обработчики.
В этой лекции мы научились искать элементы на странице,
работать с их атрибутами, добавлять их, редактировать их,
создавать новые элементы и добавлять их на страницу,
обрабатывать на них события, а также мы изучили подход делегирования,
который помогает избежать большого количества обработчиков на странице.
Спасибо!