Введение. DOM в примерах.

Основным инструментом работы и динамических изменений на странице является DOM (Document Object Model) - объектная модель, используемая для XML/HTML-документов.

Согласно DOM-модели, документ является иерархией.
Каждый HTML-тег образует отдельный элемент-узел, каждый фрагмент текста - текстовый элемент, и т.п.

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

Простейший DOM

Построим, для начала, дерево DOM для следующего документа.

<html>
  <head>
    <title>Заголовок</title>
  </head>
  <body>
     Прекрасный документ
   </body>
</html>

Самый внешний тег - <html>, поэтому дерево начинает расти от него.

Внутри <html> находятся два узла: <head> и <body> - они становятся дочерними узлами для <html>.

простой DOM

Теги образуют узлы-элементы (element node). Текст представлен текстовыми узлами (text node). И то и другое - равноправные узлы дерева DOM.

Пример посложнее

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

<html>
    <head>
        <title>
            О лосях
        </title>
    </head>
    <body>
        Правда о лосях.
        <ol>
            <li>
                Лось - животное хитрое
            </li>
            <li>
                .. И коварное
            </li>
        </ol>
    </body>
</html>

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

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

Дерево образовано за счет синих элементов-узлов - тегов HTML.

А вот так выглядит дерево, если изобразить его прямо на HTML-страничке:

Кстати, дерево на этом рисунке не учитывает текст, состоящий из одних пробельных символов. Например, такой текстовый узел должен идти сразу после <ol>. DOM, не содержащий таких "пустых" узлов, называют "нормализованным".

Пример с атрибутами и DOCTYPE

Рассмотрим чуть более сложный документ.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<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 &copy;</div>
    </body>
</html>

Верхний тег - html, у него дети head и body, и так далее. Получается дерево тегов:

Атрибуты

В этом примере у узлов есть атрибуты: style, class, id. Вообще говоря, атрибуты тоже считаются узлами в DOM-модели, родителем которых является элемент DOM, у которого они указаны.

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

DOCTYPE

Вообще-то это секрет, но DOCTYPE тоже является DOM-узлом, и находится в дереве DOM слева от HTML (на рисунке этот факт скрыт).

P.S. Насчет секрета - конечно, шутка, но об этом и правда далеко не все знают. Сложно придумать, где такое знание может пригодиться...

Нормализация в различных браузерах

При разборе HTML Internet Explorer сразу создает нормализованный DOM, в котором не создаются узлы из пустого текста.

Firefox - другого мнения, он создает DOM-элемент из каждого текстового фрагмента.
Поэтому в Firefox дерево этого документа выглядит так:

DOM дерево

На рисунке для краткости текстовые узлы обозначены просто решеткой. У body вместо 3 появилось 7 детей.

Opera тоже имеет чем похвастаться. Она может добавить лишний пустой элемент "просто от себя".

Чтобы это увидеть - откройте документ по этой ссылке. Он выдает число дочерних узлов document.body, включая текстовые узлы.

У меня получается 3 для IE, 7 для Firefox и 8 (!?) для Opera.

На практике эта несовместимость не создает больших проблем, но нужно о ней помнить. Например, разница может проявить себя в случае перебора узлов дерева.

Возможности, которые дает DOM

Зачем, кроме красивых рисунков, нужна иерархическая модель DOM?

Очень просто:

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

Для манипуляций с DOM используется объект document.
Используя document, можно получать нужный элемент дерева и менять его содержание.

Например, этот код получает первый элемент с тэгом ol, последовательно удаляет два элемента списка и затем добавляет их в обратном порядке:

var ol = document.getElementsByTagName('ol')[0]
var hiter = ol.removeChild(ol.firstChild)
var kovaren = ol.removeChild(ol.firstChild)
ol.appendChild(kovaren)
ol.appendChild(hiter)

Для примера работы такого скрипта - кликните на тексте на лосиной cтраничке

document.write

В старых руководствах и скриптах можно встретить модификацию HTML-кода страницы напрямую вызовом document.write.

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

Избегайте document.write.. Кроме случаев, когда вы действительно знаете, что делаете (а зачем тогда читаете самоучитель - вы и так гуру)

Разберем подробнее способы доступа и свойства элементов DOM.

Доступ к элементам

Любой доступ и изменения DOM берут свое начало от объекта document.

Начнем с вершины дерева.

document.documentElement

Самый верхний тег. В случае корректной HTML-страницы, это будет <html>.

document.body

Тег <body>, если есть в документе (обязан быть).

Это свойство работает немного по-другому, если установлен DOCTYPE Strict. Обычно проще поставить loose DOCTYPE.

Следующий пример при нажатии на кнопку выдаст текстовое представление объектов document.documentElement и document.body. Сама строка зависит от браузера, хотя объекты везде одни и те же.

<html>
  <body>
     <script>
       function go() {
         alert(document.documentElement)
         alert(document.body)
      }
     </script>
     <input type="button" onclick="go()" value="Go"/>
  </body>
</html>

Типы DOM-элементов

У каждого элемента в DOM-модели есть тип. Его номер хранится в атрибуте elem.nodeType

Всего в DOM различают 12 типов элементов.

Обычно используется только один: Node.ELEMENT_NODE, номер которого равен 1. Элементам этого типа соответствуют HTML-теги.

Иногда полезен еще тип Node.TEXT_NODE, который равен 3. Это текстовые элементы.

Остальные типы в javascript программировании не используются.

Следующий пример при нажатии на кнопку выведет типы document.documentElement, а затем тип последнего потомка узла document.body. Им является текстовый узел.

<html>
  <body>
     <script>
       function go() {
         alert(document.documentElement.nodeType)
         alert(document.body.lastChild.nodeType)         
      }
     </script>
     <input type="button" onclick="go()" value="Go"/>
     Текст
  </body>
</html>

Пример

Например, вот так выглядел бы в браузере документ из примера выше, если каждый видимый элемент обвести рамкой с цифрой nodeType в правом верхнем углу.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<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 &copy;</div>
    </body>
</html>

DOM

Здесь показаны только элементы внутри body, т.к только они отображаются на странице. Для элементов типа 1 (теги) в скобочках указан соответствующий тег, для текстовых элементов (тип 3) - стоит просто цифра.

Дочерние элементы

С вершины дерева можно пойти дальше вниз. Для этого каждый DOM-узел содержит массив всех детей, отдельно - ссылки на первого и последнего ребенка и еще ряд полезных свойств.

  1. Все дочерние элементы, включая текстовые находятся в массиве childNodes.

    В следующем примере цикл перебирает всех детей document.body.

    for(var i=0; i<document.body.childNodes.length; i++) {
        var child = document.body.childNodes[i]
        alert(child.tagName) 
    }
    
  2. Свойства firstChild и lastChild показывают на первый и последний дочерние элементы и равны null, если детей нет.
  3. Свойство parentNode указывает на родителя. Например, для <body> таким элементом является <html>:

    alert(document.body.parentNode == document.documentElement) // true
    
  4. Свойства previousSibling и nextSibling указывают на левого и правого братьев узла.

В общем. если взять отдельно <body> с детьми из нормализованного DOM - такая картинка получается ОТ <body>:

И такая - для ссылок наверх и между узлами:

  • Синяя линия - массив childNodes
  • Зеленые линии - свойства firstChild, lastChild.
  • Красная линия - свойство parentNode
  • Бордовая и лавандовая линии внизу - previousSibling, nextSibling

Этих свойств вполне хватает для удобного обращения к соседям.

Свойства элементов

У DOM-элементов есть масса свойств. Обычно используется максимум треть из них. Некоторые из них можно читать и устанавливать, другие - только читать.

Есть еще и третий вариант, встречающийся в IE - когда устанавливать свойство можно только во время создания элемента.

Рассмотрим здесь еще некоторые (не все) свойства элементов, полезные при работе с DOM.

tagName

Атрибут есть у элементов-тегов и содержит имя тега в верхнем регистре, только для чтения.

Например,

alert(document.body.tagName) // => BODY

style

Это свойство управляет стилем. Оно аналогично установке стиля в CSS.

Например, можно установить element.style.width:

Исходный код этой кнопки:

<input
  type="button" 
  style="width: 300px" 
  onclick="this.style.width = parseInt(this.style.width)-10+'px'" 
  value="Укоротить на 10px"
/>
Обработчик события onclick обращается в этом примере к свойству this.style.width, т.к значением this в обработчике события является текущий элемент (т.е сама кнопка). Подробнее об этом - во введении в события.

Есть общее правило замены - если CSS-атрибут имеет дефисы, то для установки style нужно заменить их на верхний регистр букв.

Например, для установки свойства z-index в 1000, нужно поставить:

element.style.zIndex = 1000

innerHTML

Когда-то это свойство поддерживалось только в IE. Теперь его поддерживают все современные браузеры.

Оно содержит весь HTML-код внутри узла, и его можно менять.

Свойство innerHTML применяется, в основном, для динамического изменения содержания страницы, например:

document.getElementById('footer').innerHTML = '<h1>Bye!</h1> <p>See ya</p>'

Пожалуй, innerHTML - одно из наиболее часто используемых свойств DOM-элемента.

className

Это свойство задает класс элемента. Оно полностью аналогично html-атрибуту "class".

elem.className = 'newclass'

onclick, onkeypress, onfocus...

.. И другие свойства, начинающиеся на "on...", хранят функции-обработчики соответствующих событий. Например, можно присвоить обработчик события onclick.

Подробнее об этих свойствах и обработчиках событий - см. введение в события.