Лекция

Тема: «Работа с DOM-деревом документа»

 

План

1. Понятие DOM дерева

2. Переход по элементам DOM

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

4. Свойства узлов

5. Работа со стилями и классами

6. Добавление и удаление узлов в DOM

 

1. Понятие DOM дерева

Каждый браузер дает доступ к иерархии объектов, которые мы можем использовать для разработки.
 

На рисунке схематически отображена структура основных браузерных объектов.

 

lekc03_1

 

На вершине стоит 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>.

 

lekc03_2

 

Рассмотрим теперь более жизненную страничку:

 

<html>

<head>

    <title> О лосях </title>

</head>

<body>

    Правда о лосях.

    <ol>

        <li> Лось - животное хитрое </li>

        <li> .. И коварное </li>

    </ol>

</body>

</html>

 

Корневым элементом иерархии является html. У него есть два потомка. Первый - head, второй - body. И так далее, каждый вложенный тег является потомком тега выше:

 

lekc03_3

 

На этом рисунке синим цветом обозначены элементы-узлы, черным - текстовые элементы.

Дерево образовано за счет синих элементов-узлов - тегов 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, и так далее. Получается дерево тегов:

 

lekc03_4

 

Обратите внимание, что все текстовые блоки на рисунке обозначены символом "#". В конце каждого тега был выполнен переход клавишей 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.

 

lekc03_5

 

Нельзя получить доступ к элементу, которого еще не существует в момент выполнения скрипта.

В следующем примере, первый 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>

 

lekc03_6

 

Данный рисунок будет верен, только если все теги будут записаны в строку без символов перевода строк. Если разбить текст на строки, то первым и последним узлами будут не элементы, а пустые текстовые узлы.

Вот в этом документе 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>

 

lekc03_7

 

 

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>