В связи с большим количеством писем на тему как можно менять содержимое ячеек у таблиц решил написать эту статью. В ней в основном, как проделать эту операцию найболее правильно (на мой взгляд) и так, чтобы все работало и в 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, или по крайней мере я не нашел его. Если такой способ известен - напишите мне по адресу leechy@dhtml.ru, плз.
Несмотря на заявления разработчиков, оказывается, что Опера - очень ограниченный браузер (вспомните свойство 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 воспринимает всегда, а так как у меня уже есть определение браузера - я могу его использовать сколько нужно.
Даже вылезла возможность показать ущербным браузерам, что они ущербные. Возникает мысль, что раз пишу код таким образом, значить можно и не создавать слой динамически, но ради спортивного интереса создам.
Второй этап - как создать слой?
Так как у меня все еще нет универсального скрипта, динамически создающий слой в 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";
}}
При этом этой передаем имя самого элемента, и имена двух вспомагательных - все просто!
Теперь нужно будет в уже знакомую функцию 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();
}
Что они делают... Во-первых, чтобы не создавать слой сразу после загрузки страницы (может получится так, что он вообще не понадобиться), мы его создаем только когда он реально нужен - перед первой замены и то только если его нет.
Раз у нас точно есть уже слой, то можно над ним уже издеваться. Вариант свойства innerHTML для NN4 - просто записать в нем с помощью document.write то, что нужно, своевременно открыв и закрыв его. Потому, что любой слой в NN4 - это самостоятельный документ, правда с некоторыми ограничениями (например нельзя сабмитить пост-форму в нем не перегружая всю страницу).
Согласен, что получилось настоящее извращение, но я заменю этот кусок с более-приличным, когда будет готов скрипт создающий слоев. Обещаю.
Все это работает. Не работает только то, что это отдельный слой и если там слишком много информации, то он может "налезть" на другой информации на странице - той, которая расположена ниже отведенного места. Поэтому нужно заранее позаботиться и поставить "распорку" нужной величины, зная какое количество информации может попасть туда.
Итоги
Так как все работает, можно считать цель достигнутой. Для копипейстеров - полный скрипт:
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('Ваш браузер не поддерживаеть динамическую замену содержимого элементов!');
}
//--></script>
Я делал этот скрипт для Студии Артемия Лебедева, поэтому в коде стоит соответствующий копирайт. Если будете его использовать - не удаляйте его!
Функция createReplacementElem() - это кусок кода, который раньше вставлял в самом HTML. Он создает необходимые элементы, а если браузер не поддерживает замену - пишет об этом. Примерный код в HTML должен выглядеть так:
<script language=JavaScript>>!--
createReplacementElem('chble');
// --></script>
<noscript>
Выполнение скриптов в вашем браузере выключено, поэтому вы не сможете увидеть динамическую замену содержимого элементов!
</noscript>
После этого нужно только вызвать replaceElemContents('chble', 'желаемый текст'), где 'желаемый текст' - новое содержимое елемента и текст поменяется.
В итоге сам скрипт весит 1667 байт - я даже немного горжусь собой.
Если есть какие-то вопросы или предложения - к вашим услугам наш форум «dHTML Разговоры»