В этой статье обсуждается объектная модель документа, известная под именем
DOM (аббревиатура Document Object Model). Ключевым понятием DOM является объект
нового типа -- узел. Подробно обсуждаются три основных типа узлов, которым
в документе соответствуют HTML элементы, их атрибуты и текстовое содержание.
Описаны все ключевые свойства и методы узлов согласно документации W3C. В
настоящий момент основные положения W3C DOM поддерживает Internet
Explorer 5 и находящийся в процессе создания броузер с NGLayout-процессором
от Mozilla (идущий на смену Netscape Navigator 4). Описан также ряд
методов, не входящих в W3C DOM, но поддерживаемых Internet Explorer 5.
Содержание статьи
Структура документа в DOM
DOM представляет документ как иерархию объектов нового типа -- узлов
(node). На вершине иерархии находится узел (объект)document, который
представляет весь документ. В качестве узлов в DOM представлено все содержание
документа: HTML элементы, атрибуты этих элементов и текст HTML
элементов-контейнеров. Возьмем для примера небольшой фрагмент HTML
документа: <HTML>
<HEAD>
<TITLE>
Overview of the DOM
</TITLE>
</HEAD>
<BODY>
<H1>
Иерархия узлов
</H1>
<P>
На вершине иерархии находится узел
<TT>
document
</TT>
, который представляет в DOM сам документ.
</P>
</BODY>
</HTML>
РазделHEADэтого документа содержит элементTITLEс текстом.
РазделBODYсодержит заголовок первого уровня и параграф, в котором одно
слово набрано телетайпным шрифтом. На следующей диаграмме показаны все узлы
этого документа. Узлы типа элемент изображены в виде тэгов этого элемента и
размещены в прямоугольных ячейках. Узлы типа текст (их содержание сокращено для
компактности) размещены в ячейках с закругленными углами. Линии соединяют
родительский узел с расположенными под ним его детками.
Сравнивая эту диаграмму с ее HTML источником, можно легко понять, что
иерархия узлов задается вложенностью тэгов друг в друга и текста внутрь
элементов. Текст элементаPпредставлен двумя текстовыми узлами,
поскольку он разбит на две части элементомTT. Показанную на рисунке
иерархию узлов принято называть деревом документа. Надо отметить, что Internet
Explorer 5 не считает корневой узелdocumentчастью этого
дерева.
Очевидно, что элементы, имеющие открывающий и закрывающий тэги
(элементы-контейнеры), могут иметь деток. Текст, атрибуты и элементы
типаIMG, не имеющие закрывающего тэга, иметь деток не могут. Очевидно
также, что узлы типа атрибут могут быть детками только узлов типа элемент. В
HTML разные элементы, например,PилиIMG, имеют разный набор
допустимых для них атрибутов. В DOM узлы-элементы разного вида также имеют
разный набор допустимых для них узлов-атрибутов. За редким исключением они
соответствуютHTML 4. Списки
рекомендованных атрибутов для различных элементов можно найти в документации
W3C. Впрочем, реальные списки зависят от реализации броузеров.
Узлы разного типа (document, элементы, атрибуты и текст) имеют свой
набор свойств и методов, которые позволяют через сценарии манипулировать ими.
Ключевые свойства и методы узлов описаны в этой статье. Ряд узлов-элементов
(объектов) имеют свои собственные свойства и методы. Так, например, объект
элементаTABLEимеет методcreateTHead(). Полный набор
рекомендованных свойств и методов можно найти в документации W3C. Надо отметить,
что Internet Explorer 5 поддерживает гораздо более широкий набор, чем
содержится в рекомендации W3C.
При обсуждении свойств и методов узлов мы будем использовать следующий
фрагмент документа: <DIV>
<UL ID="components">
<LI>HTML</LI>
<LI>CSS</LI>
<LI>Javascript</LI>
</UL>
</DIV>
В DOM этому фрагменту соответствует ветка дерева, растущая из
узла<DIV>к узлу<UL>. Здесь она разветвляется на
три веточки по числу узлов<LI>(узлы-атрибуты не принято включать
в состав дерева). Каждый из этих узлов имеет по одному побегу, который
заканчивается текстовым узлом.
Навигация по дереву документа
Навигацию по дереву документа можно начинать с любого узла-элемента, для
которого мы знаем идентификатор, присваиваемый ему в качестве значения
атрибутаID. Ссылку на такой узел можно получить с помощью
методаgetElementById()объектаdocument. Параметром метода
является идентификатор. Следующая строка кода присваивает
переменнойoListссылку на наш список:
var oList = document.getElementById("components")
Стартуя с некоторого узла, мы можем бродить по дереву в любом направлении,
используя ряд свойств узлов. Так, узлы-элементы и текстовые узлы имеют
свойствоparentNode, которое возвращает ссылку на родительский узел.
Возьмем для примера узел (объект)oList. Ссылку на родительский
элементDIVэтого узла можно получить следующий образом:
var oParent = oList.parentNode
Узлы-элементы и текстовые узлы, являющиеся детками некоторого узла, входят в
состав коллекцииchildNodesэтого узла. (Узлы-атрибуты составляют
отдельную коллекциюattributes, которая обсуждается в последнем
разделестатьи.) К
каждому из них можно обращаться по индексу массива. Например, строка кода
var oItem1 = oList.childNodes[1]
присваивает переменнойoItem1ссылку на
элемент<LI>CSS</LI>нашего списка. Именно этот элемент
представлен в DOM как узелchildNodes[1]узлаoList. Первый
(childNodes[0]) и последний элементы коллекции имеют специальные
имена:firstChildиlastChild. Эти имена являются свойствами
родительского узла. Каждый из элементов коллекции имеет
свойстваpreviousSiblingиnextSibling. Эти свойства хранят
ссылку на ближайщих братков элемента -- предыдущий и последующий элементы
коллекции (возвращаютnull, когда братков нет). Так,
элементchildNodes[1]является
свойствомnextSiblingэлементаchildNodes[0]и
свойствомpreviousSiblingэлементаchildNodes[2]. Используя эти
свойства, мы можем получить ссылку на узелchildNodes[1]любым из
следующих способов:
oList
.firstChild.nextSiblingoList
.childNodes[2].previousSibling
Ссылка на более удаленные узлы как по горизонтали, так и по вертикали дерева
формируется путем слияния ссылок на ближайших родственников по стандартным
правилам объектно-ориентированного программирования. Так,
oList.childNodes[1].firstChild
является ссылкой на
текст"CSS"элемента<LI>CSS</LI>нашего списка.
На следующей диаграмме приведены имена всех ближайших родственников
некоторого узлаoNode.
Заметим, что все описанные выше свойства узлов
(parentNode,firstChild,lastChild,nextSiblingиpreviousSibling),
необходимые для навигации по дереву документа, являются свойствами только для
чтения. Помимо них, узлы имеют еще ряд свойств, которые мы сейчас опишем.
Свойства-характеристики узлов
Свойство узлаnodeType(только для чтения) возвращает 1, 2 или 3 для
узлов, соответствующих тэгу, атрибуту или тексту, соответственно.
СвойствоnodeName(только для чтения) возвращает имя HTML тэга, которому
соответствует данный узел, например,Pдля параграфа илиULдля
ненумерованного списка. Для узлов-атрибутовnodeNameвозвращает название
атрибута, а для тестовых узлов возвращает#text.
Текстовые узлы имеют еще одно очень важное свойство:nodeValue. Это
свойство для чтения и записи хранит содержание текстового узла. Для элементов
оно возвращаетnull, а для атрибутов -- значение атрибута.
Создание новых узлов
Технику создания новых элементов обсудим на конкретном примере. Мы хотим
добавить к нашему списку элемент
<LI>XML</LI>
Этому HTML элементу в DOM соответствуют два узла:
узел-элемент<LI>и текстовый узел"XML". Следовательно, мы
должны создать два новых узла. Новые узлы создаются с помощью
методовcreateElement()иcreateTextNode()объектаdocument.
МетодcreateElement()
МетодcreateElement()принимает в качестве параметра строку с
названием тэга элемента и создает новый HTML элемент указанного типа. Например,
строка кода
var oItem = document.createElement("LI")
создает новый элемент списка<LI>, у которого нет содержания.
Метод возвращает ссылку на созданный им объект. Созданный выше
элемент<LI>находится в памяти, но не входит в состав текущего
документа. Для того, чтобы он стал частью документа, его надо добавить к
существующим узлам документа с помощью
методовinsertBefore()илиappendChild(), которые подробно
описаныниже.
Хотя название тэга элемента можно набирать как заглавными, так и прописными
буквами, рекомендуется использовать ЗАГЛАВНЫЕ БУКВЫ.
Заметим, что в Internet Explorer 5 можно создавать через сценарий все
элементы, кромеFRAME,IFRAMEиSELECT. Свойства
создаваемых элементов являются свойствами для чтения и записи.
МетодcreateTextNode()
МетодcreateTextNode()используется для создания текстового узла. Он
принимает в качестве параметра строку текста, которая задает значение
свойстваnodeValueтекстового узла. Например, строка кода
var oText = document.createTextNode("XML")
создает новый текстовый узел"XML". Метод возвращает ссылку на
созданный им объект. Созданный текст еще не входит в состав текущего документа.
Для того, чтобы он стал частью документа, его надо присоединить к существующим
узлам документа с помощью
методовappendChild(),replaceNode()илиinsertBefore(),
которые подробно описаныниже.
Если у вас случайно завалялась ссылка на ненужный текстовый узел
(например,oText), то можно не создавать нового узла, а воспользоваться
уже существующим. Для этого надо просто присвоить новый текст в качестве
значения свойстваnodeValueсуществующего узла:
oText.nodeValue= "XML"
Итак, мы создали два новых узла: узел-элемент<LI>и текстовый
узел"XML". Теперь займемся встраиванием этих узлов в документ.
Редактирование дерева документа
Примечание.Методы, помеченные звездочкой*, не
входят в список рекомендуемых W3C
DOM, как редактор, позволяет копировать, вставлять, замещать и удалять как
отдельные узлы, так и целые ветви дерева документа. Мы начнем с присоединения
одного узла к другому.
Напомню, что у нас есть два узла: узел-элемент<LI>и текстовый
узел"XML". Оба узла находятся в памяти и мы хотим встроить их в текущий
документ. Прежде всего, надо задать текст"XML"в качестве содержания
элемента<LI>. Сделать это можно с помощью
методаappendChild().
Вставка: методappendChild()
Этот метод добавляет элемент в конец коллекцииchildNodesузла,
который его активизировал. Сам этот узел становится родительским узлом для узла,
ссылку на который метод принимает в качестве параметра. Например, строка кода
oItem.appendChild(oText)
добавляет узелoTextк узлуoItem. Отметим, что метод
возвращает ссылку на объект, который он добавляет. В нашем случае это
объектoItem.firstChild. Теперь мы имеет в памяти элемент
(веточку дерева из двух узлов)
<LI>XML</LI>
Пора вставлять эту веточку в текущий документ. Если мы хотим вставить ее в
самый конец нашего списка, то надо, как и выше, использовать
методappendChild():
oList.appendChild(oItem)
Поскольку узелoList, к которому мы присоединили узелoItem,
является частью текущего документа, созданный нами элемент списка также
становится частью документа. Теперь наш список выглядит так:
<UL ID="components">
<LI>HTML</LI>
<LI>CSS</LI>
<LI>Javascript</LI>
<LI>XML</LI>
</UL>
Обсудим теперь как можно вставить созданный нами элемент списка не в конец,
а, скажем, после элемента списка<LI>HTML</LI>. Сделать это
можно с помощью методаinsertBefore().
Вставка: методinsertBefore()
В отличие от методаappendChild(),
методinsertBefore()позволяет указать, в какое место
коллекцииchildNodesбудущего родительского узла будет вставлен новый
узел. Как следует из названия, метод требует ссылки на узел, перед которым будет
вставлен его новый браток. Мы создадим эту ссылку в отдельной строке, хотя это и
необязательно. Итак, код
var oBrother = oList.firstChild.nextSiblingoList.insertBefore(oItem, oBrother)
добавляет в
коллекциюchildNodesузлаoListузелoItemсразу после
узлаchildNodes[0]. В качестве первого параметра
методinsertBefore()принимает ссылку на узел, который мы хотим добавит,
а в качестве второго параметра -- ссылку на узел, перед которым будет
вставлен новый браток. Второй параметр метода является необязательным. Если
родительский узел не имеет деток, то задавать его не следует. Если родительский
узел имеет деток, а второй параметр не задан, то добавляемый узел становится
самым последним среди деток родительского объекта.
МетодinsertBefore()возвращает ссылку на вставленный в документ объект.
Теперь наш первоначальный список выглядит так:
<UL ID="components">
<LI>HTML</LI>
<LI>XML</LI>
<LI>CSS</LI>
<LI>Javascript</LI>
</UL>
Продолжим обсуждение методов редактирования дерева документа.
Копирование: методcloneNode()
Если мы хотим скопировать некоторый узел вместе со всеми его атрибутами, то
надо воспользоваться методомcloneNode(). В качестве параметра метод
принимает выражение типа Boolean. Если оно равноfalse, то копируется
только тот узел, который активизирует метод. Если параметр метода
равенtrue, то копируется узел вместе со всеми его потомками. Например,
строка кода
var oClone = oList.cloneNode(true)
копирует в память всю ветвь дерева, начинающуюся на узлеoList, то
есть весь наш список целиком. Метод возвращает ссылку на копию узла. Используя
эту ссылку, мы можем в дальнейшем работать с этой копией, например,
отредактировать ее и вставить в документ.
Замещение: методыreplaceChild()иreplaceNode()
МетодreplaceChild()позволяет у узла, который его активизирует,
заменить одного из его деток на нового. Ссылку на новый и на заменяемый узлы
метод принимает в качестве первого и второго параметров, соответственно. Так,
следующий фрагмент сценария
var oItem=document.createElement("LI")oItem.appendChild(document.createTextNode("JScript"))
oList.replaceChild(oItem, oList.lastChild)
создает сначала элемент списка с текстом "JScript", а затем заменяет им
последний элемент нашего списка. Отметим, что метод возвращает ссылку на
вставленный в документ узел.
Теперь наш список выглядит так: <UL ID="components">
<LI>HTML</LI>
<LI>CSS</LI>
<LI>JScript</LI>
</UL>
Конечно, описанный выше пример надо рассматривать только как иллюстративный,
поскольку тот же результат можно получить гораздо проще: oList.lastChild.firstChild.nodeValue= "JScript"
Если мы хотим заменить некоторый узел в документе
другим узлом, то надо воспользоваться методомreplaceNode(). Подчеркнем,
что этот метод не входит в список рекомендуемых W3C, но поддерживается Internet
Explorer 5. МетодreplaceNode()удаляет из документа узел, который
его активизирует, и вставляет в документ вместо него новый узел, ссылку на
который от принимает в качестве параметра. Заметим, что узел удаляется вместе со
всеми своими атрибутами и потомками. Так, следующий фрагмент сценария var oParagraph = document.createElement("P")
var oText = document.createTextNode("Составные части DHTML")
oParagraph.appendChild(oText)
oList.replaceNode(oParagraph)
создает сначала параграф с текстом "Составные части DHTML", а затем заменяет
наш списокoListна этот параграф. Отметим, что метод возвращает ссылку
на вставленный в документ узел.
Удаление: методыremoveChild()иremoveNode()
МетодremoveChild()позволяет у узла, который его активизирует,
удалить одного из его деток. Ссылку на удаляемый узел метод принимает в качестве
параметра. Например, строка кода var oRemovedItem = oList.removeChild(oList.lastChild)
удаляет из нашего списка последний элемент. Метод возвращает ссылку на
удаляемый им узел. Поскольку удаленный из документа узел остается в памяти, мы
можем в дальнейшем работать с ним, используя эту ссылку. Например, пристроить в
какой-нибудь другой список.
Если мы хотим удалить некоторый узел из документа, то
надо воспользоваться методомremoveNode(). Подчеркнем, что этот метод не
входит в список рекомендуемых W3C, но поддерживается Internet Explorer 5. В
качестве параметра метод принимает выражение типа Boolean. Если оно
равноfalse, то удаляется только тот узел, который активизировал метод.
При этом, идущая от него ветвь дерева присоединяется к его родительскому узлу.
Если параметр метода равенtrue, то узел удаляется вместе со всеми
своими потомками. Например, строка кода var oRemovedList = oList.removeNode(true)
удаляет из документа весь наш список целиком.
МетодremoveNode()возвращает ссылку на удаляемый им узел. Поскольку
удаленный из документа узел остается в памяти, мы можем в дальнейшем работать с
ним, используя эту ссылку.
Использоватьfalseв качестве параметра надо осмысленно. Так, если мы
применим методremoveNode()с параметромfalseк нашему списку, то
его детки должны будут перейти к элементуDIV. Но, согласно требованиям
HTML 4, элементы спискаLIне могут размещаться в этом контейнере!
Перестановка: методswapNode()
Если мы хотим переставить два узла в документе (в общем случае, две ветви
дерева), то надо воспользоваться методомswapNode(). Подчеркнем, что
этот метод не входит в список рекомендуемых W3C, но поддерживается Internet
Explorer 5. Этот метод меняет местами узел, который его активизировал, и
узел, ссылку на который от принимает в качестве параметра. Например, строка кода
var oSwappedNode = oFirst.swapNode(oSecond)
переставляет узлыoFirstиoSecond. Возвращает ссылку на узел,
который активизирует метод.
Небольшое отступление для поклонников Internet Explorer.W3C DOM не
поддерживает известные нам еще с Internet Explorer 4 очень удобные для
копирования и вставки различных фрагментов документа такие свойства объектов,
какinnerHTML,innerText,outerHTMLиouterText.
Многие действия с помощью этих свойств программируются гораздо проще, чем в
рамках W3C DOM. В качестве иллюстрации ниже приведен простенький пример
использования свойстваouterHTML. Когда пользователь щелкает по кнопке
передачи формы на сервер, вместо кнопки появляется сообщение с благодарностью:
<INPUT TYPE="submit" VALUE="Отправить"
onClick="this.outerHTML='Благодарим Вас за участие в нашем опросе.'">
А вот как это же действие можно реализовать, используя только средства W3C
DOM:
<INPUT TYPE="submit" VALUE="Отправить"
onClick="replaceButton(this)">
<SCRIPT TYPE="text/javascript">
function replaceButton(oButton) {
var oText = document.createTextNode("Благодарим Вас за участие
в нашем опросе.")
var oParent = oButton.parentNode
oParent.replaceChild(oParagraph,oButton)
}
</SCRIPT>
Помимо рассмотренных нами методов редактирования дерева документа, имеется
еще два метода, о которых необходимо сказать.
МетодapplyElement()
Описанные выше методыappendChild()иinsertBefore()позволяют
узлам заводить деток. Internet Explorer 5 поддерживает также метод, который
позволяет узлу-элементу обзаводиться родителем. Таким методом является
методapplyElement(), который принимает в качестве параметра ссылку на
будущего родителя. Например, строка кода var oParent = oChildNode.applyElement(oParentNode)
задает узелoParentNodeв качестве родительского для
узлаoChildNode. При этом, узелoParentNodeлишается (если они у
него были) как своего родителя, так и своих деток перед тем, как "заполучить"
нового наследника. Метод возвращает ссылку на нового родителя. Подчеркнем, что
методapplyElement()не применим к текстовым узлам.
МетодhasChildNodes()
Узнать, есть или нет у узла детки, можно с помощью
методаhasChildNodes(). Например, строка кода oTestedNode.hasChildNodes()
возвращаетtrue, когда узелoTestedNodeимеет деток,
иfalseв противном случае.
Работа с атрибутами элементов
Все атрибуты узла-элемента (за исключением атрибутаSTYLE) составляют
коллекциюattributes. W3C определяет эту коллекцию как массив с доступом
по именам элементов,
например,oNode.attributes.alignилиoNode.attributes["align"]возвращает
значение атрибутаALIGNузлаoNode. (В документации Microsoft по
DHTML сказано, что к элементам коллекциинадообращаться по индексу
массива. Но на практике работает описанное выше обращение по именам. Вероятно,
документация обновляется реже, чем появляются новые версии броузера.) Имя
атрибута надо набирать прописными буквами вне зависимости от того, в каком
регистре они набраны в HTML-источнике. СвойствоnodeNameдля
узлов-атрибутов возвращает название атрибута, аnodeValue --
значение атрибута. Свойство атрибутовspecifiedпозволяет узнать,
определен или нет этот атрибут. Если, например, у
узлаoNodeатрибутALIGNопределен, то oNode.attributes["align"].specified
возвращаетtrue, а если не определен, тоfalse. (Такое
поведение свойстваspecifiedреализовано и в Internet Explorer 5
вопреки документации Microsoft по DHTML)
МетодcreateAttribute()
МетодcreateAttribute()объектаdocumentпозволяет создать
узел-атрибут. В качестве параметра метод принимает имя атрибута. Internet
Explorer 5 не поддерживает этот метод.
МетодsetAttribute()
Атрибуты как новых элементов, так и тех, что заданы через HTML, можно задать
либо традиционным способом -- присваивая значение свойству (атрибуту) узла,
либо с помощью методаsetAttribute(). Оба способа демонстрируются ниже
для атрибутаID. Так, любая из строк кода oNode.id= "newItem"
oNode.setAttribute("id","newItem")
задает для элементаoNodeв качестве идентификатора
строку"newItem". МетодsetAttribute()требует два параметра.
Первый параметр -- строка, которая задает название атрибута. Второй
параметр -- строка, число или булево выражение соответствует значению
атрибута. Согласно документации в Internet Explorer 5, по умолчанию
названия атрибутов чувствительны к регистру, в котором они набраны. Если набор
строчных и прописных букв не совпадает с использованным ранее в названии
атрибута, то будет создан новый атрибут. Зависимость от регистра можно отменить,
задав в качестве третьего параметра метода число0.
МетодremoveAttribute()
Удалить атрибут у элемента можно, активизируя
методremoveAttribute(), который требует в качестве параметра название
этого атрибута. Если удаленный атрибут имеет значение по умолчанию, то оно
восстанавливается. Согласно документации в Internet Explorer 5, по
умолчанию в параметре набор строчных и прописных букв должен совпадать с
использованным ранее в названии атрибута. Зависимость от регистра можно
отменить, задав в качестве второго параметра метода число0. Метод
возвращаетtrueпри успешном выполнении действия иfalseв
противном случае.
МетодgetAttribute()
Узнать текущее значение атрибута у элемента можно, активизируя
методgetAttribute(), который требует в качестве параметра название
этого атрибута.
Дополнительные методы в Internet Explorer 5
МетодclearAttributes()удаляет все атрибуты у объекта, который
активизирует этот метод. Не требует параметров. Возвращает ссылку на этот
объект.
МетодmergeAttributes()позволяет скопировать все
атрибуты у объекта, ссылку на который он принимает в качестве параметра, и
добавить эти атрибуты к объекту, который активизировал этот метод. Например,
строка кода oTargetNode.mergeAttributes(oSourceNode)
задает те же атрибуты для узлаoTargetNode, что и у
узлаoSourceNode. Метод возвращает ссылку на узел, который его
активизировал. Оба узла (oTargetNodeиoSourceNode) должны
соответствоватьодинаковымHTML-элементам, например,
элементамH1илиP.
|