Лекция
Тема: «Работа с DOM-деревом документа»
План
1. Понятие DOM дерева
2. Переход по элементам DOM
3. Поиск элементов в DOM
4. Свойства узлов
5. Работа со стилями и классами
6. Добавление и удаление узлов в DOM
1. Понятие DOM дерева
Каждый браузер дает доступ к иерархии объектов, которые мы можем
использовать для разработки.
На рисунке схематически отображена структура основных браузерных объектов.

На вершине стоит window, который еще называют глобальным объектом.
Все остальные объекты делятся на 3 группы.
·
Объектная
модель документа (DOM).
Доступна через document.
Дает доступ к содержимому страницы.
·
Объектная
модель браузера (BOM).
BOM
— это объекты
для работы с чем угодно, кроме документа. Доступ к фреймам, запросы к серверу, функции alert/confirm/prompt -
все это BOM.
·
Объекты
и функции JavaScript.
Javascript — связующий все это
язык.
Основным инструментом работы и динамических изменений на странице является DOM (Document Object Model) - объектная модель, используемая для XML/HTML-документов.
Согласно DOM-модели, документ является иерархией. Каждый HTML-тег образует отдельный элемент-узел, каждый фрагмент текста - текстовый элемент, и т.п.
Проще говоря, DOM - это представление документа в виде дерева тегов. Это дерево образуется за счет вложенной структуры тегов плюс текстовые фрагменты страницы, каждый из которых образует отдельный узел.
Построим, для начала, дерево DOM для следующего документа.
<html>
<head>
<title>Заголовок</title>
</head>
<body>
Прекрасный документ
</body>
</html>
Самый внешний тег - <html>, поэтому дерево начинает расти от него.
Внутри <html> находятся два узла: <head> и <body> - они становятся дочерними узлами для <html>.

Рассмотрим теперь более жизненную страничку:
<html>
<head>
<title> О лосях </title>
</head>
<body>
Правда о лосях.
<ol>
<li> Лось - животное хитрое </li>
<li> .. И коварное </li>
</ol>
</body>
</html>
Корневым элементом иерархии является html. У него есть два потомка. Первый - head, второй - body. И так далее, каждый вложенный тег является потомком тега выше:

На этом рисунке синим цветом обозначены элементы-узлы, черным - текстовые элементы.
Дерево образовано за счет синих элементов-узлов - тегов HTML.
Разбор дерева в браузере
При разборе HTML браузер кроме тегов и текстовых блоков также распознает пустые текстовые блоки, образуемые символом перевода строки. Продемонстрируем это на примере. Пусть имеется страница вида:
Рассмотрим чуть более сложный документ.
<html>
<head>
<title>Документ</title>
</head>
<body>
<div id="dataKeeper">Data</div>
<ul>
<li style="background-color:red">Осторожно</li>
<li class="info">Информация</li>
</ul>
<div id="footer">Made in Russia
</div>
</body>
</html>
Верхний тег - html, у него дети head и body, и так далее. Получается дерево тегов:

Обратите внимание, что все текстовые блоки на рисунке обозначены символом "#". В конце каждого тега был выполнен переход клавишей Enter. Такой перевод генерирует пустой текстовый блок. Такие пустые блоки не отображаются на странице, но влияют на количество элементов в дереве и на порядок обхода по дереву.
DOM нужен для того, чтобы манипулировать страницей - читать информацию из HTML, создавать и изменять элементы. У элементов DOM есть свойства и методы, которые позволяют изменять их.
Например, можно поменять цвет BODY и вернуть обратно:
document.body.style.backgroundColor
= 'red';
alert('Поменяли цвет
BODY');
document.body.style.backgroundColor
= '';
alert('Сбросили цвет BODY');
Фактически, DOM предоставляет возможность делать со страницей всё, что угодно.
2. Переход по элементам DOM
Для того, чтобы изменить узел DOM, например, раскрыть в нем меню, нужно сначала его получить.
Доступ к DOM начинается с document. Оттуда можно добраться до любых других узлов. Корень: documentElement и body
Войти в «корень» дерева можно двумя путями.
Первая точка входа — document.documentElement. Это свойство ссылается на DOM-объект для тега HTML. Вторая точка входа — document.body, который соответствует тегу BODY.

Нельзя получить доступ к элементу, которого еще не существует в момент выполнения скрипта.
В следующем примере, первый alert выведет null:
<html>
<head>
<script>
alert("Из HEAD:
" + document.body); // null
</script>
</head>
<body>
<script>
alert("Из BODY:
" + document.body);
</script>
</body>
</html>
В мире DOM для свойств-ссылок на узлы в качестве значения «нет такого элемента» или «узел не найден» используется не undefined, а null.
Дочерние элементы
Из узла-родителя можно получить все дочерние элементы. Для этого есть несколько способов.
·
childNodes
Псевдо-массив childNodes хранит все дочерние элементы, включая текстовые, в том числе и пустые.
Пример ниже последовательно выведет дочерние элементы document.body:
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<div>Пользователи:</div>
<ul>
<li>Маша</li>
<li>Вовочка</li>
</ul>
<!-- комментарий -->
<script>
var childNodes = document.body.childNodes;
for(var i=0; i<childNodes.length; i++)
{
alert (childNodes[i]);
}
</script>
</body>
</html>
Во всех браузерах document.body.childNodes[0] это пустой текстовый узел после тега body, а
DIV - второй потомок: document.body.childNodes[1].
·
children
Перечисляет только дочерние узлы, соответствующие тегам без текстовых узлов.
Посмотрим следующий пример. Он идентичен предыдущему, за исключением того, что в нем используется children вместо childNodes. Поэтому он будет выводить не все узлы, а только узлы-элементы.
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<div>Пользователи:</div>
<ul>
<li>Маша</li>
<li>Вовочка</li>
</ul>
<!-- комментарий -->
<script>
var children = document.body.children;
for(var i=0; i<children.length; i++)
{
alert(children[i]); //DIV, UL, SCRIPT
}
</script>
</body>
</html>
·
Ссылки
вверх и вниз
Для комфортного перемещения по узлам существуют дополнительные свойства, указывающие вверх, вниз, на соседей и т.п.
firstChild и lastChild
Свойства firstChild и lastChild обеспечивают быстрый доступ к первому и последнему потомку.
Например, для документа:
<html><body><div>...</div><ul>...</ul><div>...</div></body></html>

Данный рисунок будет верен, только если все теги будут записаны в строку без символов перевода строк. Если разбить текст на строки, то первым и последним узлами будут не элементы, а пустые текстовые узлы.
Вот в этом документе firstChild и lastChild будут указывать уже не на узлы-элементы, а на текстовые, пробельные узлы:
<html>
<body>
<div>...</div>
<ul>...</ul>
<div>...</div>
</body>
</html>
В любом случае firstChild и lastChild — это более быстрый и короткий способ обратиться к первому и последнему элементам childNodes. Верны равенства:
document.body.firstChild == body.childNodes[0]
document.body.lastChild == body.childNodes[body.childNodes.length-1]
Данные свойства могут вести отчет от любого узла дерева.
Например, пусть имеется документ вида:
<html>
<body>
<div>...</div>
<ul><li>...</li><li>...</li></ul>
<div>...</div>
</body>
</html>
Тогда запись вида:
document.body.children[1].firstChild
указывает на первый тег li внутри тега UL, который является вторым элементов в массиве children тега body.
Если список записать с переводом строки:
<html>
<body>
<div>...</div>
<ul>
<li>...</li>
<li>...</li>
</ul>
<div>...</div>
</body>
</html>
тогда предыдущая запись будет ссылаться на пустой текстовый блок после тега UL.
·
parentNode, previousSibling и
nextSibling
Свойство parentNode ссылается на родительский узел. Свойства previousSibling и nextSibling дают доступ к левому и правому соседу. Ниже изображены ссылки между BODY и его потомками документа:
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<div>Начало</div>
<ul>
<li>Содержание</li>
</ul>
<div>Конец</div>
</body>
</html>

3. Поиск элементов в DOM
Прямая навигация от родителя к потомку удобна, если элементы рядом. Если положение элемента не известно или элементы разбросаны по дереву, то получить доступ к ним можно с помощью специальных методов поиска. Данные методы могут вызываться для любого узла. В этом случае поиск выполняется, начиная с текущего узла и вниз по всем потомкам.
·
getElementById
У каждого DOM-элемента может быть атрибут id. Его значение должно быть уникальным для документа.
Вызов document.getElementById(id) возвращает элемент с данным id. Если не найден, то метод вернет null.
Например:
<body>
<div id="info">Информация</div>
<script>
var div = document.getElementById('info'); //получить ссылку на div#info
div.style.backgroundColor='yellow'; //изменить цвет фона у элемента
</script>
</body>
Важно, что в документе может быть только один элемент с данным id. Конечно, можно нарушить это правило и создать много элементов с одинаковым идентификатором, но в таком случае поведение метода getElementById будет непредсказуемым.
·
getElementsByTagName
Метод elem.getElementsByTagName(tag) ищет все элементы с заданным тегом tag внутри элемента elem и возвращает их в виде массива. Регистр тега не имеет значения.
Можно искать и в элементе и в документе
//получить все div-элементы
var
elements = document.getElementsByTagName('div');
Найдём все элементы input внутри таблицы:
<table
id="age-table">
<tr>
<td>Ваш возраст:</td>
<td>
<label>
<input type="radio" name="age" value="young"
checked> младше 18
</label>
<label>
<input type="radio" name="age"
value="mature"> от 18 до 50
</label>
<label>
<input type="radio" name="age"
value="senior"> старше 60
</label>
</td>
</tr>
</table>
<script>
var tableElem = document.getElementById('age-table'); //ссылка на таблицу по id
var elements = tableElem.getElementsByTagName('input'); //массив элементов input из таблицы
//цикл по всем элементам массива
for (var i=0; i<elements.length; i++)
{
//выводим значения каждого элемента в массиве
var input = elements[i];
alert(input.value + ':' + input.checked);
}
</script>
Можно получить все элементы, передав звездочку '*' вместо тега:
//получить все элементы документа
var allElems = document.getElementsByTagName('*');
Если хочется получить только один элемент - можно указать индекс сразу же:
//получить ссылку на первый элемент input
var
element = document.getElementsByTagName('input')[0];
//получить ссылку на третий элемент input
var
element = document.getElementsByTagName('input')[2];
·
getElementsByName
Метод getElementsByName(name) позволяет получить все элементы с данным атрибутом name.
Например:
//все элементы с именем age
var
elems = document.getElementsByName('age');
//получить массив элементов со свойством name='test'
<input
name="test">
<div
name="test"></div>
<script>
//получаем массив
var elems = document.getElementsByName('test');
//выводим размер полученного массива
alert(elems.length); //будет выведено значение 2
</script>
·
getElementsByClassName
Метод getElementsByClassName(className) возвращает коллекцию элементов с классом className. Находит элемент и в том случае, если у него несколько классов, а искомый - один из них.
Например:
<div class="article">Статья</div>
<div
class="long article">Длинная статья</div>
<script>
//получить массив элементов с class='article'
var articles =
document.getElementsByClassName('article');
//вывести размер массива
alert( articles.length ); //будет выведено значение 2
</script>
4. Свойства узлов
Каждый узел имеет множество свойств, с помощью которых можно этим узлом управлять или выполнять иные действия.
·
nodeType
Позволяет определить тип узла дерева. Каждый тип задается числовым значением. Всего типов 12, то реально используется 3:
1 - элемент-тег;
3 - текстовый узел;
8 - комментарий.
Например, выведем все узлы-потомки document.body, являющиеся элементами-тегами:
<body>
<div>Читатели:</div>
<ul>
<li>Вася</li>
<li>Петя</li>
</ul>
<!-- комментарий -->
<script>
//получаем массив всех потомков тега body
var childNodes = document.body.childNodes;
//в цикле проходим по всем элементам массива
for (var i=0; i<childNodes.length; i++)
{
//если элемент массива - это тег, то выводим его
if (childNodes[i].nodeType == 1)
alert(childNodes[i]);
}
</script>
</body>
·
nodeName и tagName
Существует целых два свойства: nodeName и tagName, которые содержат название (тег) элемента узла.
Название HTML-тега всегда находится в верхнем регистре.
Например,
alert( document.body.nodeName ); //выведет BODY
alert(
document.body.tagName ); //выведет BODY
Указанные свойства идентичны, но есть отличия. nodeName имеется у всех узлов, включая родительский узел document и комментарии. tagName имеется только у элементов с nodeType=1.
Чаще рекомендуется использовать именно tagName.
·
innerHTML
Свойство innerHTML позволяет получить HTML-содержимое узла в виде строки. В innerHTML можно и читать и писать.
Пример выведет на экран все содержимое document.body, а затем заменит его на другое:
<body>
<p>Параграф</p>
<div>Div</div>
<script>
alert(document.body.innerHTML ); //выводит текущее содержимое
document.body.innerHTML = 'Новый BODY!'; //заменяем содержимое
</script>
</body>
innerHTML — очень полезное свойство и одно из самых часто используемых.
·
outerHTML
Свойство outerHTML содержит HTML узла целиком, включая теги, в которые узел помещен
Пример чтения outerHTML:
<div id="hi">Привет <b>Мир</b></div>
<script>
//получить ссылку на блок div#hi
var div =
document.getElementById('hi');
//вывести содержимое элемента
alert(div.innerHTML); //будет выведено Привет <b>Мир</b>
//вывести полный текст элемента
alert(div.outerHTML); //будет выведено <div>Привет <b>Мир</b></div>
</script>
·
стандартные свойства
Каждый тег имеет атрибуты (например: width, align, src и т.п.). Набор этих атрибутов у каждого тега может быть разным. Но каждый узел DOM-дерева имеет свойства с такими же именами, что и атрибуты. Причем записываются свойства строчными буквами.
//получить ссылку на первое изображение документа
vap
pict=document.getElementsByTagName('img')[0];
//поменять картинку у элемента
pict.src='new.jpg';//атрибут src
//поменять ширину картинки
pict.width='300px';//атрибут width
//получить массив всех абзацев
var p=document.getElementsByTagName('p');
//все четные абзацы выровнять по центру
for
(var i=0; i<p.length; i++)
if (i%2!=0)
p[i].align='center';//атрибут align
//получить ссылку на последнюю гиперссылку в документе
var
a=document.getElementsByTagName('a')[a.length-1];
//поменяем у гиперссылки адрес
a.href='http://newpage.com';
Для работы с атрибутами каждый узел имеет ряд методов:
elem.hasAttribute('атрибут') - проверяет наличие атрибута. Возвращает true/false
elem.getAttribute('атрибут') - получает значение атрибута
elem.setAttribute('атрибут', 'значение') - устанавливает атрибут. Аналогично присваиванию значения одноименному свойству.
elem.removeAttribute('атрибут') - удаляет атрибут
//определить сколько картинок имеет свойство alt='Рисунок'
//получаем массив картинок
var pict=document.getElementsByTagName('img');
//сначала количество 0
var k=0;
//цикл по картинкам
for
(var i=0;i<pict.length;i++)
//если у картинки атрибут alt='Рисунок'
if
(pict[i].getAttributes('alt')=='Рисунок')
//наращиваем количество на 1
k++;
//выводим результат в сообщении
alert('Всего картинок: '+k);
5. Работа со стилями и классами
Каждому элементу можно задавать стили оформления, изменять или удалять их. Кроме этого доступно изменение отдельных правил стилей. Для работы со стилями каждый элемент имеет ряд свойств:
·
className
Данной свойство является аналогом атрибута class в теге.
//добавим к каждому абзацу class='text'
//получаем массив всех абзацев
var p=document.getelementsByTagName('p');
//в цикле проходим по абзацам
for (var i=0;i<p.length;i++)
//каждому абзацу добавляем class='text'
p[i].className+='text';
·
style
Свойство style дает доступ к стилю элемента. Это свойство можно как читать, так и править.
С помощью него можно изменять большинство CSS-свойств, например element.style.width='100px' работает так, как будто у элемента есть атрибут style="width:100px". Так же, как и в CSS, вам нужно указывать единицы измерения, например px.
Для свойств, названия которых состоят из нескольких слов, используется вотТакаяЗапись:
background-color
=> backgroundColor
z-index
=> zIndex
border-left-width => borderLeftWidth
Исключением является правило float, которое записывается как cssFloat.
Например:
element.style.zIndex = 10000;
При присваивании значения какому-либо правилу стиля происходит автоматическое изменение внешнего вида страницы. При присваивании пустого значения происходит возврат к исходному значению правила стиля.
//в каждом списке ul скрыть первый элемент li
//получим массив списков ul
var ul=document.getElementsByTagName('ul');
//в цикле проходим по всем спискам
for
(var i=0;i<ul.length;i++)
{
//получаем для очередного списка первый элемент li
var
li=ul[i].getElementsByTagName('li')[0];
//скрываем элемент
li.style.display='none';
}
//отобразим обратно все первые элементы li каждого списка
//для этого свойству style.display присваиваем пустое значение
//этим мы вернем свойство в начальное значение
//получим массив списков ul
var ul=document.getElementsByTagName('ul');
//в цикле проходим по всем спискам
for
(var i=0;i<ul.length;i++)
{
//получаем для очередного списка первый элемент li
var
li=ul[i].getElementsByTagName('li')[0];
//отображаем элемент
li.style.display='';
}
·
style.cssText
Свойство style является специальным объектом, ему нельзя присваивать строку. Запись div.style="color:blue" работать не будет.
Свойство style.cssText позволяет поставить стиль целиком в виде строки.
Например:
<div>Button</div>
<script>
var div = document.body.children[0];
div.style.cssText="color:red; background-color:yellow; width:100px;
text-align:center;";
</script>
·
Получение
информации о style
Зачастую перед нами встают задачи, для решения которых нужно узнать размер элемента, отступ и т.п.
Свойство style позволяет читать эту информацию, но лишь ту, которая доступна напрямую из свойства/атрибута "style":
<body
style="color:red">
Красный текст
<script>
alert(document.body.style.color); //будет выведено red
</script>
</body>
Если же стиль получен из CSS, особенно когда задано составное свойство (margin вместо margin-top), то style «не знает» об этом.
Попытка получить marginTop в этом примере, будет неудачной:
<style>
body
{
margin: 10px
}
</style>
<body>
<script>
alert(document.body.style.marginTop); //в большинстве браузеров ничего не выведет
</script>
</body>
·
getComputedStyle и currentStyle
Свойство style дает доступ только к той информации, которая хранится в elem.style.
Оно не скажет ничего об отступе, если он появился в результате наложения CSS или встроенных стилей браузера:
Для того, чтобы получить текущее используемое значение (used value) свойства, используется метод window.getComputedStyle, описанный в стандарте DOM.
Его синтаксис таков:
getComputedStyle(element,'')
Данные метод вернет объект, поля которого будут хранить текущие значения параметров стиля элемента.
Пример. Получим некоторые значения стиля для первого абзаца в документе.
//получим объект со значениями всех параметров стиля первого абзаца
var
pStyle = getComputedStyle(document.getElementsByTagName('p')[0], '');
alert(pStyle.marginTop); //выведет верхний отступ в пикселях
alert(pStyle.color); //выведет цвет шрифта
Обратите внимание, что метод getComputedStyle требует указывать полное свойство! Для правильного получения значения нужно указать точное свойство. Например: paddingLeft, marginTop, borderLeftWidth.
При обращении к сокращенному: padding, margin, border — правильный результат не гарантируется.
6. Добавление и удаление узлов в DOM
Изменение DOM — ключ к созданию «живых» страниц.
·
Создание
элементов: createElement
Для создания элементов используются следующие методы документа:
document.createElement(tag) Создает новый элемент с указанным тегом: var div = document.createElement('div');
document.createTextNode(text) Создает новый текстовый узел с данным текстом: var textElem = document.createTextNode('Тут был я');
Новому элементу тут же можно поставить свойства:
var
newDiv = document.createElement('div');
newDiv.className
= 'myclass';
newDiv.id
= 'myid';
newDiv.innerHTML = 'Привет, мир!';
·
Клонирование
Новый элемент можно склонировать из существующего:
newElem = elem.cloneNode(true) - клонирует элемент elem, вместе с атрибутами, включая вложенные в него.
newElem = elem.cloneNode(false) - клонирует элемент elem, вместе с атрибутами, но без подэлементов.
Пример. Склонируем созданный выше элемент с именем newDiv
var
newDiv2=newDiv.cloneNode(true);
·
Добавление
элемента: appendChild, insertBefore
Чтобы DOM-узел был показан на странице, его необходимо вставить в документ.
Для этого у любого элемента есть методы:
parentElem.appendChild(elem) - добавляет elem в список дочерних элементов parentElem. Новый узел добавляется в конец списка. Существующий узел перемещается в конец списка. Следующий пример добавляет новый элемент в уже существующий div:
<div>
...
</div>
<script>
//получаем ссылку на первый элемент страницы (наш div)
var
parentElem = document.body.children[0];
//создаем новый элемент div
var newDiv = document.createElement('div');
//для нового элемента задаем содержимое
newDiv.innerHTML = 'Привет, мир!';
//в конец имеющегося элемента div добавляем новый элемент
parentElem.appendChild(newDiv);
</script>
parentElem.insertBefore(elem, nextSibling) - вставляет elem в список дочерних parentElem, перед элементом nextSibling. Новый элемент вставляется перед элементом nextSibling. Существующий элемент перемещается в позицию перед элементом nextSibling. Сделать новый div первым дочерним можно так:
<div>
...
</div>
<script>
//получаем ссылку на первый элемнет страницы (наш div)
var
parentElem = document.body.children[0];
//создаем новый элемент div
var newDiv = document.createElement('div');
//для нового элемента задаем содержимое
newDiv.innerHTML = 'Привет, мир!';
//у имеющегося элемента div добавляем новый элемент
//перед его первым дочерним элементом
parentElem.insertBefore(newDiv, parentElem.firstChild);
</script>
·
Удаление
узлов: removeChild
Для удаления узла есть два метода:
parentElem.removeChild(elem) - удаляет elem из
списка детей parentElem.
parentElem.replaceChild(elem,
currentElem) - среди детей parentElem заменяет currentElem на elem, удаляя elem со старого места
Если вы хотите переместить элемент на новое место — не нужно его удалять со старого.
Все методы вставки автоматически удаляют вставляемый элемент со старого места.
Например, поменяем элементы местами:
<div>Первый</div>
<div>Второй</div>
<script>
//получаем ссылку на первый div
var first = document.body.children[0];
//получаем ссылку на второй div
var last = document.body.children[1];
//вставляем второй div перед первым (меняем их местами)
//нет необходимости в предварительном document.body.removeChild(last)
document.body.insertBefore(last, first);
</script>