DOM - объектная модель, используемая для XML/HTML-документов
DOM – это представление документа в виде дерева объектов, доступное для изменения через JavaScript
DOM нужен для того, чтобы манипулировать страницей – читать информацию из HTML, создавать и изменять элементы.
Все элементы страницы, включая теги, текст, комментарии, являются узлами DOM
DOM
document.documentElement- Элемент <HTML>
document.body- Элемент <BODY>
По всем узлам:
- parentNode
- childNodes/first(last)child
- next(previous)Sibling
Только по элементам:
- parentElement
- children/first(last)ElementChild
- next(previous)ElementSibling
все навигационные свойства – только для чтения
Навигация
- parentElement – родитель-элемент.
- next(previous)ElementSibling - получает доступ к элементам слева и справа
- children - только дочерние узлы-элементы, то есть соответствующие тегам.
for (var i = 0; i < document.body.children.length; i++) {
alert( document.body.children[i] ); // DIV, UL, DIV, SCRIPT
}
elem.firstElementChild === elem.children[0]
elem.lastElementChild === elem.children[elem.children.length - 1]Поиск
Кроме того:
- Есть метод elem.matches(css), который проверяет, удовлетворяет ли элемент CSS-селектору. Он поддерживается большинством браузеров в префиксной форме (ms, moz, webkit).
- Метод elem.closest(css) ищет ближайший элемент выше по иерархии DOM, подходящий под CSS-селектор css. Сам элемент тоже включается в поиск.
- elemA.contains(elemB) Возвращает true, если elemA является предком (содержит) elemB.
var elements = document.querySelectorAll('ul > li:last-child');
for (var i = 0; i < elements.length; i++) {
alert( elements[i].innerHTML ); // "тест", "пройден"
}
var elems = document.body.children;
for (var i = 0; i < elems.length; i++) {
if (elems[i].matches('a[href$="zip"]')) {
alert( "Ссылка на архив: " + elems[i].href );
}
}
var numberSpan = document.querySelector('.num');
// ближайший элемент сверху подходящий под селектор li
alert(numberSpan.closest('li').className) // subchapter
// ближайший элемент сверху подходящий под селектор .chapter
alert(numberSpan.closest('.chapter').tagName) // LI
// ближайший элемент сверху, подходящий под селектор span
// это сам numberSpan, так как поиск включает в себя сам элемент
alert(numberSpan.closest('span') === numberSpan) // trueСвойства узлов
- node.nodeType - Тип узла: 1(элемент) / 3(текст) / другие.
- elem.tagName - Тег элемента.
- elem.innerHTML - HTML внутри элемента.
- elem.outerHTML - Весь HTML элемента, включая сам тег. На запись использовать с осторожностью, так как не модифицирует элемент, а вставляет новый вместо него.
- node.data / node.nodeValue - Содержимое узла любого типа, кроме элемента.
- node.textContent - Текстовое содержимое узла, для элементов содержит текст с вырезанными тегами (IE9+).
- elem.hidden - Если поставить true, то элемент будет скрыт (IE10+).
Атрибуты
- elem.hasAttribute(name) – проверяет наличие атрибута
- elem.getAttribute(name) – получает значение атрибута
- elem.setAttribute(name, value) – устанавливает атрибут
- elem.removeAttribute(name) – удаляет атрибут
Свойство classList – это объект для работы с классами.
- elem.classList.contains("class") – возвращает true/false, в зависимости от того, есть ли у элемента класс class.
- elem.classList.add/remove("class") – добавляет/удаляет класс class
- elem.classList.toggle("class") – если класса class нет, добавляет его, если есть – удаляет.
elem.dataset.* - Значения атрибутов вида data-* (IE10+).
elem.attributes все атрибуты элемента
elem.className в виде строки
<div id="elem" about="Elephant"></div>
alert( elem.getAttribute('About') ); // (1) 'Elephant', атрибут получен
elem.setAttribute('Test', 123); // (2) атрибут Test установлен
var attrs = elem.attributes; // (4) можно получить коллекцию атрибутов
for (var i = 0; i < attrs.length; i++) {
alert( attrs[i].name + " = " + attrs[i].value );
}
<body class="main page">
var classList = document.body.classList;
classList.remove('page'); // удалить класс
classList.add('post'); // добавить класс
for (var i = 0; i < classList.length; i++) { // перечислить классы
alert( classList[i] ); // main, затем post
}
alert( classList.contains('post') ); // проверить наличие класса
<div id="elem" data-about="Elephant" data-user-location="street"> </div>
alert( elem.dataset.about ); // Elephant
alert( elem.dataset.userLocation ); // street
Добавление/изм. узлов
Создание
-
document.createElement(tag)- Создать элемент с тегом tag
-
document.createTextNode(txt) - Создать текстовый узел с текстом txt
-
node.cloneNode(deep) - Клонировать существующий узел, если deep=false, то без потомков.
Вставка и удаление
- parent.appendChild(elem) Добавляет elem в конец дочерних элементов parentElem.
- parent.insertBefore(elem, nextSibling) Вставляет elem в коллекцию детей parentElem, перед элементом nextSibling.
- parent.insertAdjacentHTML("beforeBegin|afterBegin|beforeEnd|afterEnd", html)
- parent.removeChild(elem) Удаляет elem из списка детей parentElem.
- parent.replaceChild(newElem, elem) Среди детей parentElem удаляет elem и вставляет на его место newElem.
innerHTML вставит именно HTML, а createTextNodeинтерпретирует теги как текст.
var div = document.createElement('div');
var textElem = document.createTextNode('Тут был я');
<ol id="list">
<li>0</li>
<li>1</li>
<li>2</li>
</ol>
var newLi = document.createElement('li');
newLi.innerHTML = 'Привет, мир!';
list.appendChild(newLi);
list.insertBefore(newLi, list.children[1]);// создать копию узла
var div2 = div.cloneNode(true);
// копию можно подправить
div2.querySelector('strong').innerHTML = 'Супер!';
// вставим её после текущего сообщения
div.parentNode.insertBefore(div2, div.nextSibling);var data = prompt("Введите текст для пункта списка", "");
var li = document.createElement('li');
li.appendChild(document.createTextNode(data));
ul.appendChild(li);Стили
- elem.style- Стили в атрибуте style элемента (единицы измерения обязательны)
- Единицы измерения обязательны
- style.cssFloat вместо style.float
- style.cssText позволяет поставить стиль целиком в виде строки.
-
getComputedStyle(element[, pseudo]) получить текущее используемое значение свойства с учетом всего каскада, вычисленный и примененный (только чтение)
document.body.style.backgroundColor = prompt('background color?', 'green');
div.style.cssText="color: red !important; \
background-color: yellow; \
width: 100px; \
text-align: center; \
blabla: 5; \
";
var computedStyle = getComputedStyle(document.body);
alert( computedStyle.marginTop ); Размеры и прокрутка
Элемента
- offsetParent - родительский элемент
- offsetLeft/Top смещение относительно offsetParent
- offsetWidth/Height Полный размер элемента: ширина/высота, включая border.
- clientLeft/Top Ширина левой/верхней рамки border
- clientWidth/Height Ширина/высота внутренней части элемента, включая содержимое и padding, не включая полосу прокрутки (если есть).
- scrollWidth/Height Ширина/высота внутренней части элемента, с учетом прокрутки.
- scrollLeft/Top Ширина/высота прокрученной области.
Размеры и прокрутка
Страницы
document.documentElement.clientHeight - ширина/высота видимой области
прокрутка(изменение):
- window.scrollBy(x,y): на x,y относительно текущей позиции.
- window.scrollTo(pageX, pageY): на координаты в документе.
- elem.scrollIntoView(true/false): прокрутить, чтобы elem стал видимым и оказался вверху окна(true) или внизу(false)
Чтобы запретить прокрутку страницы, достаточно поставить document.body.style.overflow = "hidden".
Координаты
- относительно окна: elem.getBoundingClientRect()
- относительно документа: elem.getBoundingClientRect() + прокрутка страницы
- получить элемент по координатам: document.elementFromPoint(clientX, clientY)
События(Triggers)
- Мыши
- на элементах управления
- Клавиатурные
- Документа
- CSS
Событие – это сигнал от браузера о том, что что-то произошло. Существует много видов событий. Посмотрим список самых часто используемых, пока просто для ознакомления:
События мыши:
- click – происходит, когда кликнули на элемент левой кнопкой мыши
- contextmenu – происходит, когда кликнули на элемент правой кнопкой мыши
- mouseover – возникает, когда на элемент наводится мышь
- mousedown и mouseup – когда кнопку мыши нажали или отжали
- mousemove – при движении мыши
Мышь
События на элементах управления:
- submit – посетитель отправил форму <form>
- focus – посетитель фокусируется на элементе, например нажимает на <input>
Клава/док
Клавиатурные события:
- keydown – когда посетитель нажимает клавишу
- keyup – когда посетитель отпускает клавишу
События документа:
- DOMContentLoaded – когда HTML загружен и обработан, DOM документа полностью построен и доступен.
События CSS:
- transitionend – когда CSS-анимация завершена.
Обработчики
- Атрибут HTML: onclick="...".
- Свойство: elem.onclick = function(){}.
невозможность повесить несколько обработчиков на одно событие. - elem.addEventListener( событие, handler[, phase]), удаление через removeEventListener.
<input type="button" id="button" onclick="sayThanks()" />
button.onclick = sayThanks;
elem.onclick = function() {
alert( 'Спасибо' );
};
elem.addEventListener( "click" , function() {alert('Спасибо!')});Внутри обработчика события this ссылается на текущий элемент, то есть на тот, на котором он сработал.
Достоинства
- Некоторые события можно назначить только через addEventListener.
- Метод addEventListener позволяет назначить много обработчиков на одно событие.
Недостатки
- Обработчик, назначенный через onclick, проще удалить или заменить.
- Метод onclick кросс-браузерный.
Порядок обработки событий
- JavaScript выполняется в едином потоке. Современные браузеры позволяют порождать подпроцессы Web Workers, они выполняются параллельно и могут отправлять/принимать сообщения, но не имеют доступа к DOM.
- Обычно события становятся в очередь и обрабатываются в порядке поступления, асинхронно, независимо друг от друга.
- Синхронными являются вложенные события, инициированные из кода.
- Чтобы сделать событие гарантированно асинхронным, используется вызов через setTimeout(func, 0).
Отложенный вызов через setTimeout(func, 0) используется не только в событиях, а вообще – всегда, когда мы хотим, чтобы некая функция func сработала после того, как текущий скрипт завершится.
Объект события
elem.onclick = function(event) {
// вывести тип события, элемент и координаты клика
alert(event.type + " на " + event.currentTarget);
alert(event.clientX + ":" + event.clientY);
}
element.onclick = function(event) {
event = event || window.event;
// Теперь event - объект события во всех браузерах.
};- Объект события содержит ценную информацию о деталях события.
- Он передается первым аргументом event в обработчик
event.type Тип события
event.clientX / event.clientY Координаты курсора в момент клика (относительно окна)
event.target это исходный элемент, на котором произошло событие, в процессе всплытия он неизменен
event.currentTarget(this) Элемент, на котором сработал обработчик. текущий элемент, до которого дошло всплытие, на нём сейчас выполняется обработчик.
Всплытие и перехват
При наступлении события обработчики сначала срабатывают на самом вложенном элементе, затем на его родителе, затем выше и так далее, вверх по цепочке вложенности.
события «всплывают» от внутреннего элемента вверх через родителей, подобно тому, как всплывает пузырек воздуха в воде.
Самый глубокий элемент, который вызывает событие, называется «целевым» или «исходным»элементом и доступен как event.target.
Отличия от this (=event.currentTarget):
- event.target – это исходный элемент, на котором произошло событие, в процессе всплытия он неизменен.
- this – это текущий элемент, до которого дошло всплытие, на нём сейчас выполняется обработчик.
event.stopPropagation(). Для остановки всплытия
три стадии прохода события:
- Событие сначала идет сверху вниз. Эта стадия называется «стадия перехвата» (capturing stage).
- Событие достигло целевого элемента. Это – «стадия цели» (target stage).
- После этого событие начинает всплывать. Это – «стадия всплытия» (bubbling stage).
Алгоритм:
- При наступлении события – элемент, на котором оно произошло, помечается как «целевой» (event.target).
- Далее событие сначала двигается вниз от корня документа к event.target, по пути вызывая обработчики, поставленные через addEventListener(...., true).
- Далее событие двигается от event.target вверх к корню документа, по пути вызывая обработчики, поставленные через on* и addEventListener(...., false).
Каждый обработчик имеет доступ к свойствам события:
- event.target – самый глубокий элемент, на котором произошло событие.
- event.currentTarget (=this) – элемент, на котором в данный момент сработал обработчик (до которого «доплыло» событие).
- event.eventPhase – на какой фазе он сработал (погружение =1, всплытие = 3).
Любой обработчик может остановить событие вызовом event.stopPropagation(), но делать это не рекомендуется, так как в дальнейшем это событие может понадобиться, иногда для самых неожиданных вещей.
Делегирование
Обычно делегирование – это средство оптимизации интерфейса. Мы используем один обработчик для схожихдействий на однотипных элементах.
Алгоритм:
- Вешаем обработчик на контейнер.
- В обработчике: получаем event.target.
- В обработчике: если event.target или один из его родителей в контейнере (this) – интересующий нас элемент – обработать его.
table.onclick = function(event) {
var target = event.target;
// цикл двигается вверх от target к родителям до table
while (target != table) {
if (target.tagName == 'TD') {
// нашли элемент, который нас интересует!
highlight(target);
return;
}
target = target.parentNode;
}
// возможна ситуация, когда клик был вне <td>
// если цикл дошёл до table и ничего не нашёл,
// то обработчик просто заканчивает работу
}table.onclick = function(event) {
var target = event.target;
var td = target.closest('td');
if (!td) return; // клик вне <td>, не интересует
// если клик на td, но вне этой таблицы (возможно при вложенных таблицах)
// то не интересует
if (!table.contains(td)) return;
// нашли элемент, который нас интересует!
highlight(td);
}Плюсы
- Упрощает инициализацию и экономит память: не нужно вешать много обработчиков.
- Меньше кода: при добавлении и удалении элементов не нужно ставить или снимать обработчики.
- Удобство изменений: можно массово добавлять или удалять элементы путём изменения innerHTML.
Минусы
- Во-первых, событие должно всплывать. Нельзя, чтобы какой-то промежуточный обработчик вызвал event.stopPropagation() до того, как событие доплывёт до нужного элемента.
- Во-вторых, делегирование создает дополнительную нагрузку на браузер, ведь обработчик запускается, когда событие происходит в любом месте контейнера, не обязательно на элементах, которые нам интересны. Но обычно эта нагрузка настолько пустяковая, её даже не стоит принимать во внимание.
Приём проектирования "поведение"
Шаблон проектирования «поведение» (behavior) позволяет задавать хитрые обработчики на элементе декларативно, установкой специальных HTML-атрибутов и классов.
Приём проектирования «поведение» состоит из двух частей:
- Элементу ставится атрибут, описывающий его поведение.
- При помощи делегирования ставится обработчик на документ, который ловит все клики и, если элемент имеет нужный атрибут, производит нужное действие.
document.onclick = function(event) {
var target = event.target;
var id = target.getAttribute('data-toggle-id');
if (!id) return;
var elem = document.getElementById(id);
elem.hidden = !elem.hidden;
};Счётчик:
<button data-counter>1</button>
Ещё счётчик:
<button data-counter>2</button>
<script>
document.onclick = function(event) {
if (!event.target.hasAttribute('data-counter')) return;
var counter = event.target;
counter.innerHTML++;
};
</script>Отмена действий браузера
Есть два способа отменить действие браузера:
- Основной способ – это воспользоваться объектом события. Для отмены действия браузера существует стандартный метод event.preventDefault().
- Если же обработчик назначен через onсобытие (не через addEventListener), то можно просто вернуть falseиз обработчика.
<a id="linkToGoogle" href="http://google.com">Google<a>
<script>
var linkToGoogle = document.querySelector('linkToGoogle');
linkToGoogle.addEventListener('click', function (e) {
e.preventDefault();
window.location.href = 'http://yandex.ru'; // Найдется всё!
});
</script>
<a href="/" onclick="return false">Нажми здесь</a>
или
<a href="/" onclick="event.preventDefault()">здесь</a>- document.forms[name/index] - получить форму по имени или индексу
- form.elements[name/index] - элемент формы( если несколько, возвращает коллекцию). Также есть у fieldset
- element.form по элементу опеределить форму
document.forms.my -- форма с именем 'my'
document.forms[0] -- первая форма в документе
<fieldset name="set">
<legend>fieldset</legend>
<input name="text" type="text">
</fieldset>
var form = document.forms.my; // можно document.forms[0]
var elem = form.elements.one; // можно form.elements[0]
alert( form.elements.set.elements.text ); //для fieldset
alert(elem.form == form); // trueФормы и элементы управления
- Для большинства типов input значение ставится/читается через свойство value. Исключение Текущее «отмеченное» состояние для checkbox и radio находится в свойстве checked (true/false).
- Для элемента select можно задать опцию по номеру через select.selectedIndex и перебрать опции через select.options. При этом выбранные опции (в том числе при мультиселекте) будут иметь свойство option.selected = true.
input.value = "Новое значение";
textarea.value = "Новый текст";
if (input.checked) {
alert( "Чекбокс выбран" );
}
<form name="form">
<select name="genre" multiple>
<option value="blues" selected>Мягкий блюз</option>
<option value="rock" selected>Жёсткий рок</option>
<option value="classic">Классика</option>
</select>
</form>
var form = document.forms[0];
var select = form.elements.genre;
for (var i = 0; i < select.options.length; i++) {
var option = select.options[i];
if(option.selected) {
alert( option.value );
}
}
select.selectedIndex = 0; // первая опция
var selectedOption = select.options[select.selectedIndex];var option = new Option("Текст", "value", true, true);selected выбрана ли опция
index номер опции в списке селекта
text Текстовое содержимое опции (то, что видит посетитель).
События focus/blur происходят при получении и снятия фокуса с элемента.
-
Они не всплывают. Но на фазе перехвата их можно перехватить. Это странно, но это так, не спрашивайте почему.
Везде, кроме Firefox, поддерживаются всплывающие альтернативы focusin/focusout.
-
По умолчанию многие элементы не могут получить фокус. Например, если вы кликните по DIV, то фокусировка на нем не произойдет.
Но это можно изменить, если поставить элементу атрибут tabIndex. Этот атрибут также дает возможность контролировать порядок перехода при нажатии Tab.
Текущий элемент на котором фокус: document.activeElement
| Событие | Описание |
|---|---|
| change | Изменение значения любого элемента формы. Для текстовых элементов срабатывает при потере фокуса |
| input | Событие срабатывает только на текстовых элементах. Оно не ждет потери фокуса, в отличие от change. |
При генерации события submit Обработчик в нём может проверить данные и, если они неверны, то вывести ошибку и сделать event.preventDefault() – тогда форма не отправится на сервер.
Чтобы отправить форму на сервер из JavaScript – нужно вызвать на элементе формы метод form.submit().
Создание графических компонентов
Основные принципы:
- Виджет – это объект, который либо контролирует готовое дерево DOM, либо создаёт своё.
- В конструктор виджета передаётся объект аргументов options.
- Виджет при необходимости создаёт элемент или «оживляет» готовый. Внутри в разметке не используются id.
- Обработчики назначаются через делегирование – для производительности и упрощения виджета.
- Обработчики событий вызывают соответствующий метод, не пытаются делать всё сами.
- При инициализации, если существенный участок работы можно отложить до реального задействования виджета – откладываем его.
function Menu(options) {
var elem;
function getElem() {
if (!elem) render();
return elem;
}
function render() {
elem = document.createElement('div');
elem.className = "menu";
var titleElem = document.createElement('span');
elem.appendChild(titleElem);
titleElem.className = "title";
titleElem.textContent = options.title;
elem.onmousedown = function() {
return false;
};
elem.onclick = function(event) {
if (event.target.closest('.title')) {
toggle();
}
}
}
function renderItems() {
var items = options.items || [];
var list = document.createElement('ul');
items.forEach(function(item) {
var li = document.createElement('li');
li.textContent = item;
list.appendChild(li);
});
elem.appendChild(list);
}
// продолжение
function open() {
if (!elem.querySelector('ul')) {
renderItems();
}
elem.classList.add('open');
};
function close() {
elem.classList.remove('open');
};
function toggle() {
if (elem.classList.contains('open')) close();
else open();
};
this.getElem = getElem;
this.toggle = toggle;
this.close = close;
this.open = open;
}// создать объект меню с данным заголовком и опциями
var menu = new Menu({
title: "Сладости",
items: [
"Торт",
"Пончик",
"Пирожное",
"Шоколадка",
"Мороженое"
]
});
// получить сгенерированный DOM-элемент меню
var elem = menu.getElem();
// вставить меню в нужное место страницы
document.body.appendChild(elem);class Voter {
constructor({element} = {}) {
this.element = element;
this.voteElement = element.querySelector('.vote');
this.initiate();
}
initiate() {
this.element.addEventListener('click', e => {
if (e.target.closest('.up')) {
this.upVote();
} else
if (e.target.closest('.down')) {
this.downVote();
}
});
this.element.addEventListener('mousedown', e => {
e.preventDefault();
});
}
upVote() {
this.voteElement.textContent++;
}
downVote() {
this.voteElement.textContent--;
}
setVote(value) {
this.voteElement.textContent = value;
}
}function Voter(options) {
var elem = this._elem = options.elem;
this._voteElem = elem.querySelector('.vote');
elem.onmousedown = function() {
return false;
};
elem.onclick = this._onClick.bind(this);
}
Voter.prototype._onClick = function(event) {
if (event.target.closest('.down')) {
this._voteDecrease();
} else if (event.target.closest('.up')) {
this._voteIncrease();
}
};
Voter.prototype._voteIncrease = function() {
this._voteElem.innerHTML = +this._voteElem.innerHTML + 1;
};
Voter.prototype._voteDecrease = function() {
this._voteElem.innerHTML = +this._voteElem.innerHTML - 1;
};
Voter.prototype.setVote = function(vote) {
this._voteElem.innerHTML = +vote;
};DOM
By Anastasia Shemetillo
DOM
- 117