Скальпель, сестра!

11 октября 2008 года, 12:38

Как бы рутинно не выглядел процесс разработки Web-документов, он всё равно является творческим: нет ничего интереснее, чем придумывать формы представления информации, упрощать процесс взаимодействия пользователя с ней, склонять читателей к чтению текстов, а писателей — к их немедленному написанию. Пожалуй, это и есть Web как среда обмена информацией. А что если нам, как инженерам, разделить на низком уровне слои представления информации? Такой хитрый ход позволит обеспечить лёгкую модификацию ранее разработанных документов, не прибегая к прямому вмешательству в организацию документа: всё будет проходить достаточно прозрачно и просто.

Давайте попробуем сегодня достичь подобного результата. Думаю, что у нас всё получится (впрочем, как всегда).

Теоретические изыскания

Как же можно облегчить жизнь тому, кто хочет каким-либо образом изменить, дополнить или перекроить Web-документы? Можно попробовать разделить документ на слои, чтобы легче было работать с каждым из них. Чтобы легче было понять смысл слоёных документов, представим себе следующую картину: у нас есть документ, содержащий в себе заголовок, основную часть и подвал. Мы хотим добавить к заголовку ещё и подзаголовок, но у нас сложилась такая ситуация, что мы не можем напрямую изменить содержимое документа (например, мы пишем расширение Firefox, или скрипт Greasemonkey и подобные) или нам нужно ненавязчиво расширить функциональность какого-то блока нашего документа (например, в экспериментальных целях). В обычной ситуации, мы бы перебирали всё DOM-дерево, чтобы добраться до нужного нам узла, после этого использовали ещё дюжину DOM-функций для дополнения искомого элемента до нужного нам представления. Согласитесь, что выполнять такое каждый раз — достаточно утомительное занятие. Как же избежать подобного?

Сделаем так: на основной странице ключевые элементы обычно всегда определяются по атрибуту id. Этим мы и воспользуемся: будем через AJAX подгружать нужный нам XML-файл, в котором будет описание слоя-настилки на наш документ. Посмотрим пример такого слоя:

<?xml version="1.0" encoding="utf-8" ?> <collection> <overlay name="Header overlaying" id="header" insert="end" type="plain"> <![CDATA[<h2>Subtitle</h2>]]> </overlay> </collection>

Данный слой накладывается на элемент с атрибутом id, равным header после всех существующих там элементов (атрибут insert со значением end). Само содержимое нового слоя содержится в специальном элементе CDATA, чтобы парсер не воспринимал всё содержимое элемента как XML, а считал его обычным текстом (при этом обязательно использование атрибута type со значением plain). С другой стороны, мы можем записывать содержимое слоя-настилки в виде XML, но тогда потребуется дополнительная процедура обработки содержимого слоя-настилки для превращения всех XML-элементов в XHTML-элементы.

Посмотрим на наш основной документ:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html lang="ru" xml:lang="ru" xmlns="http://www.w3.org/1999/xhtml"> <head> <title>JsOverlay</title> </head> <body> <div id="header"> <h1>Page title</h1> </div> <div id="content"> <p> This is page content </p> </div> <div id="footer"> <p> Footer, footer </p> </div> </body> </html>

Всё становится предельно понятно: наш слой при наложении на документ образует симбиоз элементов, изменяя структуру документа до нужной нам:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html lang="ru" xml:lang="ru" xmlns="http://www.w3.org/1999/xhtml"> <head> <title>JsOverlay</title> </head> <body> <div id="header"> <h1>Page title</h1> <!-- Вот он, наш добавленный элемент --> <h2>Subtitle</h2> </div> <div id="content"> <p> This is page content </p> </div> <div id="footer"> <p> Footer, footer </p> </div> </body> </html>

Маленькая деталь: предыдущий пример немного лукавит, так как перед тем, как наложить слой на документ, он оборачивается в элемент div с установленным атрибутом id для того, чтобы позже можно было бы проводить операции с данным слоем (удалять, например).

Что-ж, перейдём к практической части нашего повествования.

Практические изыскания

Сделаем для нашей теории небольшую практическую реализацию в качестве примера. Назовём наш класс-синглтон JSOverlay и создадим для него отдельное пространство имён:

var JSOverlay = {

Далее, нам понадобится ассоциативный массив для хранения загруженных слоёв (чтобы их можно было контролировать после загрузки):

Overlays : {},

Мы уже сейчас можем реализовать метод для получения слоя (или слоёв, так как одному элементу может быть назначено несколько слоёв):

GetOverlayById : function(overlayid) { if (JSOverlay.overlays[overlayid] != null) { //Возвращаем всё, что найдено return JSOverlay.overlays[overlayid]; } else { //Ничего не найдено return false; } },

Опишем класс слоя, чтобы было легче управлять столь одинаковыми сущностями:

Overlay : function() {

Нам понадобится структура информации о слое, которая должна состоять из имени слоя, его типа и позиции. Создадим её:

this.meta = { "name" : null, "insert" : "end", "type" : null };

Чтобы не разбрасывать ассоциированные элементы, заключим их также в одну структуру:

this.elements = { //Контейнер слоя "container" : null, //Содержимое слоя (CDATA или чистый XML) "content" : null, //Элемент, который мы дополняем (исходный элемент) "element" : null };

Вслед за этим, реализовываем методы, необходимые для утилизации возможностей слоя:

this.attach = function() { //Создаём контейнер this.elements.container = document.createElement("div"); //Атрибут id контейнера равен jsoverlay-<id-исходного-элемента> this.elements.container.id = "jsoverlay" + ((this.elements.element.id != "") ? "-" + this.elements.element.id : "-body-element"); //Заполняем его содержимое if (this.meta.type == "plain") { for (var i = 0; i < this.elements.content.childNodes.length; i++) { if (this.elements.content.childNodes[i].nodeType == 4) this.elements.container.innerHTML = this.elements.content.childNodes[i].nodeValue; } } else if (this.meta.type="xml"> { //Если необходимо, это также можно реализовать } //Прибавляем контейнер в нужное местоположение switch (this.meta.insert) { case "end": this.elements.element.appendChild(this.elements.container); break; case "start": this.elements.element.insertBefore(this.elements.container, this.elements.element.firstChild); break; } }

И ещё один метод, для отделения нового слоя от исходного элемента:

this.remove = function() { //Удаляем грязно this.elements.element.removeChild(this.elements.container); //Обнуляем контейнер this.elements.container = null; }

Заметьте, что мы не удаляем весь слой, а только то, что он внедряет на страницу. Осталось только завершить наш класс:

},

Двигаемся дальше. Теперь нам нужен метод уже пространства имён JSOverlay для загрузки нашего слоя. По сути, мы разделим данный метод на два метода: собственно загрузка и обработка XML-данных вместе с созданием, добавлением и активацией слоя. Начнём с загрузки через XMLHttpRequest (нет ничего проще):

LoadOverlay : function(overlayfile) { var Request = false; if (window.XMLHttpRequest) { Request = new XMLHttpRequest(); } else if (window.ActiveXObject) { try { Request = new ActiveXObject("Microsoft.XMLHTTP"); } catch (Exp) { try { Request = new ActiveXObject("Msxml2.XMLHTTP"); } catch (ExpCascade) { //Очень жаль Request = false; } } } //Что?! Браузер не поддерживает XMLHttpRequest? С ума сойти! if (!Request) return false; //Создаём GET-запрос к XML-файлу с нашим слоем Request.open("GET", overlayfile); Request.onreadystatechange = function() { //Вроде всё хорошо if (Request.readyState == 4) { //Ещё раз убеждаемся, что всё действительно хорошо if (Request.status != 200 && Request.status != 304) { //Заберите меня отсюда! return; } //Вызываем обработчик XML-данных JSOverlay.ParseOverlay(Request.responseXML); } } //Отсылаем запрос Request.send(null); },

Понятно, что с помощью вышеописанного метода мы сможем загружать слои как JSOverlay.LoadOverlay("overlay/my.xml");. Естественно, путь, который загружается с помощью данного метода, должен существовать, иметь Content-Type text/xml (или один из подобных) и содержать в себе валидный и соответствующий XML.

Мы не реализовали последний метод, сделаем же это:

ParseOverlay : function(XmlOverlayDocument) { try { //Получаем набор слоёв var XmlOverlays = XmlOverlayDocument.getElementsByTagName("overlay"); //Один файл может содержать несколько слоёв for (var i = 0; i < XmlOverlays.length; i++) { //Текущий слой var XmlCurrentOverlay = XmlOverlays[i]; //Создаём новый экземпляр класса var Overlay = new JSOverlay.Overlay(); //Метаинформация Overlay.meta.name = XmlCurrentOverlay.getAttribute("name"); Overlay.meta.insert = XmlCurrentOverlay.getAttribute("insert"); Overlay.meta.type = XmlCurrentOverlay.getAttribute("type"); //Получаем id исходного элемента var element_id = XmlCurrentOverlay.getAttribute("id"); //Типичные ситуации switch (element_id) { //Нужно присоединить к body case "body": element_id = document.body; break; default: element_id = document.getElementById(element_id); break; } if (element_id != null) { //Устанавливаем исходный элемент Overlay.elements.element = element_id; //Устанавливаем содержимое Overlay.elements.content = XmlCurrentOverlay; //Подключаем Overlay.attach(); if (JSOverlay.overlays[element_id.id] == null) { JSOverlay.overlays[element_id.id] = []; } //Добавляем новый слой в массив слоёв для данного элемента JSOverlay.overlays[element_id.id].push(Overlay); } else { continue; } } } catch (excp) { return false; } }

Готово! Всё, что получилось, можно посмотреть на странице-примере, открыв её исходные коды.

Что можно добавить?

Чтобы облегчить себе жизнь в некоторых ситуациях, можно превратить overlay.js в какой-нибудь серверный скрипт, который будет генерировать JavaScript-код для подключения разных слоёв. К примеру, можно сделать так: при обращении к /overlay/add/ генерируется JavaScript для подключения слоя с именем .

Выводы

Представленный прототип уже сейчас может выполнять возложенные на него обязанности, но его можно расширять до бесконечности: поддержка стилей, встроенных в расширение скриптов (хотя, этого можно достичь, просто прописав в нём элемент script, который будет подключать внешний файл с кодом), дерева DOM (для поиска элементов не только по id, но и по другим отличительным чертам, например, по атрибуту class или и вовсе по произвольному атрибуту элементов). Можно даже периодически заглядывать на вышеуказанную страницу с примером, так как иногда там будет появляться новая версия небольшого скрипта.

Как всегда, в конце записи желаю моим читателям хорошего настроения на все выходные.

Мнения (3)

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

Я тоже знаю!

Вы можете тоже написать собственный комментарий. Если хотите к кому-то обратиться, используйте символ @, после которого не забудьте написать имя того, к кому обращаетесь. Не забывайте про существование XHTML-элементов, с помощью которых вы можете оформить ваш комментарий как вам угодно. И, да: ведите себя достойно, вы же не роботы, правда? Если вам интересно, можете подписаться на комментарии по RSS.