Текст ниже — перевод статьи "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()trueinsertBefore(newchild,oldchild)newchild перед узлом oldchild в дереве документа.removeChild(oldchild)oldchild.replaceChild(newchild,oldchild)newchild на место узла oldchildremoveAttribute(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 доступны, мы хотим:
Эта функция должна:
Первую задачу решить нетрудно:
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 — нет.getElementsById может послужить причиной частого переписывания скрипта (rescripting).className содержит вашу строку, а не то, что он ей равен, так как некоторым программистам нравиться применять несколько классов к одному элементу.Когда появился Internet Explorer 4, родился innerHTML — быстрый способ создания и изменения контента. Этот способ прочитать содержание элементов много проще чем рекомендованный W3C, главным образом, в случае, если элемент содержит дочерние узлы, которые сами элементы, а нам надо прочитать содержание целиком. Чтобы сделать это с помощью одного только DOM, вам придется пройти сквозь мучительные испытания — проверять тип и читать значения каждого узла.
Свойство innerHTML много проще использовать, но у него есть несколько недостатков. Например, вы не можете получить обратно ссылки на элементы, созданные с его помощью, так как итоговое значение скорее строка, чем объект. Кроме того, innerHTML поддерживается только HTML, но не XML, в отличие от DOM, которая обеспечивает переносимость на любую разметку. Просмотрите раздел DOM в Quirksmode.org или всестороннее исследование в Developer-x, чтобы сравнить поддержку DOM разными браузерами.