В статье были внесены исправления. Если вы пытались воспользоваться скриптом и у вас что-то не получалось, то просмотрите ее еще раз ;-)
Спасибо Олегу «illinar» Гродзевичу
Предыдущая версия статьи

Очень часто, для работы клиентских скриптов нам нужны дополнительные данные, которых изначально нет на странице. Обычно они задаются как значения каких-то переменных прямо в JavaScript'е. Примером могут служить настройки крайне популярных HierMenus или мое дерево. На самом деле в этом нет абсолютно ничего плохого. Таким образом задаются дополнительные данные для практически всех скриптов, которые сложнее, чем роловер. Недостатком этого способа можно считать только плохая читабельность - посмотрев впервые на такой код далеко не каждый поймет что к чему относиться.

Более понятным для человека может являтся использование XML'ных конструкций. Помимо повышенной читабельности они дают возможность правильнее задавать древовидные структуры, которые используются в приведенных выше примеров. Подобные конструкции очень легко обрабатываются DOM-функциями, но недоступны для старых браузеров, которые не поддерживают DOM level 1. Т.е. ими можно пользоваться в настоящее время только в Netscape6/Mozilla и MSIE5+.

Использовать XML-данных в клиентских скриптов можно двумя способами. Первый, самый логичный и самый правильный - это подгружение внешних XML-файлов. Но этот способ я рассмотрю в другой статье. Второй способ, который буду описывать далее - это использование "нестандартных" тэгов и атрибутов в самом HTML-файле.

Нестандартные атрибуты

HTML - хороший язык. И наверное самое лучшее в нем - то, что программы его понимающие (браузеры) должны игнорировать все тэги и атрибуты, с которыми они не знакомы. На этом принципе построены почти все дополнения в нем - фреймы, стили и т.п. Т.е. браузер, который не понимает CSS просто проигнорирует атрибут style для элемента и никаких ошибок не выдасть - это не его дело, что там за атрибуты еще могут быть.

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

<img src="image.gif" name="myImage"
   defaultSrc="image.gif"
   selectedSrc="image-s.gif"
   activeSrc="image-a.gif"
   onMouseOut="this.src = this.getAttribute('defaultSrc');"
   onMouseOver="this.src = this.getAttribute('selectedSrc');"
   onMouseDown="this.src = this.getAttribute('activeSrc');"
>

В данном примере используется DOM-метод getAttribute(), с чьей помощью нужно доставать значения как стандартных так и нестандартных атрибутов. Хотя, если использовать максимально дотошно методов DOM, то в обработчиках нужно было использовать также и setAttribute(), чтобы задать нового значения для src:

onMouseOut="this.setAttribute('src', this.getAttribute('defaultSrc'));"

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

Конечно, атрибуты можно доставать не только из самого элемента. Метод getAttribute() есть для всех элементов страницы и для его использование достаточно просто обратиться к конкретному элементу. Например, чтобы сделать предварительную загрузку всех альтернативных изображений для роловеров можно просто "пробежаться" по всем тэгам img на странице и проверить есть ли у них соответствующие атрибуты. Это можно сделать с помощью коллекции document.images, но если в вашем скрипте вам будут нужны элементы, которые не попадают ни в одной стандартной коллекцией, то нужно воспользоваться методом getElementsByTagName():

<script language="JavaScript" type="text/javascript"><!--

var preloadedImages = new Array();

function preloadAlternateImages() {
   if (document.getElementsByTagName) {
      var allImages = document.getElementsByTagName('img');
      for (var i = 0; i < allImages.length; i++) {
         if (allImages.item(i).getAttribute('name')) {
            var imageName = allImages.item(i).getAttribute('name');
            if (allImages.item(i).getAttribute('defaultSrc')) {
               preloadedImages[imageName + 'defImg'] = new Image();
               preloadedImages[imageName + 'defImg'].src = allImages.item(i).getAttribute('defaultSrc');
            }
            // ... то-же для selectedSrc и activeSrc;
         }
      }
   }
}

// --></script>

Данная функция должна вызываться после загрузки документа, например на onload, потому что раньше просто не будут тэги <img>. Наверное заметили, что в функции проверяется наличие тэгов с помощью все того-же метода getAttribute(), вместо hasAttribute(). Дело в том, что hasAttribute() появился в DOM level 2 и работает только в Netscape6/Mozilla. MSIE6 поддерживает только DOM level 1. Второй способ определения атрибута - использовать свойство specified не очень удачен из-за того, что метод getAttributeNode(), с помощью которого нужно обращаться к атрибуту, чтобы проверить его свойства поддерживается только MSIE6 и Netscape6/Mozilla, а обращение к атрибуту как к свойству объекта (object.defaultSrc) - не является правильным и может не сработать, если касается нестандартных атрибутов.

И конечно, если будете подгружать картинки, нужно чуть-чуть изменить код обработчиков событий, в котором теперь будет еще и проверка на наличие подгруженного изображения:

onMouseOut="if (preloadedImages[this.getAttribute('name') + 'defImg'])
   this.setAttribute('src', preloadedImages[this.getAttribute('name') + 'defImg']);"

Для полноты картины нужно отметить также, что методом setAttribute() можно устанавливать любые атрибуты, как стандартных, так и придуманных вами. А методы createAttribute() и setAttributeNode(), которые предназначены для этого, все еще недстаточно корректно обрабатываются браузерами.

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

Нестандартные тэги

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

Несмотря на это, для цели, с которой я начал статью, нестандартные тэги очень удобны. Приведу в качестве примера часть структуры дерева этого сайта:

<tree>
   <branch id="item1" name="Как сделать?" uri="/howto/" isOpen="false">
      <branch id="item2" name="Слои" uri="/howto/layers/"/>
      <branch id="item3" name="Делаем дерево из таблицы" isOpen="true">
         <branch id="item4" name="Часть 1" uri="/howto/tree1/"/>
         <branch id="item5" name="Часть 2" uri="/howto/tree2/"/>
      </branch>
      <branch id="item6" name="Играем в прятки" uri="/howto/hiding/"/>
      <branch id="item7" name="Скажи мне, что у тебя за браузер..." uri="/howto/browserdetect/"/>
   </branch>
   <branch id="item8" name="Общие" uri="/common/" isOpen="false">
      <branch id="item9" name="Есть в браузере такая фича?" uri="/common/hasfeature/"/>
      <branch id="item10" name="Netscape 6 вышел!" uri="/common/nn6out/"/>
   </branch>
</tree>

Этот код не будет показан ни одним браузером, пока тэги <tree> и <branch> не станут стандартными. Несмотря на это, этот код доступен DOM-методам. К тому-же понять какие ветки являются дочерными по отношении какой, а также, где указывается имя, где путь и каким образом нужно добавлять новые ветки - просто прозрачно даже для пользователя впервые увидившего код.

Примечание Leechy
К сожалению код, приведенный ниже будет работать только в NN6/Mozilla в случае, если он изначально присутствовал на странице. MS Internet Explorer распознает подобные нестандартные тэги в DOM-дереве документа, только если они были вставлены в дерево стандартными DOM-методами, описанными коротко в статье DOM: Коротко о Создании. Прошу прощения за эту ошибку с моей стороны. Тем не менее описанные DOM-методы работают для стандартных тэгов, для сгенеренного контента и для импортированного XML!

Для того, чтобы достать все данные и привести их к тому виду, который описан в статье "Делаем дерево из таблицы (часть 2)" придется снова воспользоваться методом getElementsByTagName(), но по самим веткам дерева будем бежать свойствами firstChild и nextSibling, чтобы показать как они работают:

<script language="JavaScript" type="text/javascript"><!--

function getBranches(parentItem, level) {
   if (parentItem.hasChildNodes()) {
      var parentId = (parentItem.getAttribute('id'))? parentItem.getAttribute('id') : '';
      var currentItem = parentItem.firstChild;
      while (currentItem) {
         if (currentItem.nodeType == 1 && currentItem.tagName == 'BRANCH') {
            var currentId = currentItem.getAttribute('id');
            var isOpenAttr = currentItem.getAttribute('isOpen');
            var isOpen = (isOpenAttr && isOpenAttr == 'true')? true : false;
            treeItems[currentId] = new treeItem(currentId, level, parentId, isOpen);
            getBranches(currentItem, level + 1);
         }
         currentItem = currentItem.nextSibling;
      }
   }
}

function getTrees() {
   var treeElement = document.getElementsByTagName('tree').item(0);
   if (treeElement) getBranches(treeElement, 0);
}

// --></script>

В самом начале функции getTrees() мы присваиваем treeElement'у document.getElementByTagName('tree').item(0). Т.е. первый элемент <tree>, который есть на странице. Просто предполагается, что он будет единственный. Если в вашем случае нужно использовать больше, чем один основной тэг, то тогда нужно обработать всех элементов таким-же образом, каким обрабатываются тэги <img> в примере с роловерами.

Если есть treeElement, то запускается функция getBranches(), которая собственно и будет доставать информацию. Ей передаем текущий родительский элемент (в начале это <tree>) и текущий уровень (это необходимо скрипту, из статьи). Начинаем с проверки есть ли дочерные элементы (в терминологии DOM их называют "нодами", от англ. node - узел, но "узлами" я называть их не буду) у родительского элемента. Для этого есть метод hasChildNodes(), который возвращает true если есть под-ноды. Проверка нужна, потому, что мы будем запускать функцию getBranches() для всех веток дерева, чтобы унифицировать код.

Первый дочерный элемент, в случае если есть под-ноды, указан в свойстве firstChild. Кроме этого у каждого нода есть также свойство lastChild, которое указывает соответственно последного под-нода. Но в данном случае нам нужно "бежать" по дереву сверху-вниз, поэтому использую firstChild.

Цикл, с помощью которого будем рассматривать всех под-нодов, это простейший while, в теле которого переменной currentItem присваивается currentItem.nextSibling. Свойство nextSibling - указатель на следующий нод этого уровня. Т.е. если currentItem.firstChild - это первый под-нод, то currentItem.nextSibling - следующий под-нод для родителя currentItem. Как наверное догадываетесь есть еще и previousSibling, который указывает соответственно на предыдущего нода в списке.

Итак, в чем состоит обработка всех под-нодов? Для начала нужно определить что это за нод. Ноды бывают разными. В спесификации их 9 видов, но поскольку MSIE5 различает всего два из них - тэги и текст, то пока не буду описывать всех. Теперь подробнее... у каждого нода есть свойство nodeType - это константа, чье числовое значение указывает что это за нод. У тэгов nodeType равен 1 (единице), у текста - 3 (тройке). На возникающий вопрос "Почему нужно проверять что за нод обрабатываем?" ответить просто - все пробелы и переносы строк, которые находятся между элементами и в HTML обычно игнорируются на самом деле являются текстовыми нодами. Т.е. между двумя нодами <branch> есть еще и один нод со значением nodeType = 3. Это очень часто не учитывается при написании скриптов и случаются ошибки.

Помимо проверки типа, нужно также проверить и имя тэга. Оно содержится в свойстве tagName. Проверка делается из-за того, что между ветками дерева можно вставлять любые другие тэги, даже HTML (которые отобразятся на странице), но нас интересуют только тэги <branch>.

Далее, нужно узнать информацию, которая нам нужна для того, чтобы сделать массив treeItems. А это в данном случае только id и isOpen. Остальные данные сейчас не нужны. Может быть они понадобятся для построения самого дерева. Также нужен id родительского нода, его можно узнать всего один раз в самом начале обработки под-нодов, что и сделано. Думаю, не нужно разъяснят также и то каким образом достаем данные из других атрибутов.

В самом конце просто добавляем в массив treeItems новую запись и запускаем функцию getBranches() для поиска эвентуальных под-веток текущей. Вот и все.

Думаю все просто. Если вы разобрались, значить знаете всех основных DOM-методов и свойств с помощью которых можете работать не только над нестандартными HTML-элементами, но также и с самым-обычным HTML-кодом. Для справки приведу их список:

getElementById('id-тэга') - возвращает указатель на элемент с уникальным атрибутом id
getElementsByTagName('имя-тэга') - возвращает масив из элементов с заданным именем
getAttribute('имя-атрибута') - возвращает значение указанного атрибута
setAttribute('имя-атрибута', 'значение-атрибута') - задает новый атрибут или меняет значение старого
hasChildNodes() - возвращает true если у этого нода есть дочерние ноды, в противном случае - false
firstChild - указывает на первый дочерный нод
lastChild - указывает на последний дочерный нод
nextSibling - указывает на следующий нод
previousSibling - указывает на предыдущий нод
nodeType - возвращает значение типа
tagName - возвращает имя тэга (для тэгов, если это текстовой нод, то возвращает '#text')

Москва,
02.02.02
Leechy




© 2000 - 2026 Leechy (leechy@dhtml.ru)