Удобное дерево с AJAX-подгрузкой

Эта статья показывает, как написать простое и удобное дерево с AJAX-подгрузкой узлов.

Она основана на материалах грамотное javascript-дерево и интеграция AJAX в интерфейс.

Добавить AJAX-подгрузку, используя эти две технологии - очень просто. Вообще, это может послужить основой для того, чтобы создавать любые AJAX-виджеты и сложные системы интерфейсов.

Дерево: делаем UI-компонент

Для начала оформим дерево в виде компоненты интерфейса. Инициализацию будет осуществлять функция tree, которая получает два параметра:

id
ID узла DOM, который служит контейнером для дерева
url
Адрес, с которого подгружать узлы при помощи AJAX-запроса

Код ниже полностью описывает дерево, за исключением функции 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-индикация, а в статье по интеграции 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).

Наше дерево:
  • Каталог

Вы также можете:

AltStyle によって変換されたページ (->オリジナル) /