Начало · Справочники · Курсы · Разговоры

leechy.ru · Сайт почти придуман

Текстовый roll-over

В связи с большим количеством писем на тему как можно менять содержимое ячеек у таблиц решил написать эту статью. В ней в основном, как проделать эту операцию найболее правильно (на мой взгляд) и так, чтобы все работало и в NN4. Далее — история создания еще одного скрипта.

Пример

Начнем с примера. Введите в строке ввода текст и нажмите кнопку... текст в зеленом(?) поле должен быть заменен.

Как все это устроено?

В MSIE4+ и NN6/Mozilla (а на самом деле в DOM level 1) есть замечательное свойство elem.innerHTML. С его помощью можно достать или изменить содержание практически любого элемента страницы, которое является HTML-кодом. Поэтому функция для этих браузеров получается довольно простая:

function replaceElemContents(elemId, contents) {
   if (dom) document.getElementById(elemId).innerHTML = contents;
   else if (ie4) document.all[elemId].innerHTML = contents;
}

Но не спешите радоваться - это был мой начальный вариант. Но начальные варианты потому и начальны, что зачастую очень сильно отличаются от финальных.

Глюк 1 — Opera 4

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

Несмотря на заявления разработчиков, оказывается, что Опера — очень ограниченный браузер (вспомните свойство display). Из-за того, что Опера положительно отзывается на запрос document.getElementById, она попадает в мое определение «DOM-совместимый браузер». Для совместимости мы переделаем нашу функцию так:

opera = (navigator.userAgent.indexOf('Opera') >= 0)? true : false;

function replaceElemContents(elemId, contents) {
   if (dom && !opera) document.getElementById(elemId).innerHTML = contents;
   else if (ie4 && !opera) document.all[elemId].innerHTML = contents;
}

Это все еще вариант 2. Но есть и другие...

Глюк 2 — ...другие DOM-совместимые браузеры

Как выяснилось, все больше из новых браузеров поддерживают document.getElementById, и все больше становиться вероятность, что какой-то скрипт, где-то не заработает. К сожалению у меня нет возможности отслеживать всех версий браузеров для всех платформ. Но не перестаю надеяться, что эти браузеры будут по-настоящему DOM-совместимые и все будет ok. И по-прежнему забочусь только об Опере. Надеюсь делаю правильно.

Насчет второго глюка — под какой-то Linux в браузере Konqueror этот скрипт подглючивает. Мне так и не стало ясно что именно глючить, как глючить, но факт, что время от времени скрипт все-таки работает, наводит меня на мысль, что глючит браузер, а не скрипт.

Глюк 3 или Большая Проблема — Netscape Navigator 4

В четвертом Netscape нет innerHTML. И не будет. И вообще нельзя менять ничего на странице после перезагрузки. Кроме слоев, конечно. Но перезагружать страницу, только чтобы поменять строчку текста — это по-моему извращение. Поэтому нужно что-то делать со слоями.

Еще один минус — слой, у которого можно менять содержимое — должен быть только абсолютно позиционированный. В принципе задача не тяжелая, но нужно помнить, что скрипт универсален, и не у всех есть возможность server-side определять браузер и выдавать/не выдавать слой. К тому-же абсолютно позиционированному слою нельзя сказать ширина = 100% от той-то ячейки той-то таблицы, поэтому нужно еще померить ширину ячейки и т. д.

Из-за всего этого, я счел что самое правильное решение — померить ширину и расположение ячейки, а потом создать динамически слой этой-же ширины и поставить его «сверху» нужного места.

Первый этап — как померить ширину?

Первая попытка была поставить относительно позиционированный слой в ячейке и разтянуть его на 100%, после чего померить его ширину и положение. Идея неплохая и была бы вообще идеальная, но... не сработало — ширина = undefined.

Поэтому пришлось остановиться на использование двух относительно позизционированных слоев, разнесенные по краям ячейки. И снова проблемы — так и не смог разнести их с помощью атрибутов align. Никак.

Так как третий вариант определение ширины у меня нет, я решил, что нужно использовать таблицу из двух ячеек. Одна с шириной 100%, а другая только со слойчиком, в котором есть один <br>. А чтобы не грузить другие браузеры этой-же таблицы, содержимое этой ячейки писать с помощью document.write.

Опять неудача. Оказывается, если пишеш код относительно позиционированного слоя с помощью document.write (например <span id=a style="position: relative">), то Netscape4 отказывается его считать за слой! Я на 98% уверен, что это у меня ошибка, а не у браузера, но был настолько разъярен, что не стал выяснять доконца. Слава богу никто не отменил тэг <ilayer> — его NN4 воспринимает всегда, а так как у меня уже есть определение браузера — я могу его использовать сколько нужно.

Итак, вот реальное содержание ячейки:

if ((dom && !opera) || (ie4 && !opera))
   document.write('<div id="chble">Подождите загрузки страницы...<\/div>');
else if (nn4) document.write('<table cellpadding=0 cellspacing=0 border=0 width=100%>' +
   '<td width=100%><ilayer id="chbleLeftAnc"><br><\/ilayer><\/td>' +
   '<td><ilayer id="chbleRightAnc"><br><\/ilayer><\/td><\/table>');
else document.write('<p>Ваш браузер не поддерживаеть динамическую замену содержимого элементов!<\/p>');

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

Второй этап — как создать слой?

Так как у меня все еще нет универсального скрипта, динамически создающий слой в NN4 (и любые другие элементы в DOM-совместимых браузерах), придется быстро сделать неуниверсальную функцию, которая будет работать в этом пока что исключительном случае.

Итак, новый слой в NN4 создается с через объект Layer, которому задается ширина. Для определении ширины, мы вычитаем из положение правого относительно позициониранного слоя — положение левого. Как это сделать мы знаем (см. ст. Где находятся элементы?).

После нам нужно поставить его в нужном месте. Для этого свойства left и top новосозданного слоя приравниваем к аналогичным значениям одного из двух вспомагательных элементов. И наконец, так как динамически созданные слои по умолчанию скрыты — мы его показываем. Вот что получилось:

function createNN4Leer(leerId, firstLeer, secondLeer) {
   if (nn4) {
      document.layers[leerId] = new Layer(document.layers[secondLeer].pageX - document.layers[firstLeer].pageX);
      document.layers[leerId].left = document.layers[firstLeer].pageX;
      document.layers[leerId].top = document.layers[secondLeer].pageY;
      document.layers[leerId].visibility = "show";
   }
}
11 
12 
13 
14 
15 
16 
17 
18 

При этом этой передаем имя самого элемента, и имена двух вспомагательных — все просто!

Теперь нужно будет в уже знакомую функцию replaceElemContents добавить новые строки:

else if (nn4) {
   if (!document.layers[elemId]) createNN4Leer(elemId, elemId+'LeftAnc', elemId+'RightAnc');
   document.layers[elemId].document.open();
   document.layers[elemId].document.write(contents);
   document.layers[elemId].document.close();
}
01 
02 
03 
04 
05 
06 

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

Раз у нас точно есть уже слой, то можно над ним уже издеваться. Вариант свойства innerHTML для NN4 — просто записать в нем с помощью document.write то, что нужно, своевременно открыв и закрыв его. Потому, что любой слой в NN4 — это самостоятельный документ, правда с некоторыми ограничениями (например нельзя сабмитить пост-форму в нем не перегружая всю страницу).

Согласен, что получилось настоящее извращение, но можно заменить этот кусок с более-приличным позже.

Все это работает. Не работает только то, что это отдельный слой и если там слишком много информации, то он может «налезть» на другой информации на странице — той, которая расположена ниже отведенного места. Поэтому нужно заранее позаботиться и поставить «распорку» нужной величины, зная какое количество информации может попасть туда.

Итоги

Так как все работает, можно считать цель достигнутой. Для копипейстеров — полный скрипт:

// 
// Copyright (c) Art. Lebedev Studio | http://www.design.ru/
// Author - Leechy | leechy@design.ru | http://www.dhtml.ru/
// 
 
opera = (navigator.userAgent.indexOf('Opera') >= 0)? true : false;
dom = (document.getElementById)? true : false;
ie4 = (document.all && !dom)? true : false;
nn4 = (document.layers)? true : false;
 
function createNN4Leer(leerId, firstLeer, secondLeer) {
   if (nn4) {
      document.layers[leerId] = new Layer(document.layers[secondLeer].pageX - document.layers[firstLeer].pageX);
      document.layers[leerId].left = document.layers[firstLeer].pageX;
      document.layers[leerId].top = document.layers[secondLeer].pageY;
      document.layers[leerId].visibility = "show";
   }
}
 
function replaceElemContents(elemId, contents) {
   if (dom && !opera) document.getElementById(elemId).innerHTML = contents;
   else if (ie4 && !opera) document.all[elemId].innerHTML = contents;
   else if (nn4) {
      if (!document.layers[elemId]) createNN4Leer(elemId, elemId+'LeftAnc', elemId+'RightAnc');
      document.layers[elemId].document.open();
      document.layers[elemId].document.write(contents);
      document.layers[elemId].document.close();
   }
}
 
function createReplacementElem(elemId, alternativeText) {
   if ((dom && !opera) || (ie4 && !opera))
      document.write('<div id="' + elemId + '">Подождите загрузки страницы...<\/div>');
   else if (nn4) document.write('<table cellpadding=0 cellspacing=0 border=0 width=100%>' +
      '<td width=100%><ilayer id="' + elemId + 'LeftAnc"><br><\/ilayer><\/td>' +
      '<td align=right><ilayer id="' + elemId + 'RightAnc"><br><\/ilayer><\/td><\/table>');
   else if (alternativeText) document.write(alternativeText);
   else document.write('Ваш браузер не поддерживаеть динамическую замену содержимого элементов!');
}
01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
34 
35 
36 
37 
38 
39 

Я делал этот скрипт для Студии Артемия Лебедева, поэтому в коде стоит соответствующий копирайт. Если будете его использовать — не удаляйте его.

Функция createReplacementElem() — это кусок кода, который раньше вставлял в самом HTML. Он создает необходимые элементы, а если браузер не поддерживает замену — пишет об этом. Примерный код в HTML должен выглядеть так:

<script language="JavaScript">
createReplacementElem('chble');
</script>
<noscript>
Выполнение скриптов в вашем браузере выключено, поэтому вы не сможете увидеть динамическую замену содержимого элементов!
</noscript>

После этого нужно только вызвать replaceElemContents('chble', 'желаемый текст'), где 'желаемый текст' — новое содержимое елемента и текст поменяется.

В итоге сам скрипт весит 1667 байт — я даже немного горжусь собой.

Если есть какие-то вопросы или предложения — к вашим услугам наш форум «dHTML Разговоры»