Глаза страшат, а руки делают

ЧАСТЬ ТРЕТЬЯ


33. Работа с узлами
элементов DOM

  • объект nodeList;
  • свойства доступа к элементам;
  • методы доступа к элементам;
  • некоторые методы создания элементов.

При работе с HTML мы сталкивались с различными коллекциями: document.images, document.links, document.all и т.д. Эти коллекции не принадлежат интерфейсу DOM 3 и являются своеобразным «атавизмом», оставшимся от DOM 0.

Почему я обращаю на это ваше внимание?

Эти атавистические коллекции были затем встроены в интерфейс JavaScript и ведут себя как объекты Array. А коллекции DOM 3 — это свойства узлов DOM, они не яаляются объектами Array и ведут себя несколько иначе.

Смешение этих понятий может привести к созданию неработающих или «кривых» скриптов.

Преимущество использования DOM 3 в том, что мы можем сами создавать любые коллекции — как соответствующие, так и не соответствующие встроенным.

Объект nodeList

Когда мы обращаемся к группе узлов — например, к элементам с определённым тэгом, или только к текстовым узлам, или к атрибутам данного элемента, — мы получаем их в виде списка nodeList. Он похож на массив JavaScript, но таковым не является.

Как и массив, он имеет свойство length и порядковые номера элементов (начиная с нуля), указывающиеся в квадратных скобках. Но другие свойства и методы массивов JavaScript с ним не работают.

Каждый элемент объекта nodeList является узлом — объектом, имеющим ряд свойств.

Можно представить себе nodeList в виде хэша. Например, в списке атрибутов можно усмотреть массив «ключей» (имена атрибутов: align, color и т.д.) и «значений» (значения атрибутов: center, black и т.д.)

В некоторых случаях для работы с содержимым объекта nodeList бывает удобно преобразовать это содержимое в обычные массивы. Например, мы хотим получить массив всех тэгов, находящихся в <body>.

Метод document.getElementsByTagName("BODY")[0] возвращает элемент с тэгом <body> (подробнее о методе см. ниже, как и о свойстве childNodes).

Свойство childNodes элемента <body> возвращает список всех его дочерних узлов.

Устанавливаем «фильтр», выбирающий из них только узлы элементов (nodeType == 1).

Строим массив, содержащий имена тэгов (nodeName).

var allBodyTags = new Array;
var elmBody = document.getElementsByTagName("BODY")[0];
for (var i = 0; i < elmBody.childNodes.length; i++)
{
    // если тип узла - элемент
    if (elmBody.childNodes[i].nodeType == 1)
    {
        // наращиваем возвращаемый массив
        // из имён элементов

        allBodyTags = allBodyTags.concat(elmBody.childNodes[i].nodeName);
    }
}

Свойства доступа к элементам

Рассматриваемые ниже свойства относятся именно к элементам DOM. В HTML они интерпретируются с помощью тэгов.

(Ещё раз напомню, что это не синонимы. Если сравнить элемент с беговой дорожкой, то тэги — это старт и финиш. В элементах, не имеющих закрывающего тэга, эти понятия совпадают.)

Примечание

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

Многие свойства доступа к элементам являются массивами, хотя иногда они работают не совсем так, как массивы JavaScript. Нет чёткого разделения между элементами и текстовыми узлами, что создаёт проблемы в разных браузерах.

Каждый элемент содержит свойства, отражающие его место в иерархии DOM. Они позволяют перемещаться по всем элементам дерева. Вот некоторые свойства взаимоотношений элементов:

parentNode

родительский элемент

childNodes (массив)

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

firstChild

первый элемент в массиве childNodes

lastChild

последний элемент в массиве childNodes

prevSibling

предыдущий элемент в массиве childNodes

nextSibling

следующий элемент в массиве childNodes

Ещё раз приведу схему дерева простого документа из прошлого урока.


parentNode

Возвращает родительский элемент. На нашей схеме для узла HTML родительским элементом будет сам документ, для HEAD и BODY — узел HTML и т.д.

Текстовые узлы также поддерживают это свойство.

У узла документа, естественно, этого свойства нет.

Возможны вложения «матрёшечного» типа для доступа к «дедушке» или «прадедушке»:

элемент.parentNode.parentNode

childNodes

Список потомков узла (объект Nodelist). При работе с HTML эта функция полезна только для элементов. Текстовые узлы и узлы-атрибуты не имеют потомков.

Синтаксис

Node.childNodes[N]

Node — родительский узел.

N — номер потомка в списке.

Примечание

Браузеры семейства Netscape воспринимают пробелы в коде (пустые строки или переносы на другую строку) как текстовые узлы.

В нашем примере простейшего документа:

Узел document имеет одного потомка: элемент "html".

Элемент "html" по идее имеет двух потомков: элемент "head" и элемент "body". Но в браузерах семейства Netscape возникнут 4 потомка: перенос строки перед <head>, собственно <head>, перенос строки перед <body> и собственно <body>. Об этом нужно помнить, просматривая список childNodes в цикле for.

В природе как бы существует метод normalize(), которому надлежит устранить проблему, но элементы HTML на него не реагируют. Поэтому могу предложить лишь «кустарный» способ.

У этого способа есть один недостаток: он исключает из обращения все текстовые узлы, а не только «левые». Хотя это как посмотреть: возможно, он делает полезное дело, очищая зёрна от плевел и давая более чёткое разделение элементов и текстовых узлов.

Итак,

«…нормальные бандиты всегда идут в обход»

Смысл этой функции в превращении свойства childNodes в обычный массив JavaScript, очищенный от текстовых узлов (примерно то же, что я демонстрировал в разделе об объекте nodeList).

function cleanNodes(elm) {
// в качестве значения для аргумента elm выступает
// родительский элемент, который нам надо просмотреть.
var res = new Array;
    for (var i = 0; i < elm.childNodes.length; i++)
    {
        // если тип узла - элемент
        if (elm.childNodes[i].nodeType == 1)
        // наращиваем возвращаемый массив из имён элементов
        res = res.concat(elm.childNodes[i].nodeName);
    }
return res;
}

Почему строим массив именно из имён элементов (nodeName)?

Потому что nodeType у них у всех единица, а nodeValue, как мы помним, — null. А свойство length принимает правильный, нормализованный вид.

***

Есть ещё один кустарный способ избавиться от этого глюка: просто записать весь код HTML на одной длинной строке.

firstChild, lastChild

Указатели на соответствующие элементы в списке childNodes (см. таблицу выше).

элемент.firstChild — то же, что элемент.childNodes[0].

элемент.lastChild — то же, что элемент.childNodes[элемент.childNodes.length-1].

Обращаются к родительскому элементу, имеющему «семью» childNodes.

prevSibling, nextSibling

Эти указатели обращаются уже к самим «членам семьи» childNodes.

элемент[n].prevSibling = элемент.parentNode.childNodes[n-1]
элемент[n].nextSibling = элемент.parentNode.childNodes[n+1]

Естественно, что у childNodes[0] отсутствует prevSibling,
а у childNodes[childNodes.length-1] бесполезно искать nextSibling.

Примечание

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

Методы доступа к элементам

В этом обзорном уроке мы рассмотрим лишь несколько основных методов доступа к элементам. Первая группа методов принадлежит корневому объекту document, некоторые из них мы уже использовали.

Методы второй группы могут применяться и к самим элементам.

Сейчас мы рассмотрим только те немногие методы, которые нормально работают во всех браузерах (если есть какие-то нюансы, я их прокомментирую).

Метод объекта document

getElementById()

Этим методом мы уже не раз пользовались. Он ссылается на элемент с ID, указанным в аргументе (нужно не забыть задать ID при вёрстке страницы).

Синтаксис

document.getElementById("ID")
Метод для всех элементов

getElementsByTagName()

Возвращает список дочерних элементов по указанному в аргументе имени тэга.

Обратите внимание на правописание имени метода

Часть «Elements» пишется во множественном числе, с буковкой «s» на конце.

Синтаксис

элемент.getElementsByTagName("ИМЯ_ТЭГА")
элемент.getElementsByTagName("*")

элемент — любой элемент DOM.

Если в качестве параметра аргумента указана звёздочка, возвращается список всех дочерних элементов.

Этот метод можно применять как к самому документу, так и к любому элементу. Например, его можно применить к элементу <table> для перебора всех <tr>. Или для работы с фрагментом документа, заключённым в тэг <div>.

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

Например (обращение ко второму абзацу в контейнере с id-"content"):

document.geyElementById("content").getElementsByTagName("P")[1]

Поскольку метод всегда возвращает список, номер элемента (нулевой) необходимо указывать даже для тех элементов, которые существуют заведомо в единственном числе:

document.getElementsByTagName("BODY")[0]

Казалось бы, этот метод (с параметром в виде звёздочки) подходит для решения описанной выше проблемы с удалением лишних текстовых узлов. Но есть один нюанс: с этим параметром метод возвращает список не только дочерних элементов, но и всех «внуков» и «пра[прапра]внуков».

Некоторые методы создания элементов

Перечислены только методы, работающие с HTML во всех браузерах.

Метод объекта document

createElement()

Записывает в память образец объекта, связанного с тэгом, указанном в аргументе метода.

Синтаксис

document.createElement("имя_тэга")

Для того, чтобы созданный этим методом элемент отобразился на странице, нужно задать ему атрибуты и/или содержимое, а затем «привязать» к существующему «родителю». Всё это делается другими методами, которые бюудут рассмотрены ниже.

Методы, применимые к другим элементам

appendChild() — добавляет узел в конец списка дочерних узлов

cloneNode() — создает копию узла

hasChildNodes() — проверяет наличие дочерних узлов

insertBefore() — вставляет новый узел перед заданным дочерним узлом

removeChild() — удаляет заданный дочерний узел

replaceChild() — заменяет заданный дочерний узел новым узлом


appendChild()

Добавляет узел, определённый в аргументе, в конец списка childNodes данного узла и возвращает его в качестве результата. Если этот узел уже был в списке, то он сначала удаляется, а затем добавляется.

Синтаксис

элемент.appendChild("НОВ_ДОЧ_УЗ")

элемент — элемент, из которого вызван метод.

"НОВ_ДОЧ_УЗ" — имя добавляемого дочернего узла

Пример

var elem = document.createElement("IMG");
var _body = document.getElementsByTag("BODY")[0];
_body.appendChild(elem);
document.write(_body.lastChild.tagName);
// возвращает "IMG"

cloneNode()

Создаёт копию данного узла и возвращает ее.

Синтаксис

элемент.cloneNode(уровень)

элемент — элемент, из которого вызван метод.

уровень — булево выражение.

Если уровень — true, то создаётся копия поддерева документа, начиная с данного узла; если false, то копируется только сам узел (и его атрибуты, если это элемент).

hasChildNodes()

Возвращает булево значение: true, если элемент имеет хотя бы один дочерний узел; в противном случае — false.

Синтаксис

элемент.hasChildNodes()

элемент — элемент, из которого вызван метод.

insertBefore()

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

Синтаксис

элемент.insertBefore(новый_узел, точка_входа)

элемент — элемент, из которого вызван метод.

новый_узел — узел для вставки.

точка_входа — существующий узел, перед которым будет вставлен новый.

Если узел, указанный как новый, уже был в списке, то он сначала удаляется, а затем добавляется.

Если точка_входа — null (второй аргумент опущен), то узел вставляется в конце списка дочерних узлов элемента.

Пример

<div id="test2">
<p>Первый абзац</p>
<p id="entryPoint">Второй абзац</p>
<p>Третий абзац</p>
</div>
<script type="text/javascript">
function inserting() {
var parent = document.getElementById("test2");
var point = document.getElementById("entryPoint");
// создаём новый абзац
var elem = document.createElement("P");
// создаём текст для нового абзаца
var txt = document.createTextNode("А это вставка");
// загоняем текст в абзац
elem.appendChild(txt);
// вставляем абзац в nodeList перед абзацем с id="entryPoint"
return parent.insertBefore(elem, point);
}
</script>
<form>
<input type="button" value="Вставить" onClick="inserting()">
<input type="button" value="Сбросить" onClick="location.reload()">
</form>

Примечание

О методе createTextNode(), создающем новый текстовый узел, подробнее см. в одном из следующих уроков.

Результат

Первый абзац

Второй абзац

Третий абзац

removeChild()

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

Синтаксис

элемент.removeChild(удаляемый_узел)

элемент — элемент, из которого вызван метод.

удаляемый_узел — дочерний узел элемента, подлежащий удалению

Пример

Удаляем первый абзац из предыдущего примера.

<script type="text/javascript">
var _div = document.getElementById("test2");
</script>
<form>
<input type=
"button" value="Удалить" onClick="_div.removeChild(_div.firstChild)">
<input type=
"button" value="Восстановить" onClick="location.reload()">
</form>

Примечание

В браузерах семейства Netscape на первом щелчке на странице ничего не произойдёт (в коде удалится перевод строки перед первым тэгом <p>). Поэтому лучше не использовать свойства типа firstChild.

Итак, мы узнали:

о доступе к элементам DOM.

А также научились:

создавать, добавлять и удалять эти элементы.


К следующему уроку >>


 020034