Возможность обхода всех узлов в дереве документа дает нам средство поиска оп_ ределенных узлов. При программировании с использованием DOM API довольно
336 Глава 15. Работа с документами
часто возникает задача получения определенного узла из документа или списка узлов определенного типа. К счастью, DOM API предоставляет функции, облег_ чающие решение этой задачи.
Объект Document является корневым элементом для всего DOM_дерева, но он не представляет ни один из HTML_элементов в этом дереве. Свойство document.docu_ mentElement ссылается на тег <html>, который выступает в роли корневого элемента документа. Свойство document.body соответствует тегу <body>, который в большин_ стве случаев представляет больший интерес, чем его родительский тег <html>.
Свойство body объекта Document представляет собой особое удобное свойство, через котрое предпочтительно обращаться к тегу <body> HTML_документа. Однако при отсутствии такого специального свойства мы могли бы обратиться к тегу <body> следующим образом:
document.getElementsByTagName("body")[0]
Это выражение вызывает метод getElementsByTagName() и выбирает первый эле_ мент полученного массива. Вызов getElementsByTagName() возвращает массив всех элементов <body> в документе. HTML_документы могут содержать только один тег <body>, поэтому мы знаем, что нас интересует первый элемент полученного массива.1
Метод getElementsByTagName() может использоваться для получения списка HTML_ элементов любого типа. Чтобы, например, найти все таблицы в документе, необ_ ходимо сделать следующее:
var tables = document.getElementsByTagName("table"); alert("Количество таблиц в документе: " + tables.length);
Обратите внимание: поскольку HTML_теги нечувствительны к регистру, строки, передаваемые в getElementsByTagName(), также нечувствительны к регистру. То есть предыдущий код находит теги <table>, даже если в коде они выглядят как <TABLE>. Метод getElementsByTagName() возвращает элементы в том порядке, в ко_ тором они расположены в документе. И наконец, если передать методу getEle_ mentsByTagName() специальную строку "*", он вернет список всех элементов в по_ рядке их присутствия в документе. (Этот особый вариант не поддерживается в IE 5 и 5.5. См. описание специфического для IE массива Document.all[] в IV час_ ти книги.)
Иногда требуется получить не список элементов, а один конкретный элемент до_ кумента. Если о структуре документа известно многое, то можно прибегнуть к методу getElementsByTagName(). Так, сделать что_то с четвертым абзацем доку_ мента можно при помощи следующего кода:
var myParagraph = document.getElementsByTagName("p")[3];
Однако, как правило, это не самый лучший (и не самый эффективный) прием, по_ скольку он в значительной степени зависит от структуры документа – вставка но_ вого абзаца в начало документа нарушит работу кода. Когда требуется манипули_
1 Формально метод getElementsByTagName() возвращает подобный массиву объект No_ deList. В этой книге для обращения к объектам NodeList используется нотация массивов, и неформально я буду называть их массивами.
15.6. Поиск элементов в документе
ровать определенными элементами документа, лучше идти другим путем и опре_ делить для этих элементов атрибут id, задающий уникальное (в пределах доку_ мента) имя элемента. Тогда элемент можно будет найти по его идентификатору. Например, отметить специальный четвертый абзац документа тегом можно так:
<p id="specialParagraph">
Теперь легко найти узел этого абзаца посредством следующего JavaScript_кода:
var myParagraph = document.getElementById("specialParagraph");
Обратите внимание: метод getElementById() не возвращает массив элементов, как метод getElementsByTagName(). Так как значение каждого атрибута id является (или предполагается) уникальным, getElementById() возвращает только один эле_ мент с соответствующим атрибутом id.
Метод getElementById() достаточно важен и довольно часто применяется в DOM_ программировании. Обычно он служит для определения вспомогательной функ_ ции с более коротким именем:
// Если x – это строка, предполагается, что это идентификатор элемента
// и требуется отыскать этот элемент.
// В противном случае предполагается, что x – это уже элемент,
// поэтому нужно просто вернуть его.
function id(x) {
if (typeof x == "string") return document.getElementById(x); return x;
}
С помощью аналогичных функций можно реализовать такие методы манипули_ рования DOM_деревом, которые будут принимать в качестве аргументов элемен_ ты или идентификаторы элементов. Для каждого такого аргумента x перед его использованием достаточно будет записать x = id(x). Один широко известный на_ бор инструментальных средств для применения в сценариях1, написанных на клиентском JavaScript, определяет подобный этому вспомогательный метод, имеющий еще более короткое имя – $().
Оба метода, getElementById() и getElementsByTagName(), относятся к методам объек_ та Document. Однако объект Element также определяет метод getElementsByTag_ Name(). Этот метод объекта Element ведет себя так же, как метод объекта Document, за исключением того, что возвращает только элементы, являющиеся потомками того элемента, для которого он вызван. Благодаря этому можно, например, сна_ чала использовать метод getElementById() для поиска определенного элемента, а затем – метод getElementsByTagName() для поиска всех потомков данного типа внутри найденного тега, например:
// Ищет определенный элемент Table внутри документа
// и подсчитывает количество строк в таблице.
var tableOfContents = document.getElementById("TOC"); var rows = tableOfContents.getElementsByTagName("tr"); var numrows = rows.length;
1 Имеется в виду библиотека Prototype, разработанная Сэмом Стефенсоном (Sam Stephenson) и доступная на сайте http://prototype.conio.net.
338 Глава 15. Работа с документами
И наконец, следует отметить, что для HTML_документов объект HTMLDocument оп_ ределяет также метод getElementsByName(). Этот метод похож на getElementById(), но выполняет поиск элементов по атрибуту name, а не по атрибуту id. Кроме того, поскольку атрибут name не обязательно уникален в пределах документа (напри_ мер, группы переключателей в HTML_формах обычно имеют одинаковый атри_ бут name), getElementsByName() возвращает массив элементов, а не одиночный эле_ мент. Пример:
// Ищем тег <a name="top">
var link = document.getElementsByName("top")[0];
// Ищем все элементы <input type="radio" name="shippingMethod"> var choices = document.getElementsByName("shippingMethod");
В дополнение к выбору элементов по названию тега и идентификатору очень час_ то бывает удобно иметь возможность отбирать элементы по принадлежности к определенному классу. Атрибуту class в HTML и соответствующему ему свой_ ству className в JavaScript можно присваивать одно или более имен классов (раз_ деленных пробелами). Эти классы предназначены для использования совместно с таблицами CSS_стилей (подробнее об этом см. в главе 16), но это – не единствен_ ное их предназначение. Предположим, что в HTML_документ вставляются важ_ ные предупреждения, например следующим образом:
<div class="warning"> Это предупреждение </div>
Имея такое определение, можно использовать таблицы CSS_стилей, с помощью которых задать цвет, отступы, рамки и другие атрибуты вывода предупрежде_ ний этого класса. Но что делать, если необходимо написать JavaScript_сцена_ рий, который мог бы отыскивать теги <div>, являющиеся членами класса «пре_ дупреждений», и манипулировать этими тегами? Возможное решение приво_ дится в примере 15.4. Здесь определяется метод getElements(), который позволя_ ет отобрать элементы по имени класса и/или тега. Обратите внимание на ухищрения, используемые при работе со свойством className, – они вызваны тем, что в данном свойстве могут храниться имена нескольких классов. Метод getEle_ ments() содержит вложенную функцию isMember(), которая проверяет принад_ лежность HTML_элемента к заданному классу.
Пример 15.4. Отбор HTML'элементов по имени класса или тега
/**
* getElements(classname, tagname, root):
* Возвращает массив DOM_элементов, которые являются членами заданного класса,
* соответствуют тегам с определенным именем и вложены в элемент root.
*
* Если аргумент classname не определен, отбор элементов производится
* без учета принадлежности к какому_то определенному классу.
* Если аргумент tagname не определен, отбор элементов производится без учета имени тега.
* Если аргумент root не определен, поиск производится в объекте document.
* Если аргумент root является строкой, он воспринимается как идентификатор
* элемента и поиск производится методом getElementsById()
*/
function getElements(classname, tagname, root) {
15.7. Модификация документа
// Если корневой элемент не определен, произвести поиск по всему документу
// Если это строка, найти сам объект
if (!root) root = document;
else if (typeof root == "string") root = document.getElementById(root);
// Если имя тега не определено, искать без учета имени тега if (!tagname) tagname = "*";
// Искать элементы, вложенные в элемент root и имеющие определенное имя тега var all = root.getElementsByTagName(tagname);
// Если имя класса не определено, вернуть все теги без учета имени класса if (!classname) return all;
// В противном случае отобрать элементы по имени класса
var elements =
[];
// Создается пустой массив
for(var i = 0;
i <
all.length; i++) {
var element = all[i];
if (isMember(element,
classname)) //
Метод isMember() определен далее
elements.push(element);
//
Добавлять члены класса в массив
}
// Обратите внимание: всегда возвращается массив, даже если он пустой return elements;
// Определяет принадлежность элемента к заданному классу.
// Эта функция оптимизирована для случая, когда свойство
// className содержит единственное имя класса. Но учитывает возможность
// наличия нескольких имен классов, разделенных пробелами.
function isMember(element, classname) {
var classes
=
element.className;
// Получить
список класов
if
(!classes)
return false;
//
Класс не
определен
if
(classes
== classname) return true; //
Точное совпадение
// Нет точного совпадения, поэтому если в списке нет пробелов,
// то этот элемент не является членом класса.
var whitespace = /\s+/;
if (!whitespace.test(classes)) return false;
// В этой точке известно, что элемент принадлежит нескольким
// классам, поэтому нужно проверить каждый из них.