Текст ниже — перевод статьи "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 доступны, мы хотим:
Эта функция должна:
Первую задачу решить нетрудно:
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 разными браузерами.