Уровни Объектной Модели Документа(DOM) и их поддержка в браузерах
Краткая история вопроса. Когда возможность работы с Java Script была впервые представлена в браузерах, некоторые виды интерфейсов требовали разрешения от элементов на странице для доступа к ним посредством скриптов. Каждый производитель браузера представлял ее собственную реализацию, но де-факто назревала необходимость как-то стандартизировать основную для всех модель.
К примеру, большинство браузеров используют массив объектов Image для представления всех элементов <img /> на странице. Это дает возможность доступа к ним и управления ими с помощью Java Script. Простейшую подмену картинки можно сделать так:
document.images[3].src = "images/img2.gif"
Это устаревшая и ограниченная модель. Она дает доступ лишь к немногим типам элементов и атрибутов, например к изображениям(<img />), ссылкам(<a>) или формам(<form>).
Когда появлялись новые версии браузеров, в них появлялись новые возможности модели. Но и разница в ее реализации увеличивалась. Основной проблемой совместимости браузеров от различных производителей было добавление новых возможностей, присущих только этому браузеру.
Через некоторое время большинство производителей начали адаптировать свои браузеры (в основном это Internet Explorer, Netscape и Opera) к стандартам DOM, установленным консорциумом W3C.
Порядок обратной совместимости различных уровней стандартов DOM определен. Вы можете найти ссылки на DOM Level 0 ("DOM0"), которые соответствуют модели, используемой первыми браузерами, поддерживающими скрипты (в основном Internet Explorer и Netscape до 4 версии), например http://www.w3.org/TR/WD-DOM-19980318/. В 1998 году появился DOM1 и включил в себя возможности, представленные в 4 версии браузеров.
Браузеры, совместимые со стандартом
Большинство современных браузеров(начиная с 5 версии) поддерживают стандарт DOM2 или хотя бы часть его. Они также поддерживают более ранние уровни DOM, а также собственные ранее разработанные расширения для обеспечения работоспособности старых web-страниц.
Эта статья сосредоточена на стандарте DOM2. На момент написания статьи именно этот стандарт применяется к документам XML в целом и к документам HTML в частности.
Можно надеяться, что более новые версии браузеров будут полностью следовать стандартам. Сейчас же существует огромное количество трудностей, с которыми приходится сталкиваться при написании web-документов, полностью совместимых как с современными, так и более ранними браузерами.
Один из примеров — Netscape 6.0, который перестал поддерживать некоторые возможности Netscape 4 (например тэг <layer> и соответствующий ему объект Layer). Они не работают в версии 6, да и вообще никогда не соответствовали стандартам.
Еще пример — существующая и работающая в Internet Explorer конструкция document.all это его собственная возможность и никогда не соответствовала стандартам. Пока она может поддерживаться многими версиями IE, но как правило не работает в других браузерах.
Вы должны иметь в виду, что DOM кодирование тесно связано со стандартами HTML и CSS, так как DOM представляет элементы и атрибуты в соответствии с этими стандартами.
Дерево документа (Document Tree)
Когда браузер загружает страницу, он создает иерархическое отображение ее содержимого похожее на ее HTML структуру. Это результат древообразной организации узлов, каждый из которых содержит элемент, атрибут, текстовый или любой другой объект.
Узлы (Nodes)
Каждый их этих различных типов объектов имеет свои уникальные методы и свойства, а также обеспечивает взаимосвязь узлов (Nodes). Это общие установки методов и свойств, связанные с древообразной структурой документа. Чтобы лучше понять, как все это происходит, рассмотрим простое дерево узлов.
Каждый узел (Node) содержит свойства, отражающие эту структуру и позволяющие перемещаться по всем узлам дерева документа. Ниже показаны примеры взаимоотношений этих узлов:
NodeA.firstChild = NodeA1 NodeA.lastChild = NodeA3 NodeA.childNodes.length = 3 NodeA.childNodes[0] = NodeA1 NodeA.childNodes[1] = NodeA2 NodeA.childNodes[2] = NodeA3 NodeA1.parentNode = NodeA NodeA1.nextSibling = NodeA2 NodeA3.prevSibling = NodeA2 NodeA3.nextSibling = null NodeA.lastChild.firstChild = NodeA3a NodeA3b.parentNode.parentNode = NodeA
Интерфейс узлов содержит методы для динамического добавления, обновления или удаления узлов, например:
- insertBefore()
- replaceChild()
- removeChild()
- appendChild()
- cloneNode()
Это будет раскрыто чуть позже, а пока рассмотрим, как дерево документа отражает контент web-страницы.
Корень Документа (Document Root)
Объект document является основанием дерева документа. Он обслуживается тем же интерфейсом, что и узел (Node). У него есть дочерние узлы (child nodes), но отсутствуют родительский узел (parent node) и узлы одного с ним уровня, так как он начальный узел. В добавление к существующему Node-интерфейсу, он содержит Document-интерфейс.
Этот интерфейс содержит методы доступа к другим узлам и создания новых узлов в дереве документа. Вот некоторые из них:
- getElementById()
- getElementsByTagName()
- createElement()
- createAttribute()
- createTextNode()
Эти методы не такие, как у других узлов. Они могут быть только у объекта document. Все указанные выше методы (кроме getElementsByTagName()) могут использоваться только объектом document, т.е. их синтаксис должен быть: document.methodName().
Объект document может содержать и некоторые другие свойства устанавливаемые более ранними версиями DOM. Например многие браузеры все еще поддерживают массивы document.images и document.links или свойства document.bgColor и document.fgColor, соответствовавшие атрибутам bgcolor и text тэга <body>.
Эти свойства предназначены для обеспечения обратной совместимости, чтобы web-страницы, созданные для старых браузеров, могли правильно отображаться в новых браузерах. Они все еще используются в скриптах, но их не стоит применять, т.к. в будущем они могут больше не поддерживаться.
Перемещение по Дереву Документа
Как уже было сказано, дерево документа отражает структуру HTML кода страницы. Каждый тэг или пара тэгов изображены как узел элемента, с узлами представляющими атрибуты или символьные данные (например, текст).
Формально, объект document имеет только один дочерний элемент (child element) устанавливаемый как document.documentElement. Для web-страниц он установлен тэгом <html>, который является корневым элементом дерева документа. У него есть дочерние элементы, установленные тэгами <head> и <body>, у которых в свою очередь, есть другие дочерние элементы.
Учитывая это и используя методы интерфейса Node можно перемещаться по дереву документа и обращаться к любому узлу этого дерева. Рассмотрим пример:
<html>
<head>
<title></title>
</head>
<body><p>Здесь какой-то текст.</p></body>
</html>
и этот код:
alert(document.documentElement.lastChild.firstChild.tagName);
который покажет "p", название тэга,представленного этим узлом. В этом коде:
document.documentElement - возвращает тэг <html>. .lastChild - возвращает последний дочерний элемент для <html>, т.е. тэг <body>. .firstChild - возвращает первый дочерний элемент внутри <body>. .tagName - возвращает название тэга, в данном случае "p".
При этом всплывает очевидная проблема доступа к узлам. Достаточно изменить код документа, например, если добавить другие элементы, текст или изображения, то будет изменена структура дерева документа. И путь к этому узлу также может измениться.
Менее очевидна совместимость с доступом к узлам в некоторых браузерах. В приведенном выше примере между тэгами <body> и <p> нет ничего, даже пробелов, а теперь мы добавим между ними пару переводов строки до и после <p>:
<html>
<head>
<title></title>
</head>
<body>
<p>Здесь какой-то текст.</p>
</body>
</html>
В этом случае Netscape обнаружит в этих местах новые узлы, тогда как IE не станет этого делать. В Netscape вышенаписанный код JavaScript покажет "undefined", т.к. теперь в этом месте появился текстовый узел в виде пробела. Так как это не узел элемента, он не содержит теперь имени тэга. С другой стороны, IE не добавит узлов для таких пробелов, как здесь и по прежнему будет указывать на тэг "p".
Прямой доступ к элементам
Для этого существует удобный метод document.getElementById(). Для работы с ним необходимо добавить атрибут id тэгу "p" (да и любому другому тоже можно), тогда появляется возможность обращаться к элементу напрямую:
<p id="myP">Здесь какой-то текст.</p>
...
alert(document.getElementById("myP").tagName);
В этом случае можно не опасаться некоторой несовместимости браузеров, а также того, где в дереве документа размещен узел для тэга "p". Важно лишь помнить о том, что атрибут id должен быть уникальным в пределах этого документа.
Более сложный доступ к узлу элемента представлен методом document.getElementsByTagName(). Он возвращает массив узлов всех элементов документа, содержащих указанный HTML тэг. Для примера, сделать все ссылки на странице красными можно таким образом:
var nodeList = document.getElementsByTagName("a");
for (var i = 0; i < nodeList.length; i++)
nodeList[i].style.color = "#f00";
Типы узлов (Node Types)
Перед тем, как продолжить, более детально рассмотрим типы узлов. Как упоминалось ранее, в Объектной Модели Документа определены несколько типов узлов, но в разметке web-страницы в основном используются element, text и attribute.
Узел Element, как вы уже могли видеть, соответствует отдельному тэгу или паре тэгов в HTML коде. У него могут быть дочерние узлы (child node), которые могут быть элементами или текстовыми узлами.
Узел Text представляет обычный текст, или набор символов. У него предполагается наличие родительского узла (parent node), могут быть соседние узлы от того же родительского (sibling), но не может быть дочерних узлов (child node).
Узел Attribute отличается от вышеописанных. Он не участвует в построении дерева документа, у него нет ни родительских, ни дочерних, ни соседних узлов. Вместо этого он используется для доступа к атрибутам узла элемента. Он представляет атрибуты, определенные у HTML тэга элемента, например, атрибут href тэга <a> или src тэга <img />.
Обратите внимание, что значение атрибута всегда будет представлено текстовой строкой.
Атрибут, как узел Attribute
Существует несколько способов обращения к атрибутам элемента. Причина тому — стандарт DOM2, который был разработан для различных типов документов (т.е. XML), не только HTML. Таким образом он формально определяет тип узла для атрибута.
Но для всех документов он обеспечивает несколько схожих методов для доступа к атрибутам, добавления новых или изменения существующих. Как это происходит, смотрите дальше.
Метод document.createAttribute() дает возможность создать новый узел атрибута, которому можно присвоить значение, и применить его к узлу элемента:
var attr = document.createAttribute("myAttribute");
attr.value = "myValue";
var el = document.getElementById("myParagraph");
el.setAttributeNode(attr);
Однако, существует более простой способ доступа к атрибутам элемента, используя getAttribute() и setAttribute() методы элемента:
var el = document.getElementById("myParagraph");
el.setAttribute("myAttribute", "myValue");
Атрибуты элемента могут быть представлены и как свойства узла элемента. Другими словами, можно сделать и так:
var el = document.getElementById("myParagraph");
el.myAttribute = "myValue";
Следует обратить внимание, что Explorer 5.5 и более ранние версии не поддерживают тип узла Attribute и такой метод, как document.createAttribute() там не работает, в то время как element.getAttribute() уже как-то поддерживается. В этих браузерах получить доступ к атрибутам можно так: element.attributeName. А начиная с версии IE 6.0 уже поддерживаются узлы атрибутов, их методы и свойства.
Вы можете определять ваши атрибуты в HTML тэгах, например:
<p id="myParagraph" myAttribute="myValue">Здесь какой-то текст.</p>
...
alert(document.getElementById("myParagraph").getAttribute("myAttribute"));
увидите "myValue", а также обратите внимание: чтобы получить значение атрибута, следует использовать именно element.getAttribute(attributeName), а не element.attributeName, так как в последнем случае не все браузеры смогут определить ваши собственные атрибуты.
Атрибуты можно удалять из узла элемента, используя метод removeAttribute() или removeAttributeNode(), а также заменяя element.attributeName пустой строкой ("").
Изменение атрибутов — один из вариантов создания динамических эффектов. Например можно легко изменить выравнивание текста по правому или левому краю:
<p id="sample1" align="left">Здесь какой-то текст.</p>
<p><a href="" onclick="document.getElementById('sample1').setAttribute('align', 'left'); return false;">Нале-во!</a>
::
<a href="" onclick="document.getElementById('sample1').setAttribute('align', 'right'); return false;">Напра-во!</a></p>
Атрибуты стилей
Большинство атрибутов для HTML тэгов примитивны, они могут определять значение свойства только конкретному тэгу. Применение стилей более интересно. Как известно, CSS может быть использовано для применения параметров стилей к одному тэгу, всем однотипным тэгам или назначено с помощью классов (class). Более того, некоторые элементы наследуют стили вышестоящих элементов.
Вы можете добавлять стили элементам. Для примера, можно изменить атрибут style или class HTML тэгу. Но этот метод добавит элементу все параметры стилей. Часто приходится только какой-нибудь один или несколько параметр стиля убрать или изменить, не трогая остальные.
Атрибут style узла элемента определен как объект со свойствами для всех возможных параметров стилей. Можно получить доступ и изменить каждый отдельный параметр стиля как угодно.
Но в этом случае выравнивание текста определено параметрами стилей вместо атрибута align. Вот фрагмент кода:
<p id="sample2" style="text-align:left">Здесь какой-то текст.</p>
<p><a href="" onclick="document.getElementById('sample2').style.textAlign = 'left'; return false;">Нале-во!</a>
::
<a href="" onclick="document.getElementById('sample2').style.textAlign = 'left'; return false;">Напра-во!</a></p>
Здесь следует обратить внимание на то, что названия свойств в CSS пишутся через дефис "-" (text-align), тогда как название свойства style здесь будет написано textAlign, т.е. убираем дефис и следующую за ним букву делаем прописной.
А кроме этого, если параметры стилей изначально не были присвоены элементу, им все-равно можно устанавливать значения используя DOM. Например, в коде выше атрибут style= тэга "p" можно было вообще не указывать.