Ненавязчивый JavaScript. Глава 2: Как достичь того, что мы хотим изменить

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

Для неопытных Javascript-разработчиков HTML — это площадка для игр.

HTML:

<a href="index.html"
onmouseover="image1.src='1on.gif'"
onmouseout="image1.src='1off.gif'">
<img src="1off.gif" name="image1" border="0" height="150" width="150"
alt="home"></a>

или если они чуть более продвинутые:

HTML:

<a href="index.html"
onmouseover="roll('home',1)"
onmouseout="roll('home',0)">
<img src="home.gif" name="home" border="0"
height="150" width="150"
alt="home"></a> 
	
Javascript:

// предзагрузка изображений
homeoff = new Image();
homeoff.src = 'home.gif';
homeon = new Image();
homeon.src = 'homeoff.gif';
function roll(imgName,a)
{
 imgState=a==0?eval(imgName + 'on.src'):eval(imgName + 'off.src');
 document.images[imgName].src = imgState;
}

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

Изыди! Изыди! — вы одержимы встроенными в документ вызовами событий.

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

HTML:

<a href="index.html"><img src="home.gif" id="home" alt="home"></a>

Итак, как сменить изображение, когда на него наводиться курсор мыши?

Карабкаемся по ветвям дерева узлов (node tree)

Каждый XML- (а также и HTML-) документ — это дерево узлов. Узел — часть этого дерева (представляйте файл или папку в проводнике Windows, когда вы просматриваете ваш диск). Узел может быть двенадцатью разными вещами, в HTML только три из них действительно интересны: element, TextNode и AttributeNode.

Наше альпинистское снаряжение.

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

Функции для достижения элемента страницы

getElementById('elementID')
возвращает элемент с id elementID в виде объекта.
getElementsByTagName('tag')
возвращает все элементы с именем tag в виде массива.

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

document.getElementById('navigation').getElementsByTagName('a')[3];
//	возвращает четвёртую ссылку внутри элемента,
//	который имеет ID 'navigation'

document.getElementsByTagName('div')[2].getElementsByTagName('p')[0];
//	возвращает первый параграф внутри третьего div в документе.

Инструменты, чтобы переместиться к конкретному элементу

childNodes
возвращает массив узлов, лежащих внутри текущего. Существуют также firstChild и lastChild, это сокращенные варианты childNodes[0] и childNodes[this.childNodes.length-1].
parentNode
элемент, содержащий данный
nextSibling
следующий элемент того же уровня в дереве документа
previousSibling
предыдущий элемент того же уровня в дереве документа

Все это по желанию может быть смешано так, как нам нужно.

Javascript:

var other=document.getElementById('nav').childNodes[3].firstChild;
//	возвращает четвёртый элемент первого подэлемента
//	внутри элемента с ID=nav

var prevlink=o.parentNode.previousSibling.firstChild.childnodes[2];
//	возвращает третий узел внутри предыдущего элемента,
//	того же уровня, что и родительский элемент элемента о.

Атрибуты и функции элементов

attributes
возвращает массив атрибутов этого элемента. Не работает с IE версией менее 6.
data
возвращает или устанавливает текстовые данные узла
nodeName
возвращает имя узла (имя HTML элемента)
nodeType
возвращает тип узла: 1, если элемент — это узел, 2 — если атрибут, 3 — если текст.
nodeValue
возвращает или устанавливает значение узла. Это значение является текстом, если узел — текстовый, атрибутом, если это узел-атрибут, или null, если это элемент.
getAttribute (attribute)
возвращает значение атрибута attribute.
Javascript:

var other=document.getElementById('nav').firstChild;
if(other.nodeType==3)
{
 other.data='newtext';
}
if(other.nodeType==1)
{
 other.firstChild.data='newtext';
}

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

HTML:

<a href="index.html"><img src="home.gif" id="home" alt="home"></a>

Javascript:

function findimg()
{
 var image;
 image=document.getElementById('home');
 if (image)
  {
   image.style.border='3px dashed #ccc';
  }
}

//или:

function findimg()
{
 var imgs,i;
 imgs=document.getElementsByTagName('img');
 for(i in imgs)
 {
  if(/home.gif/.test(imgs[i].src))
   {
    imgs[i].style.border='3px dashed #ccc';
   }
  }
}

Использовать getElementById гораздо проще, так как нам не нужно пробегать все элементы и искать уникальный идентификатор. В этом примере мы проверяем, содержит ли атрибут src объекта изображения значение 'home.gif'. Более общий способ — проверить назначен ли элементу специальный класс.

HTML:

<a href="index.html"><img src="home.gif" class="roll" alt="home"></a>

Javascript:

function findimg()
{
 var imgs,i;
 imgs=document.getElementsByTagName('img');
 for(i in imgs)
  {
  if(/roll/.test(imgs[i].className))
   {
    imgs[i].style.border='3px dashed #ccc';
   }
  }
}

Итак, чтобы добавить эффект ролловера, все что нам надо сделать — это добавить функцию, которая переключает источник изображения и прикрепляет обработчик события к картинке.

function findimg()
{
 var imgs,i;
// перебираем все изображения в документе
 imgs=document.getElementsByTagName('img');
 for(i=0;i<imgs.length;i++)
 {
// проверяем существует ли класс 'roll'
  if(/roll/.test(imgs[i].className))
  {
// добавляем функцию roll на события onmouseover и onmouseout,
//затем посылаем само изображение как объект
   imgs[i].onmouseover=function(){roll(this);};
   imgs[i].onmouseout=function(){roll(this);};
  }
 }
}

function roll(o)
{
 var src,ftype,newsrc;
// послать src изображения и найти расширение файла
 src = o.src;
 ftype = src.substring(src.lastIndexOf('.'), src.length);
// проверить содержит ли источник изображения строку _on и,
// если да - удалить её
 if(/_on/.test(src))
 {
  newsrc = src.replace('_on','');
 }else{
// в противном случае добавить  _on к src
  newsrc = src.replace(ftype, '_on'+ftype);
 }
 o.src=newsrc;
}
window.onload=function(){
 findimg();
}

Здесь можно проверить этот пример

Вроде бы все хорошо, но мы забыли одну вещь: хотя то, что получилось, просто замечательно, ролловер должен работать и без мыши. Чтобы достигнуть этого, мы должны проверить, находиться ли фокус на ссылке вокруг изображения, так как сама картинка, если на неё поставлена ссылка, недостижима с клавиатуры.

Чтобы сделать проверку, нам надо получить элемент, который содержит изображение, в данном случае — ссылку. Мы сделаем это с помощью команды parentNode. Так как это заодно изменяет объект, которые передан как параметр функции roll(), нам надо будет найти изображение снова. Поэтому мы перебираем дочерние узлы ссылок и смотрим, который из них одновременно и элемент, и изображение, проверив nodeType and nodeName. Это необходимо, так как некоторые браузеры рассматривают пробелы в источнике изображения как узел, другие же нет.

function findimg()
{
 var imgs,i;
// перебираем все изображения, проверяем содержат ли они класс roll
 imgs=document.getElementsByTagName('img');
 for(i=0;i<imgs.length;i++)
 {
  if(/roll/.test(imgs[i].className))
  {
// добавляем функцию roll к родительскому элементу изображения
  imgs[i].parentNode.onmouseover=function(){roll(this);};
  imgs[i].parentNode.onmouseout=function(){roll(this);};
  imgs[i].parentNode.onfocus=function(){roll(this);};
  imgs[i].parentNode.onblur=function(){roll(this);};
  }
 }
}

function roll(o)
{
 var i,isnode,src,ftype,newsrc,nownode;
// перебираем все дочерние узлы
 for (i=0;i<o.childNodes.length;i++)
 {
  nownode=o.childNodes[i];
// если узел это и элемент, и IMG,
// то установить значение переменной и выйти из цикла
  if(nownode.nodeType==1 && /img/i.test(nownode.nodeName))
  {
   isnode=i;
   break;
  }
 }
// проверить src и сделать ролловер
 src = o.childNodes[isnode].src;
 ftype = src.substring(src.lastIndexOf('.'), src.length);
 if(/_on/.test(src))
 {
  newsrc = src.replace('_on','');
 }else{
  newsrc = src.replace(ftype, '_on'+ftype);
 }
 o.childNodes[isnode].src=newsrc;
}
	
window.onload=function(){
 findimg();
}

Проверьте этот независимый от наличия мыши код

Почему бы вам не попробовать?

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

  1. Поменяйте цвет каждого H2 на голубой. Решение для смены цвета.
  2. Обведите каждый второй параграф чёрной рамкой. Решение для обведённых параграфов
  3. Проверьте каждую ссылку в документе, является ли она внешней (проверяя, что атрибут href не содержит window.location.hostname) и добавьте href каждой ссылки в круглых скобках после ссылки. Пока вы ещё только изучаете, как это сделать правильно, используйте атрибут innerHTML объекта ссылки, чтобы изменить его содержание. Решение для внешних ссылок.
  4. Добавьте обработчик события onclick, который открывает всплывающее окно 400x400, к каждой ссылке с атрибутом target. Решение для всплывающих окон

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