Ненавязчивый JavaScript. Глава 3: Создание и уничтожение элементов страницы

Текст ниже — перевод статьи "Unobtrusive Javascript", чей автор Christian Heilmann любезно дал мне согласие на перевод. Оригинальный адрес статьи www.onlinetools.org/articles/unobtrusivejavascript/chapter3.html

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

Создание нового содержания

createElement(element)
Создание нового элемента
createTextNode(string))
Создание нового текстового узла со значением string.

Только что созданные элементы не добавляются в документ немедленно, они пребывают в чистилище до тех пор, пока мы не добавим их куда-нибудь в дерево узлов.

Javascript:

mynewparagraph=document.createElement('p');
mynewtext=document.createTextNode('this is a new paragraph');

Изменение существующего содержания

setAttribute(attribute,value)
Добавляет новый атрибут с соответствующим значением к объекту
appendChild(child)
Добавляет дочерний узел к объекту, этот узел должен быть объектом, строку использовать нельзя.
cloneNode()
Копирует узел со всеми дочерними узлами.
hasChildNodes()
Проверяет, имеет ли объект дочерние узлы и если да, возвращает true
insertBefore(newchild,oldchild)
Добавляет новый узел newchild перед узлом oldchild в дереве документа.
removeChild(oldchild)
Удаляет дочерний узел oldchild.
replaceChild(newchild,oldchild)
Перемещает узел newchild на место узла oldchild
removeAttribute(attribute)
Удаляет атрибут attribute объекта.

Пример с картинкой

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

HTML:

<ul id="imglist">
 <li><a href="home.gif" target="_blank">Home
 (new window)</a></li>
 <li><a href="home_on.gif" target="_blank">Home active
 (new window)</a></li>

 <li><a href="jscsshtml.gif" target="_blank">HTML-CSS-Javascript
 (new window)</a></li>
</ul>

Итак, в случае, когда Javascript и DOM доступны, мы хотим:

  • Удалить надпись «(new window)» из ссылки
  • Добавить обработчик события для вызова функции popw()

Эта функция должна:

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

Первую задачу решить нетрудно:

Javascript:

function imgpop()
{
 var il,imga,imgatxt;
	
// получаем все  LI из списка изображений, пробежаться по ним циклом
 il=document.getElementById('imglist').getElementsByTagName('li');
 for(i=0;i<il.length;i++)
 {
	
// вычленяем первую ссылку из LI
 imga=il[i].getElementsByTagName('a')[0];
	
// удаляем выражение (new window) из текста ссылки
// (который является содержанием (nodeValue) первого узла)
 imgatxt=imga.firstChild;
 imgatxt.nodeValue=imgatxt.nodeValue.replace(/ \(new window\)/,'');
	
// добавляем обработчик события для вызова popw();
 imga.onclick=function(){return popw(this);}
 //imga.onkeypress=function(){return popw(this);}
 }
}

Далее, для функции popw() мы должны использовать некоторые из методов описанных выше:

Javascript:

function popw(o)
{
 var newimg;
// если уже есть изображение в parentNode (li)
 if(o.parentNode.getElementsByTagName('img').length>0)
 {
// удаляем его
  o.parentNode.removeChild(o.parentNode.getElementsByTagName('img')[0]);
 } else {
// иначе создаем новое изображение и добавляем обработчик,
// который удаляет изображение, когда вы кликаете по нему
  newimg=document.createElement('img');
  newimg.style.display='block';
  newimg.onclick=function(){this.parentNode.removeChild(this);};
  newimg.src=o.href;
  o.parentNode.appendChild(newimg)
 }
 return false;
}

Посмотреть работу скрипта показа изображений

Пример: ссылка на указатель даты

Пусть, например, у нас есть форма, содержащая поля даты и мы хотим предложить указатель даты пользователям, у которых подключен Javascript, и вводить дату вручную — прочим пользователям. Давайте не будем обсуждать здесь саму реализацию указателя даты, а сфокусируемся на том, как его вызвать.

Сначала напишем необходимый HTML. Чтобы видеть какие элементы должны выдавать ссылку на указатель даты, мы добавим к ним классы date.

HTML:

<h1>Flight booking</h1>
<form action="nosend.php" method="post" onsubmit="return check(this);">
<p>Step 1 of 4</p>
<h2>Please select your dates</h2>
<p>
 <label for="startdate">Start Date</label>

 <input type="text" class="date" id="startdate" name="startdate" />
</p>
<p>
 <label for="enddate34;>End Date</label>
 <input type="text" class="date" id="enddate" name="enddate" />
</p>

<p>
 <input type="submit" value="send" />
</p>
</form>

Мы перебираем все input'ы документа, и проверяем, у какого из них содержится класс date в className (помните, элементы могут иметь более чем один класс в атрибуте class!)

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

Как только ссылка создана, мы добавляем ее после поля ввода.

Javascript:

function addPickerLink()
{
 var inputs,pickLink,pickText;
	
// перебираем все input'ы
 inputs=document.getElementsByTagName('input');
 for(i=0;i<inputs.length;i++)
 {
	
// если class содержит ‘date'
  if(/date/.test(inputs[i].className))
  {
	
// создать новую ссылку с текстом
   pickLink=document.createElement('a');
   pickText=document.createTextNode('pick a date');
	
// добавляем текст дочерним узлом к ссылке
   pickLink.appendChild(pickText);
	
// устанавливаем атрибут href в #
// и вызываем picker на клик или переход с клавиатуры
   pickLink.setAttribute('href','#');
   pickLink.onclick=function(){picker(this);return false;};
   //pickLink.onkeypress=function(){picker(this);return false;};
	
// добавляем новую ссылку к родителю поля ввода (это P)
   inputs[i].parentNode.appendChild(pickLink)
  }
 }
}

Посмотреть демонстрационный пример

Сейчас после всех полей с датами есть ссылка на picker().

Все что нам надо, это сказать функции picker к чему употребить возвращаемое значение.

Ссылка передает сама себя функции picker() в виде объекта, и мы должны получить доступ к предыдущему узлу того же уровня — INPUT.

Javascript:

function picker(o)
{
 alert('This is a simulation only.') // это демонстрационная функция
 o.previousSibling.value='26/04/1975?;
}

Готово, но не вполне. Мы добавили новую ссылку как последнего ребенка к родительскому узлу input, а ведь очень даже может быть, что предыдущий узел того же уровня, что и наша ссылка на самом деле не INPUT, а пробел! Следовательно, нам надо перебирать все предыдущие узлы того же уровня пока мы не наткнемся на узел типа элемент.

Javascript:

function picker(o)
{
 alert('This is a simulation only.') // это демонстрационная функция
 while(o.previousSibling.nodeType!=1)
 {
  o=o.previousSibling;
 }
 o.previousSibling.value='26/04/1975?;
}

Перебор всегда операция нудная (hacky) и зачастую довольно медленная. Чтобы избежать перебора мы должны изменить нашу функцию.

Изменение функции addPickerLink().

Использовать addPickerLink() легко, но это делает нас зависимым от разметки. Что случится если, к примеру, нам нужно будет добавить впоследствии за тэгом input — тэг SPAN со звездочкой (*), чтобы указать, что это поле необходимое?

Обходной маневр — это использовать insertBefore() для следующего узла того же уровня, что и наше поле ввода.

Javascript:

function addPickerLink()
{
 var inputs,pickLink,pickText;
	
// перебираем все input'ы
 inputs=document.getElementsByTagName('input');
 for(i=0;i<inputs.length;i++)
 {
	
// если class содержит 'date'
   if(/date/.test(inputs[i].className))
   {
	
// создаем новый элемент ссылки и текст
    pickLink=document.createElement('a');
    pickText=document.createTextNode('pick a date');
	
// добавляем текст как дочерний узел к ссылке
    pickLink.appendChild(pickText);
	
// устанавливаем атрибут href равным #
// и вызываем picker при клике или переходе с клавиатуры
    pickLink.setAttribute('href','#');
    pickLink.onclick=function(){picker(this)};
    //pickLink.onkeypress=function(){picker(this)};
	
// добавляем новую ссылку сразу после input'а
    inputs[i].parentNode.appendChild(pickLink)
    inputs[i].parentNode.insertBefore(pickLink,inputs[i].nextSibling);
    }
  }
}

Посмотреть демонстрационный пример.

Памятки

Ну, вот и все, с описанными инструментами мы способны получить доступ к любому элементу документа и затем изменить этот элемент, и мы можем улучшить работу пользователя, не привязываясь к Javascript.

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

Некоторые проблемы общего характера:

  • Убедитесь что элемент существует, прежде чем попытаться обратиться к нему. Некоторые браузеры счастливы проверить object.nextSibling.nodeName и возвращают false когда следующего узла нет или же этот узел текстовый, но другие генерируют ошибку, когда вы пытаетесь обратиться к атрибуту несуществующего элемента.
  • Убедитесь, что код не привязан слишком сильно к верстке, перевод строки могут быть прочитаны как новый узел, разметка может поменяться, а ведь вам неохота менять ваш скрипт с каждой сменой дизайна.
  • Чтение содержания элемента осуществляется чтением значения его childNodes, а не самого элемента! document.getElementsByTagName('h2')[0].nodeValue пуст, document.getElementsByTagName('h2')[0].firstChild.nodeValue — нет.
  • Проверяя nodeNames и атрибуты, убедитесь, что вы указываете правильный регистр , так как одни браузеры считают, что элементы пишутся в вернем регистре, другие же, что в нижнем.
  • HTML сгенерированный DOM в большинстве случаев синтаксически неверен, если вы хотите повторно использовать HTML из сгенерированной браузером странице, вы должны вычистить его.
  • Избегайте частого перебора элементов, если вы можете сами создать разметку, с которой вам предстоит работать, стремитесь использовать ID вместо перебора.
  • Знайте ваш синтаксис. Многочисленное (Many a time) использование getElementsById может послужить причиной частого переписывания скрипта (rescripting).
  • Знайте ваши объекты Javascript и HTML атрибуты. Это означает, что не нужно проверять атрибут, которого нет.
  • Не рассчитывайте, что на других сайтах придерживаются ваших приемов верстки. Например, стоит проверять, что className содержит вашу строку, а не то, что он ей равен, так как некоторым программистам нравиться применять несколько классов к одному элементу.

Как насчет innerHTML?

Когда появился Internet Explorer 4, родился innerHTML — быстрый способ создания и изменения контента. Этот способ прочитать содержание элементов много проще чем рекомендованный W3C, главным образом, в случае, если элемент содержит дочерние узлы, которые сами элементы, а нам надо прочитать содержание целиком. Чтобы сделать это с помощью одного только DOM, вам придется пройти сквозь мучительные испытания — проверять тип и читать значения каждого узла.

Свойство innerHTML много проще использовать, но у него есть несколько недостатков. Например, вы не можете получить обратно ссылки на элементы, созданные с его помощью, так как итоговое значение скорее строка, чем объект. Кроме того, innerHTML поддерживается только HTML, но не XML, в отличие от DOM, которая обеспечивает переносимость на любую разметку. Просмотрите раздел DOM в Quirksmode.org или всестороннее исследование в Developer-x, чтобы сравнить поддержку DOM разными браузерами.

Читать дальше