Эта статья показывает, как написать простое и удобное дерево с AJAX-подгрузкой узлов.
Она основана на материалах грамотное javascript-дерево и интеграция AJAX в интерфейс.
Добавить AJAX-подгрузку, используя эти две технологии - очень просто. Вообще, это может послужить основой для того, чтобы создавать любые AJAX-виджеты и сложные системы интерфейсов.
Для начала оформим дерево в виде компоненты интерфейса. Инициализацию будет осуществлять функция tree, которая получает два параметра:
Код ниже полностью описывает дерево, за исключением функции AJAX-подгрузки узлов load, которая будет разобрана ниже. Он полностью соответствует оригинальной статье, только чуть реорганизован.
Все, что он делает - это отслеживает клик на элементе element.onclick, содержащем дерево, получает узел по event.target и, если узел не содержит элементов и еще не было попыток загрузки - то получает его с сервера вызовом load. Саму функцию загрузки load разберем дальше в статье.
Функции hasClass и toggleNode - вспомогательные, для индикации скрытия-открытия ветки дерева через CSS-класс.
function tree(id, url) {
var element = document.getElementById(id)
/* вспомогательная функция */
function hasClass(elem, className) {
return new RegExp("(^|\\s)"+className+"(\\s|$)").test(elem.className)
}
function toggleNode(node) {
// определить новый класс для узла
var newClass = hasClass(node, 'ExpandOpen') ? 'ExpandClosed' : 'ExpandOpen'
// заменить текущий класс на newClass
// регексп находит отдельно стоящий open|close и меняет на newClass
var re = /(^|\s)(ExpandOpen|ExpandClosed)(\s|$)/
node.className = node.className.replace(re, '$1'+newClass+'$3')
}
function load(node) {/* ... загрузить узел с сервера, код далее ... */}
element.onclick = function(event) {
event = event || window.event
var clickedElem = event.target || event.srcElement
if (!hasClass(clickedElem, 'Expand')) {
return // клик не там
}
// Node, на который кликнули
var node = clickedElem.parentNode
if (hasClass(node, 'ExpandLeaf')) {
return // клик на листе
}
if (node.isLoaded || node.getElementsByTagName('LI').length) {
// Узел уже загружен через AJAX(возможно он пуст)
toggleNode(node)
return
}
if (node.getElementsByTagName('LI').length) {
// Узел не был загружен при помощи AJAX, но у него почему-то есть потомки
// Например, эти узлы были в DOM дерева до вызова tree()
// Как правило, это "структурные" узлы
// ничего подгружать не надо
toggleNode(node)
return
}
// загрузить узел
load(node)
}
}
Чтобы инициализовать дерево - достаточно запустить функцию tree на DOM-контейнере для дерева.
Вспомним, что согласно структуре дерева - для этого служит элемент UL.
Можно взять дерево с уже готовыми узлами, а можно и пустое дерево с единственным корневым узлом "Каталог", вот такого вида (просто картинка, рабочий вариант далее):
HTML-код:
Наше дерево:
<ul class="Container" id="tree">
<li class="Node IsRoot IsLast ExpandClosed">
<div class="Expand"></div>
<div class="Content">Каталог</div>
<ul class="Container">
</ul>
</li>
</ul>
Яваскрипт-вызов для инициализации дерева:
tree('id', '/ajax/data.php')
После этого вызова дерево становится полностью рабочим, но узлы подгружать пока не умеет.
Для этого нужно реализовать метод load и необходимые вспомогательные функции.
При описании дерева была предусмотрена AJAX-индикация, а в статье по интеграции AJAX в интерфейс - стандартные методы и последовательность вызовов. Остается применить их для дерева.
...
function load(node) {
/*
код этих трех функций -
как в статье по интеграции AJAX в интерфейсы
*/
function onSuccess(data) {... }
function onAjaxError(xhr, status) {... }
function onLoadError(error) { ...}
/*
функция showLoading использует способ
AJAX-индикации через CSS из этой же статьи.
*/
function showLoading(on) {
var expand = node.getElementsByTagName('DIV')[0]
expand.className = on ? 'ExpandLoading' : 'Expand'
}
function onLoaded(data) {
for(var i=0; i<data.length; i++) {
var child = data[i]
var li = document.createElement('LI')
li.id = child.id
li.className = "Node Expand" + (child.isFolder ? 'Closed' : 'Leaf')
if (i == data.length-1) li.className += ' IsLast'
li.innerHTML = '<div class="Expand"></div><div class="Content">'+child.title+'</div>'
if (child.isFolder) {
li.innerHTML += '<ul class="Container"></ul>'
}
node.getElementsByTagName('UL')[0].appendChild(li)
}
node.isLoaded = true
toggleNode(node)
}
showLoading(true)
$.ajax({
url: url,
data: node.id,
dataType: "json",
success: onSuccess,
error: onAjaxError,
cache: false
})
}
...
Для начала заметим, что все вспомогательные функции объявлены внутри load. Это удобно, т.к. автоматически дает им доступ к узлу node.
Можно вызвать новую загрузку load, не дожидаясь окончания текущей - конфликта доступа не произойдет, т.к обработчики через замыкание привязаны к загружаемому узлу.
Оригинальная функция здесь, пожалуй, всего одна - это onLoaded. Она принимает данные с сервера в виде массива объектов-детей:
[
{ id: 1, title: 'Node 1', isFolder: 1},
{ id: 2, title: 'Node 2', isFolder: 1},
{ id: 3, title: 'Node 3', isFolder: 0}
]
Из этих объектов создается DOM-структура дерева.
Никаких новых обработчиков событий при создании узлов на них не навешивается, т.к структура дерева использует один единый обработчик на контейнере.
Жмите на +, чтобы загрузить детей с сервера.
На стороне сервера используется скрипт, который сначала полсекунды спит (чтобы продемонстрировать индикацию загрузки), а затем возвращает 3 узла с последовательными номерами и примерно 33%-ным шансом того, что узел является листовым (isFolder=0).
Вы также можете: