Удобное дерево с AJAX-подгрузкой
Эта статья показывает, как написать простое и удобное дерево с AJAX-подгрузкой узлов.
Она основана на материалах грамотное javascript-дерево и интеграция AJAX в интерфейс.
Добавить AJAX-подгрузку, используя эти две технологии - очень просто. Вообще, это может послужить основой для того, чтобы создавать любые AJAX-виджеты и сложные системы интерфейсов.
Для начала оформим дерево в виде компоненты интерфейса. Инициализацию будет осуществлять функция 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 в интерфейс - стандартные методы и последовательность вызовов. Остается применить их для дерева.
...
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).
Вы также можете:
|
А как изменить этот скрипт, чтобы в скрытом фрейме отправлял запрос вместо аякс, а то в ИЕ6 по умолчанию отключен activeX и не отсылается или запрашивает подтверждение для включенияактивного содержимого?
Осталось еще добавить фунуцию перетаскивания узлов в дереве и между несколькими деревьями и будет перфект контрол
Да, я это когда-то все делал. В принципе, никаких проблем - статья про drag'n'drop на сайте есть. Все что надо - замечательно реализуется.
Статья очень классная, спасибо за неё!
Но у меня есть один вопрос - как в php'шном скрипте здесь узнать значение узла по которому кликнули? (т.е. то что значится как Node 1,2,3)?
Эта информация содержится в ajax-запросе:
Если что - извиняюсь за глупые вопросы ><
то что оно в этой строке содержится я разобралась. А вот как этот node.id в php вытащить?... перечитала учебника 3 и что-то не разобралась( статьи про jquery тож не помогли(
дай хотябы ссылку на то где это объясняется
Илья, огромное спасибо за статью!
Не подскажите, как сделать подгрузку узлов для бесконечного дерева из БД.
Модифицируйте PHP-скрипт, чтобы вместо генерации узлов он брал их из базы.
Если у вас возникают проблемы с node.id - убедитесь, что у каждого DOM-узла дерева есть ID. Это и есть node.id.
Ещё раз респект и уважуха Илья! Уже присоветовал сайт пацанам на работе.
Оо! Большое спасибо за багрепорт.
Как я понял - все дело было в том, что загружаемым узлам не ставился id, да ?
Исправил в тексте статьи, обновил скрипты и архив.
Да, и ещё такой вот запрос:
Добавила, что-то не заработало
источник:http://api.jquery.com/jQuery.ajax/
Как исправить функцию onLoaded, что бы это дерево не было бесконечным?
Дерево бесконечное только потому, что ПХП-скрипт на сервере возвращает все новые узлы.
В реальной жизни все будет конечное.. Обычно
разобрался как работает... алгоритм тут хитрый
Расскажите пожалуйста как это работает. Как рисуются вертикальные палочки, как добавляется строки, как все ровно так сдвигается. Из исходников не понятно как это рисуется.
Растолкуйте плиз. очень надо такую штуку а как оно работает непонимаю.
Со html структорой разобрался, тепер надо поразбираться с функциями
Спасибо за дерево, отличная штука!
Никак не могу понять, как надо привесить обработчик клика на текст элемента дерева. Помогите пожалуйста.
Виноват, поторопился с вопросом.
Всё очень просто. Для динамически генерируемых узлов надо добавить свой onlick в функцию
Это примерно 60 строка tree.js
В том-то и дело, что ничего не надо добавлять.
onclick один на все дерево.
Просто вставляете узел и оно будет работать.
Реализовывал свой аналогичный компонент. Во многом применял похожие приёмы.
Единственное чем недоволен - тем что использовал таблицы вместо списков. Но было требование, что новое дерево должно быть совместимо по формату с предыдущей версией.
Очень полезная функция - инициализация узлов дерева on demand - демка (из JSON очень быстро строится дерево с 500.000.000 узлами). Как показала практика - существует очень много пользователей, которым очень нужна эта функция.
Я кликнул мышкой по надписи Node 2, как определить, описание какой ветки нужно вывести рядом? Я так понял Node 2 - это DIV, значить при создании нового узла, надо присваевать второму диву который является классом content, идентификатор (например "div_"+child.id), потом вешать на него обработчик onclick, и дальше обрабатывать аяксом элемент в котором должно появиться описание данного узла, можно и с пхп, но прийдеться перегружать страницу, а отсюда проблема сохранения состояния дерева!
Если я не прав, то поправьте меня, просто не вижу смысла в этом дереве, если оно просто открывает и закрывает ветки, а просмотреть информацию по данной ветки, у него, как я понял мозгов нет!
Далее--------------
Автор: TT (не зарегистрирован), дата: вт., 19/05/2009 - 20:54
#permalink
Статья очень классная, спасибо за неё!
Но у меня есть один вопрос - как в php'шном скрипте здесь узнать значение узла по которому кликнули? (т.е. то что значится как Node 1,2,3)?
* Ответить
Автор: Илья Кантор, дата: вт., 19/05/2009 - 22:56
#permalink
Эта информация содержится в ajax-запросе:
Выделить все
.ajax(
...
data: node.id
...
)
это про то что я писал выше, человек спрашивал, как отловить нажатие на надписе, и узнать по какой именно был клик! Вопрос автору: описанный мною вариант имеет смысл, или я что-накрутил? Второй день копаюсь в коде, с некоторыми приколами так и не разобрался!
Вопрос номер 2: как сохранить состояние дерева, при перезагрузке страницы! Все открытые ветки пересылать по сессии, а потом циклом выводить, и присваивать каждой открытой function() { tree("tree", "data.php") }, это, мне кажеться, и вам понятно что полная чуш!
Есть какие нить предложения?
Заранее благодарен!
Спасибо за отличный скрипт!
Но не магу разобраться с одним моментом
Как можно скриптом скрыть и загрузить заново список детей в одной из папок?
В моем приложение это дерево ещё и редактируеться
И ещё как можно средствами javascript открыть одну из папок
Спасибо большое за деревко!
Подскажите, пожалуйста, почему у меня $id не передаётся? Подставляю в запрос циферку - работает, только, конечно же, один и тот же уровень всё время.
ЗЫ Если записываю его в имя нода, то в названии появляется.
Вызов $.ajax автоматически интерпретирует ответ сервера как JSON - и если с этим проблемы, то хотя XmlHttpRequest выполнился успешно, но $.ajax вызывает коллбэк для ошибки и ставит status="parsererror".
как раз такая ситуация.
консоль firebug'a показывает
xhr.status 200
из data.php возвращается
[{ id: 1, title: 'Node 1', isFolder: 1},{ id: 2, title: 'Node 2', isFolder: 1},{ id: 3, title: 'Node 3', isFolder: 0}]
но парсер говорит что некорректные данные с сервера...
в чем может быть затык?
Ошибки не будет, если в ответе и ключи и их значения будут взяты в двойные кавычки.
{"id" : "1", "title": "Node 1", "isFolder": "1"}
Нашлась затыка.
С новой библиотекой jquery 1.4.2 не работает пример.
Работает с 1.3.2
не смотрел, но видимо onAjaxError по другому работает..
При выполнении примера вываливается ошибка $ is not defined в tree.js строка 81 $.ajax({
....
Что у меня не так? Куда копать?
Подключите jquery в тот же файл, куда подключили tree.js
Илья, спасибо большое за сайт и статьи.
Мелкий баг:
На слове "Скачать " ссылка не та.
Как заметили некоторые коллеги, в формировании запроса есть ошибка...
источник:http://api.jquery.com/jQuery.ajax/
После манипуляций с библиотекой jQuery (пример работает с версией 1.3.2), в эксплорере пример заработал, а в опере по прежнему пишет: Ошибка parsererror :Некорректные данные с сервера.
В чем может быть проблема?Разве этот скрипт не кроссбраузерный?
Та же беда что у Надежда_ (не зарегистрирован), не хочет работать с id из базы
Никак не пойму, почему если id генериться автоматически - скрип работает, если id берем из базы, то получаем "Ошибка parsererror: Некорректные данные с сервера"
Ключи взятые в двойные кавычки не помагают
На сколько я понял, дело в типе данных, когда берем id из базы, переменная строковая, а должна быть int, исправляется с помощью settype().
И внесите, пожалуйста, в архив эти изменения:
Для корректной работы с библиотекой jquery 1.4.2, нужно
ключи и их значения взять в двойные кавычки
{"id" : "1", "title": "Node 1", "isFolder": 1}, но значение ключа "isFolder" не нужно
брать в "", а то всегда будут узлы.
Для jquery-1.4.4., у меня заработало так:
как правильно заметил MikhailGirshberg
и в data.php
Отличный скрипт! Автору респект.
Попытался разобраться при использовании его в форме. Что-то не выходит у меня каменный цветок.
Добавляю
данные из инпута по POST не передаются. Видимо вся загвоздка в том, что данные подгружаются динамично и первоначально их нет в форме. Помогите разобраться
сбор данных из формы
Данные то собрались, а как их в POST кинуть?
Пожалуйста, подскажите, как сделать, чтобы по умолчанию при загрузке страницы узел Каталог оказался раскрытым и автоматически подгрузились его потомки
Подскажите где найти простой пример кода на AJAX:
есть таблица групп с подгруппами
в форме 2 селекта, в 1-м выбирается группа, а во 2-й подгружаются подгруппы выбранной группы.
День добрый.
Хелп!!!
Сделал первый вывод дерева из базы.
Какой вариант есть чтобы сделать вывод последующих веток был уже зависим от содержания родительской???
Здравствуйте!!!
Очень интересная статья, пытаюсь сделать, чтобы скрипт заработал на моем виртуальном сервере, но при нажатии на "плюсик" отображается картинка загрузки и всё. Прочитал все комментарии, вносил соответствующие исправления в разных комбинациях.
У меня такой вопрос, думаю что не у меня одного, подскажите пожалуйста:
в файле tree.html есть ссылка на /misc/jquery.js, однако такого файла в архиве с исходниками нет. Может быть в этом проблема? И что должно быть в этом файле?
была та же проблема. с добавлением файла всё наладилось
МОжно ли в узлах (паротив) дерева добавить пару кнопок?
Здравствуйте, а как сделать, чтобы вот только обновил, страницу, а оно само выдвигалось, тоесть что бы не надо было на плюсик жать.!
Отличный пример. Спасибо за интересную статью, жаль только что на элементы дерева надо жмякать, круто было бы, если бы они при наведении выводились.
Здравствуйте, простите за тупой может вопрос, не могу понять как считываеться id для GET запроса. Подскажите пожалуйста как передать ид в php
Сделал так но ошибку выводит.
Добрый день.
Есть база:
id parent title note
1 0 Заголовок1 описание
2 0 Заголовок2 описание
3 1 Заголовок3 описание
4 2 Заголовок1 описание
У корневых элементов parent = 0
Далее идут элементы уже с parent корневых и так далее. Получается древовидная структура.
Как будет выглядеть код php , который мне сформирует сначала корневые
элементы с parent 0 (верхний уровень) и код php, который по ID будет подгружать иp этой базы дочерние элементы ?
Если не трудно дайте плиз листинг
Подскажите пожалуйста как раскрывать элементы дерева по клику на название title, а не на +
Ребята, я новичок, поэтому учусь на подобных сайтах.
Из вышеуказанных скриптов, я скопировал код HTML и создал файл html, а код для CSS скопировал и создал отдельный файл .css, но ничего не работает
Ребята помогите со скриптом. Суть в том, что я хочу написать в поле для поиска какое нибудь слово, которое находится в списке меню. Из обычного списка выходить слово, но когда слово находится внутри ("tree" - дерево - этот скрипт для меню под дерево я взял прямо с данной темы) скрипт не находит это слово.
==
.searchinput {
xbackground-image: url('/css/searchicon.png');
xbackground-position: 8px 6px;
xbackground-repeat: no-repeat;
width: 91%;
font-size: 14px;
padding: 6px 6px 6px 40px;
padding: 6px;
border: 1px solid #ddd;
margin: 0 0 20px 10px;
}
function filterContent(elmnt) {
var x, i, val, show, showh2, showbr;
val = elmnt.value;
x = document.getElementById("leftmenuinnerinner").getElementsByTagName("*");
for (i = 1; i < x.length; i++) {
if (x[i].tagName == "H2") {
if (showh2) {
if (show == false) {
showh2.style.display = "none";
} else {
showh2.style.display = "block";
}
}
show = false;
showh2 = x[i];
}
if (x[i].tagName == "BR") {
if (showbr) {
if (show == false) {
showbr.style.display = "none";
} else {
showbr.style.display = "block";
}
}
showbr = x[i];
}
if (x[i].tagName == "A") {
if (x[i].innerHTML.toUpperCase().indexOf(val.toUpperCase()) == -1) {
x[i].style.display = "none";
} else {
x[i].style.display = "block";
show = true;
}
}
if (showh2) {
if (show == false) {
showh2.style.display = "none";
} else {
showh2.style.display = "block";
}
}
}
}
function tree_toggle(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 // клик на листе
}
// определить новый класс для узла
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 hasClass(elem, className) {
return new RegExp("(^|\\s)"+className+"(\\s|$)").test(elem.className)
}
.Container {
padding: 10px;
margin: 0;
}
.Container li {
list-style-type: none;
}
/* indent for all tree children excepts root */
.Node {
background-image : url(img/i.gif);
background-position : top left;
background-repeat : repeat-y;
margin-left: 18px;
zoom: 1;
}
.IsRoot {
margin-left: 0;
margin-top: -15px;
}
/* left vertical line (grid) for all nodes */
.IsLast {
background-image: url(img/i_half.gif);
background-repeat : no-repeat;
}
.ExpandOpen .Expand {
background-image: url(img/expand_minus.gif);
}
/* closed is higher priority than open */
.ExpandClosed .Expand {
background-image: url(img/expand_plus.gif);
}
/* highest priority */
.ExpandLeaf .Expand {
background-image: url(img/expand_leaf.gif);
}
.Content {
min-height: 18px;
margin-left:18px;
}
* html .Content {
height: 18px;
}
.Expand {
width: 18px;
height: 18px;
float: left;
}
.ExpandOpen .Container {
display: block;
}
.ExpandClosed .Container {
display: none;
}
.ExpandOpen .Expand, .ExpandClosed .Expand {
cursor: pointer;
}
.ExpandLeaf .Expand {
cursor: auto;
}
огромное спасибо
Я думаю, что это будет полезно для моего проекта
Учитывая специфику проекта, над которым сейчас работаю(справочная система), этот плагин оказался просто незаменимым, и я использую 90% его функционала с превеликим удовольствием. И я до сих пор не видел такой же мощной альтернативы. canuckle
Удобное дерево с AJAX-подгрузкой можно реализовать с использованием JavaScript-библиотеки, Friday Night Funkin такой как jQuery или Vue.js, в зависимости от ваших предпочтений и требований проекта.