| Сегодня Суббота 27.04.2024

Получение DOM-объекта из XML (AJAX) и перевод в HTML посредством JS


Итак, название данной статьи говорит само за себя. Задача заключается в следующем:

  • Требуется построить «дерево», на основе файла data.xml, где «Item» — узел дерева;

  • Данные необходимо получать получать при помощи AJAX;

  • Должна быть возможность сворачивать/показывать узлы дерева при клике на –/+;

  • При клике на узел, должен появляться небольшой блок с содержимым «Description», следующий клик по странице должен скрывать этот блок.


Недолго обдумав пути решения проблемы, первое что приходит на ум: получаем содержимое XML-файла методом GET при помощи объекта XMLHttpRequest, тем самым получаем объект DOM XML-файла, доступ к которому будет через свойство «responseXML».
После этого, мы можем «общаться» с загруженным XML-документом используя все возможности DOM в Javascript. Также стоит отметить одну проблему в Internet Explorer (6,7?) по этому поводу: чтобы объект XMLHttpRequest (ActiveXObject('MSXML2.XMLHTTP.3.0')) получил корректный DOM-объект XML-файла, нужно запускать его через веб-сервер (т.е по адресу, например, http://myhost/file.html, а не file://localhost/c:/file.html — как при открытии файла, посредством файловой системы). Про работу с аяксом я уже писал вот в этой статье.

Получив необходимый DOM-объект XML-файла, дальше мы вправе делать с ним что угодно. Я решил написать рекурсивную функцию для парсинга всего дерева документа и перевода его в HTML-формат (т.к простая вставка XML-тегов в HTML-документ была бы не корректной и отображалась бы везде по-разному, независимо от того, какие CSS-стили применены к XML-тегам).
Алгоритм был следующий: рекурсивно пройтись по каждому элементу XML-дерева, и вставить его содержимое в указанный HTML-тег. Для дополнительного удобства обработки дерева, вторым аргументом я передаю JSON-объект, который содержит информацию о том, на какие HTML-теги делать замену, какие аттрибуты XML-тегов, заменять на соответствующие атрибуты HTML-тегов. Также имеется возможность добавления дополнительных аттрибутов (например «class» для CSS).

Главным «подводным камнем», с которым я столкнулся при написании рекурсивной функции на JS был тот факт, что если мы объявляем новую переменную внутри функции без использования ключевого слова «var», она добавляется в глобальную область видимости, происходит «замыкание», и в итоге каша, бесконечные циклы и не работающий скрипт. При объявлении переменных, перед которыми стоит ключевое слово «var» — они будут доступны только в той области, в которой они были объявлены.

Также, для более удобного использования скрипта, все 3 функции (получение файла аяксом, парсинг XML DOM-дерева, обработка полученного HTML-дерева), я собрал в один объект в глобальной области видимости. Что получилось в результате смотрим ниже:

Файл «xmltools.js»:
MyTools = 
{
'ajax': function()
{
var form, handler, method, xhr, data;

form = arguments[0];
handler = arguments[1];
method = (form.method || arguments[2] || 'GET').toUpperCase();

try {
// create
xhr = window.XMLHttpRequest ? new XMLHttpRequest() : ( window.ActiveXObject ? new ActiveXObject('MSXML2.XMLHTTP.3.0') : null );

// prepare
datasend = '';
data = form.elements;
for(i = 0; i < data.length; i++) datasend += (data[i].name +'='+ encodeURIComponent( data[i].value ) + '&');

// processing
request = form.action;
xhr.open( method, request + ( method == 'GET' ? (request.indexOf('?') == -1 ? '?' : '&')+ Math.random()*10E25 : '' ), true);
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.onreadystatechange = function() { if(xhr.readyState == 4 && handler != null) handler( xhr ) }
xhr.send( datasend );
}
catch(e) { alert( e.message ) }
},

'parseXMLTree': function( node, replacement )
{
var placeholder = node.hasChildNodes() ? document.createElement('span') : document.createTextNode('');
var container;
var childs, child;
var nodeName, nodeText, nodeAttr, tagName, tagAttr = {}, tagAttrAddon = {};

childs = node.childNodes;

for( var i = 0; i < childs.length; i++ )
{
child = childs[i];
nodeName = child.nodeName;
nodeAttr = child.attributes;
nodeText = child.textContent || child.text;
tagName = replacement[ nodeName ]

if( typeof tagName == 'object' )
{
tagAttr = tagName[1];
tagAttrAddon = tagName[2] || tagAttrAddon;
tagName = tagName[0];
}

if( nodeName == '#text' ) container = document.createTextNode(nodeText);
else {
container = document.createElement( tagName ? tagName : nodeName );

// sets xml-attributes
for( var a = 0; a < nodeAttr.length; a++ )
{
var attr = nodeAttr[a];
var attrName = tagAttr[ attr.nodeName ] || attr.nodeName;
container.setAttribute( attrName, attr.nodeValue );
}
// sets addon attributes
for( var b in tagAttrAddon) if( typeof tagAttrAddon[b] == 'string' ) container.setAttribute( b, tagAttrAddon[b] );

container.appendChild( arguments.callee(child, replacement) );
}

placeholder.appendChild( container );
}

return placeholder;
},

'getXMLTree': function( obj, container, getsource )
{
if( !obj.parsed )
{
var myself = this;
var container = typeof container == 'object' ? container : document.getElementById( container );
obj.innerHTML = 'Загрузка файла..';

this.ajax({'action': obj.href, 'elements': []},
function( xhr )
{
// получаем распарсенный в HTML DOM-объект XML-файла
var tree = myself.parseXMLTree( xhr.responseXML.getElementsByTagName('Items')[0], {
'Item' : ['dl', {'Id': 'title'}, {'class': 'item'}],
'Name' : 'dt',
'Description' : 'dd',
'Childs' : ['div', {}, {'class': 'subtree'}]
});

var items = tree.getElementsByTagName('dl');
var descs = tree.getElementsByTagName('dd');
var clear = function(){ for( var i = 0; i < descs.length; i++ ) if( descs[i].nodeName ) descs[i].style.display = 'none' }

// назначаем обработчики событий для элентов дерева
for( i = 0; i < items.length; i++ )
{
var child = items[i];
var childs = child.getElementsByTagName('div')[0];

child.getElementsByTagName('dt')[0].onclick = function( evt )
{
evt = evt || window.event;
evt.cancelBubble = true;
clear();
this.parentNode.getElementsByTagName('dd')[0].style.display = 'block'
}

var opener = document.createElement('i');
opener.innerHTML = childs.innerHTML ? '+' : '';
opener.className = childs.innerHTML ? 'opener' : 'disabled';
opener.onclick = function()
{
if( this.className == 'opener' )
{
var hidden = this.parentNode.getElementsByTagName('div')[0];
this.innerHTML = this.innerHTML == '+' ? '–' : '+';
hidden.style.display = !hidden.style.display || hidden.style.display == 'none' ? 'block' : 'none';
}
}
child.insertBefore( opener, child.firstChild );

}

// результирующая вставка обработанного «дерева» на страницу
container.appendChild( tree );
window.onclick = clear;

obj.innerHTML = 'XML-файл успешно загружен';
obj.className = 'disabled';
obj.parsed = true;

// просмотр полученного HTML-кода из XML-файла
document.getElementById( getsource ).innerHTML =
tree.innerHTML.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/\n/g, '<br>')
.replace(/\t/g, '&nbsp;&nbsp;');
}
);

}

return false;
}
}


Стилевое оформление в CSS, под указанные HTML-теги и классы для полученного дерева, получилось таким:

Файл «style.css»:
#tree { padding:10px 0; width:50%; }
#tree .item { position:relative; display:block; float:left; clear:left; margin:5px; }
#tree .item dt { float:left; color:gray; border-bottom:1px dashed gray; cursor:pointer; }
#tree .item dt:hover { color:black; }
#tree .item dd { background:white; border:1px solid silver; padding:10px; width:250px; position:absolute; top:0; right:-290px; display:none; z-index:10; }
#tree .item .subtree { display:none; padding-left:30px; }
#tree .item .opener,
#tree .item .disabled { display:block; float:left; width:20px; height:20px; text-align:center; line-height:20px; cursor:pointer; margin-right:2px; }
#tree .item .disabled { background:none; cursor:default; }
#tree .item .opener:hover { color:green; background:#eee; }


Единственный косяк, который мне пока не удалось побороть, как обычно выдал это сраный наш «всеми любимый» IE 6 (возможно и 7). После получения и обработки документа, вставка ноды (appendChild) происходит без влияния указанных в CSS-стилей. И вот хоть убей его.
А если производить вставку распарсенного HTML-дерева через свойство «innerHTML», то CSS-стили применяются, но т.к мы делаем вставку копии текста ноды, а не её саму, то все обработчики событий в JS, которые были указаны в скрипте, конечно же не работают. Если кто разберется в этой проблеме, буду благодарен, если вы отпишите по этому поводу в комментах к статье.
Категория: Разное | Добавил: Admin (10.10.2010)
Просмотров: 4146 | Рейтинг: 1.0/1
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]