HTML / CSSJavaScriptNode jsПаттерны проектированияПрактические

Введение в DOM.

DOM (Document Object Model) - внутренние объекты и функции браузера, хранящие состояние страницы.JS через это API имеет полный доступ ко всему тому, что есть в HTML и CSS.

Корень

Корнем дерева элементов DOM является объект document

Поиск элементов

Что бы найти элемент, нужно обратится к методу document, или любого другого элемента, в котором нужно что-то найти:

1var el = document.getElementById("someId"); //обратите внимания, без #
2//в последних браузерах все `id` автоматически попадают в глобальную область видимости (в объект window)
3var el2 = document.querySelector("#someId"); //поиск по любому селектору, аналог jQuery
4var el3 = document.querySelectorAll("a"); //поиск всех тегов a

Создание элементов DOM

1document.createElement("a"); //обратите внимание, без <>

Добавление элементов:

1var tr = document.createElement("tr");
2var td = document.createElement("td");
3var td2 = document.createElement("td");
4
5tr.appendChild(td); //добавление ячейки в конец строки таблицы.
6tr.insertBefore(td2, tr.childNodes[0]); //добавление ячейки перед первой ячейкой (в самое начало строки таблицы)

Ссылка на родительский элемент находится в свойстве parentElement:

1tr.childNodes[0].parentElement === tr

Свойства объектов или наборов объектов элементов в DOM

  • value - свойство а не функция для значения поля ввода.
  • attributes - объект attributes с атрибутами html-тега. Также есть 4 функции для работы с атрибутами:
  • hasAttribute - проверка на наличие атрибута
  • getAttribute - чтение
  • setAttribute - запись
  • removeAttribute - удаление
  • style - объект стиля элемента
  • innerHTML - строка вложенного HTML в элементе.
  • innerText - строка вложенного текста в элементе.

Нюансы

Элемент в дереве

может встречаться только один раз. Вы не можете вставить элемент дважды в дерево. Если вы хотите создать копию элемента в вашем DOM-дереве, используйте cloneNode.

HTML/CSS

Всё, что вы видели в HTML/CSS может быть установлено как свойства объекта-узла DOM и тут же будет отображено в браузере

children и childNodes

  • Узлами (Node) может быть любой текст в HTML, в том числе обычный текст и тот или иной тэг. Дочерние элементы каждого элемента находятся в псевдомассиве childNodes
  • В псевдомассиве children находятся только дочерние узлы-теги, но без обычного текста.

например

HTMLCollection это список узлов. Отдельный узел может быть доступен по порядковому номеру или имени узла и атрибута. Коллекции в HTML DOM являются живыми. Коллекции обновляются при изменении документа. Коллекция HTML всегда находится в DOM, в то время как NodeList является более универсальной конструкцией, которая может или не может быть в DOM.

NodeList является коллекцией узлов. NodeList обеспечивает уровень абстракции над коллекцией узлов, не определяя и не ограничивая как это коллекция используется. Объекты NodeList являются “живыми” или статическим, в зависимости от способа используемого для их получения.

События

Каждый элемент DOM содержит множество свойств on..., в которые вы можете занести тот или иной обработчик события:

1document.onmousemove = function(){
2 document.write("mouse move <br/>");
3}

Так же можно добавлять обработчики используя метод элемента addEventListener:

1document.addEventListener("mousemove",function(){
2 document.write("mouse move <br/>");
3});

Как было уже отмечено, DOM - это дерево из элементов верстки страницы, каждый из которых представлен объектом Javascript. Кроме данных, в объектах бывают те или иные действия, которые объект может осуществлять - методы.

Установка обработчиков

Для обработки событий вы можете назначить функцию-обработчик события путем присвоения ключа объекта:

1var element = document.getElementById('root')
2element.onmousemove = function(event){
3 console.log(this, event);
4}

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

1var element = document.getElementById('root')
2
3function eventHandler(event){
4 console.log(this, event);
5}
6
7
8element.addEventListener("mousemove",eventHandler);
9element.addEventListener("click",eventHandler);

Данные, передаваемые в обработчик

В обработчик события передается два аргумента:

  • this, который ссылается на элемент, на который "навешен" обработчик. Используя this вы можете узнать любую информацию о элементе и навешивать один и тот же обработчик на несколько элементов, если вам нужно схожее поведение на многих элементах.
  • event, объект-событие, переданный первым аргументом в функцию-обработчик (вы можете назвать его как угодно). С помощью этого объекта можно узнать информацию о событии (нажатые кнопки мыши и клавиатуры, положение курсора мыши, и т. п.), а так же управлять обработкой потока событий:
  • event.stopPropagation() останавливает всплытие событий (например, click всплывает от вложенных элементов к обрамляющим). Обрамляющие элементы не узнают о событии.
  • event.stopImmediatePropagation() аналогичен предыдущему, однако не будут запущены даже следующие обработчики на этом элементе
  • event.preventDefault() запрещает браузеру запускать обработчики по умолчанию (отправку данных для submit формы, переход по клику на ссылку <a> и т. п.)

addEventListener

Первый аргумент метода addEventListener - это тип события ( строка ), например: "mouseover" "mouseout" "input" "change" ... Второй аргумент - ссылка на функцию ( обработчика события ) Третий аргумент - логическое значение - будучи установленным в true, позволяет перехватить событие на фазу погружения ( capturing )

1var element = document.getElementById('root')
2
3function eventHandler(event){
4 console.log(this, event);
5}

removeEventListener

Прослушивателей событий нужно удалять, поскольку они не убираются автоматически при удалении элемента

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

🚫 Не правильно

1document.getElementById ( 'sample' )
2 .addEventListener ( 'click', function ( event ) {
3 console.log ( 'sample click event: ', event )
4 })
5document.getElementById ( 'sample' )
6 .removeEventListener ( 'click', function ( event ) {
7 console.log ( 'sample click event: ', event )
8 })

✅ Правильно

1var elem = document.getElementById ( 'element' )
2function clickHandler ( event ) {
3 this.innerHTML = '<small>My content was changed!</small>'
4}
5elem.addEventListener ( 'click', clickHandler )
6elem.removeEventListener ( 'click', clickHandler )

Всплытие событий

1<div onclick="alert('Hello!')">
2 <em>Если вы кликните на <code>EM</code>, сработает обработчик на <code>DIV</code></em>
3</div>

Всплытие

Принцип всплытия очень простой. Когда на элементе происходит событие, обработчики сначала срабатывают на нём, потом на его родителе, затем выше и так далее, вверх по цепочке предков.

Например, есть 3 вложенных элемента FORM > DIV > P с обработчиком на каждом

1<form onclick="alert('form')">FORM
2 <div onclick="alert('div')">DIV
3 <p onclick="alert('p')">P</p>
4 </div>
5</form>
Original source

Клик по внутреннему <p> вызовет обработчик onclick:

  1. Сначала на самом <p>.
  2. Потом на внешнем <div>.
  3. Затем на внешнем <form>. И так далее вверх по цепочке до самого document.

capturing and bubbling

Поэтому если кликнуть на <p>, то мы увидим три оповещения: p → div → form.

Этот процесс называется «всплытием», потому что события «всплывают» от внутреннего элемента вверх через родителей подобно тому, как всплывает пузырёк воздуха в воде.

event.target

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

Самый глубокий элемент, который вызывает событие, называется целевым элементом, и он доступен через event.target.

ℹ️Отличия от this = evet.currentTarget

event.target – это «целевой» элемент, на котором произошло событие, в процессе всплытия он неизменен.

this – это «текущий» элемент, до которого дошло всплытие, на нём сейчас выполняется обработчик.

Например, если стоит только один обработчик form.onclick, то он «поймает» все клики внутри формы. Где бы ни был клик внутри – он всплывёт до элемента <form>, на котором сработает обработчик. При этом внутри обработчика form.onclick:

  • this = event.currentTarget всегда будет элемент <form>, так как обработчик сработал на ней.
  • event.target будет содержать ссылку на конкретный элемент внутри формы, на котором произошёл клик.

Прекращение всплытия

Всплытие идёт с «целевого» элемента прямо наверх. По умолчанию событие будет всплывать до элемента <html>, а затем до объекта document, а иногда даже до window, вызывая все обработчики на своём пути. Но любой промежуточный обработчик может решить, что событие полностью обработано, и остановить всплытие. Для этого нужно вызвать метод event.stopPropagation().

Погружение

Существует ещё одна фаза из жизненного цикла события – «погружение» (иногда её называют «перехват»). Она очень редко используется в реальном коде, однако тоже может быть полезной.

Стандарт DOM Events описывает 3 фазы прохода события:

  • Фаза погружения (capturing phase) – событие сначала идёт сверху вниз.
  • Фаза цели (target phase) – событие достигло целевого(исходного) элемента.
  • Фаза всплытия (bubbling stage) – событие начинает всплывать.

Изображение ниже из спецификации демонстрирует, как это работает при клике по ячейке <td>, расположенной внутри таблицы:

capturing

При клике на <td> событие путешествует по цепочке родителей сначала вниз к элементу (погружается), затем оно достигает целевой элемент (фаза цели), а потом идёт наверх (всплытие), вызывая по пути обработчики. Ранее мы говорили только о всплытии, потому что другие стадии, как правило, не используются и проходят незаметно для нас.

Обработчики, добавленные через on<event>-свойство или через HTML-атрибуты, или через addEventListener(event, handler)с двумя аргументами, ничего не знают о фазе погружения, а работают только на 2-ой и 3-ей фазах. Чтобы поймать событие на стадии погружения, нужно использовать третий аргумент capture вот так:

1var elem = document.getElementById('root');
2function handler(){}
3elem.addEventListener(handler, {capture: true})
4// или просто "true", как сокращение для {capture: true}
5elem.addEventListener(handler, true)

Существуют два варианта значений опции capture:

  • Если аргумент false (по умолчанию), то событие будет поймано при всплытии.
  • Если аргумент true, то событие будет перехвачено при погружении.

Обратите внимание, что хоть и формально существует 3 фазы, 2-ую фазу («фазу цели»: событие достигло элемента) нельзя обработать отдельно, при её достижении вызываются все обработчики: и на всплытие, и на погружение.

Давайте посмотрим и всплытие и погружение в действии:

Пример 2

Здесь обработчики навешиваются на каждый элемент внутри элемента с id ex2, чтобы увидеть в каком порядке они вызываются по мере прохода события.

Если вы кликните по <p>, то последовательность следующая:

FORM → DIV → p (фаза погружения, первый обработчик) P (фаза цели, срабатывают обработчики, установленные и на погружение и на всплытие, так что выведется два раза) P→ DIV → FORM (фаза всплытия, второй обработчик) Существует свойство event.eventPhase, содержащее номер фазы, на которой событие было поймано. Но оно используется редко, мы обычно и так знаем об этом в обработчике.

❗️Важно

Чтобы убрать обработчик removeEventListener, нужна та же фаза Если мы добавили обработчик вот так addEventListener(..., true), то мы должны передать то же значение аргумента capture в removeEventListener(..., true), когда снимаем обработчик.

Hello