Как сделать дерево html
Структурирование данных с помощью JavaScript: Дерево
Например, статья, которую вы читаете в данный момент, отображается в браузере в виде дерева. Абзацы представлены в виде элементов
В этой статье мы используем два различных метода прохождения дерева: поиск в глубину ( DFS ) и поиск в ширину ( BFS ). Оба этих типа прохождения подразумевают различные способы взаимодействия с деревом и включают в себя использование структур данных, которые мы рассмотрели в этой серии статей. DFS использует стек, а BFS использует очередь.
Дерево (поиск в глубину и поиск в ширину)
В информатике дерево представляет собой структуру, которая задает иерархические данные с узлами. Каждый узел дерева содержит собственные данные и указатели на другие узлы.
Давайте сравним дерево со структурой организации. Эта структура имеет должность верхнего уровня ( корневой узел ), например генерального директора. Ниже этой должности располагаются другие должности, такие как вице-президент ( VP ).
Для представления их подчиненности мы используем стрелки, указывающие от генерального директора на вице-президента. Такие должности, как генеральный директор, является узлами; связи, которые мы обозначили от генерального директора к вице-президенту, являются указателями. Для создания других связей в нашей структуре организации мы повторяем этот процесс — добавляем указатели на другие узлы.
Операции дерева
Реализация дерева
Теперь давайте напишем код дерева.
Свойства Node
Свойства Tree
Tree содержит две строки кода. Первая строка создает новый экземпляр Node ; вторая строка назначает node в качестве корневого элемента дерева.
Для определения Tree и Node требуется лишь несколько строк кода. Но этого достаточно, чтобы помочь нам задать иерархические данные. Чтобы доказать это, давайте используем несколько примеров для создания экземпляра Tree :
Методы дерева
Мы создадим следующие пять методов:
Так как для каждого метода требуется прохождение дерева, мы сначала реализуем методы, которые определяют различные типы прохождения дерева.
Метод traverseDF(callback)
Метод для прохождения дерева с помощью поиска в глубину:
Шаги 2 ( завершающий себя ), 3 ( вызывающий себя ) и 4 ( обратный вызов ) повторяются до прохождения каждого узла дерева.
Рекурсия — это очень сложная тема. Для ее понимания можно поэкспериментировать с нашей текущей реализацией traverseDF( callback ) и попытаться понять, как это работает.
Теперь, давайте вызовем traverseDF(callback) :
Метод traverseBF(callback)
Метод для прохождения дерева по ширине. Разница между поиском в глубину и поиском в ширину заключается в последовательности прохождения узлов дерева. Чтобы проиллюстрировать это, давайте используем дерево, которое мы создали для реализации метода traverseDF(callback) :
Теперь давайте передадим в traverseBF(callback) тот же обратный вызов, который мы использовали для traverseDF(callback) :
Выводим строки на консоль, и диаграмма нашего дерева покажет нам картину, отображающую принцип поиска в ширину. Начните с корневого узла; затем пройдите один уровень и посетите каждый узел этого уровня слева направо. Повторите этот процесс до тех пор, пока все уровни не будут пройдены. Реализуем код, с помощью которого этот пример будет работать:
Определение traverseBF(callback) я объясню пошагово:
Метод contains(callback, traversal)
Определим метод, который позволит нам находить конкретное значение в дереве. Чтобы использовать любой из методов прохождения дерева, я устанавливаю для contains(callback, traversal) два принимаемых аргумента: данные, которые мы ищем, и тип прохождения:
Метод add(data, toData, traversal)
Теперь у нас есть метод для поиска узла в дереве. Давайте определим метод, который позволит нам добавить узел к конкретному узлу:
add(data, toData, traversal) определяет три параметра. data используется для создания нового экземпляра узла. toData — используется для сравнения каждого узла в дереве. Третий параметр, traversal — это тип прохождения дерева, используемый в этом методе.
Давайте используем add(data, toData, traversal) в нашем примере:
Вот более сложный пример использования add(data, toData, traversal) :
Метод remove(data, fromData, traversal)
Если parent не существует, мы выдаем ошибку. Если parent существует, мы вызываем findIndex() с parent.children и данными, которые мы хотим удалить из дочерних узлов parent. findIndex() — это вспомогательный метод, определение которого приводится ниже:
Полная реализация дерева
Наша реализация дерева теперь является полной:
Заключение
Каждый раз, когда вам нужно будет задать иерархическую структуру данных, вы можете использовать для этого дерево.
Форум
Справочник
Грамотное javascript-дерево за 7 шагов
В этой статье описана DOM/CSS-структура дерева, которую я в свое время разработал для dojo toolkit.
Шаг 1. DOM-модель одного узла дерева.
Визуальное представление узла:
Шаг 2. Два узла дерева.
Шаг 3. CSS для показа дерева
Стили узла
Стили компонентов узла
Иконки состояния узла
Здесь очень важен порядок, в котором следуют определения.
Поддеревья вложены, из-за этого получается такая конструкция:
Шаг 4. Добавляем структурные линии.
Структурные линии обрисовывают дерево, делая иерархию более наглядной.
В некоторых javascript-деревьях они пунктирные и используют кучу лишних тагов из-за неудачно выбранной DOM/CSS-модели.
Метод построения линий, который будем использовать мы, позволяет сделать линии гладкие, растягивающиеся при изменении размера деревьев.
Впрочем, пунктир добавить тоже никто не помешает.
И все это без добавления дополнительных тагов, исключительно средствами CSS.
Каркас из линий образуется дополнительными CSS-правилами.
Получается, что все узлы на одном уровне соединены вертикальной чертой.
Поэтому получается, что эти иконки автоматически «нанизываются» на вертикальную линию.
Вертикальные линии образуют каркас, а новые иконки Expand* присоединяют узлы к каркасу. Структурные линии построены .
Шаг 5. Скрытие-раскрытие
Для скрытия-раскрытия добавим два CSS-правила.
Для скрытия-раскрытия javascript-функция всего лишь меняет класс узла. Остальное делает CSS.
Шаг 6. AJAX-индикация
Пока что мы строили дерево исключительно из HTML-разметки.
Полностью аналогично дерево работает при создании разметки при помощи Javascript. Как загружать данные с сервера в формате JSON, и многое другое Вы можете прочитать в цикле статей AJAX.
Здесь мы посмотрим, как добавить в дерево индикаторы обработки узла: .
Индикатор обработки, вообще говоря, может обозначать любые асинхронные операции. Начиная от загрузки детей и заканчивая удалением всего этого узла с сервера.
Опишем его CSS-правилом:
Например, так может выглядеть участок дерева с активным узлом Item 1.1 :
Шаг 7. Дополнительные возможности
Вы можете пожелать добавить в дерево дополнительные элементы. Например, чекбоксы или иконки с типом узла.
Указываем размеры, отступ и float: left :
Все это осуществляется добавлением пары правил:
Скачать CSS, JS и картинки
В принципе, можно использовать и CSS/JS/картинки напрямую со страницы, но они содержат некоторое количество лишних классов.
Для удобства дерево все-в-одном с JS/CSS/HTML находится на отдельной странице. В этом примере полностью расписано дерево без AJAX-индикации и чекбоксов.
Кроме того, можно скачать материалы по статье:
Замечательная и полезная статья!
неплохо.
я бы предложил использовать классы с единым префиксом (нэймспэйсом) при создании подобных абстрактных реализаций, дабы избежать случайного конфликта с другими не менее абстрактными реализациями ^_^
Update: Добавил downloads и замечание о префиксах/неймспейсах в конце статьи.
Небольшие редакторские правки для лучшего раскрытия некоторых моментов.
А вот такой вопрос. По умолчанию дерево полностью раскрыто, как сделать его закрытым?
Или чтобы запоминалось, ну это наверное в куки надо закидывать.
Посмотри последний пример или скачай исходники. Там дерево полностью закрыто должно быть.
Да Твоё дерево не очень, так как сразу глотает всю структуру, а если у тебя будет 5 тыщ узлов ?
Да, в статье не разобран вопрос динамической подгрузки данных.
С другой стороны, её достаточно просто реализовать самому.
UPDATE: В разделе по AJAX появились статьи про интеграцию AJAX в интерфейс и статья про AJAX-дерево.
дерево и в правду сразу открывается((
В этом примере стоит ExpandClosed, так что дерево закрыто.
Отличная статья! Долго искал нечто подобное. Респект автору за грамотный подход к задаче!
Большинство подобных деревьев обычно делается через Хм. пень колоду. В данном случае, все четко соответствует спецификациям и обеспечивает широкую кроссбраузерность.
Несомненный плюс данного дерева в том, что оно не генерируется скриптом, а полностью выполнено в виде HTML кода. Что в данном случае сохраняет саму логическую разметку документа, плюс позволяет свободно индексировать содержимое поисковиками.
Ну и несомненную ценность имеет не только конечный результат, но и сама статья.
Спасибо огромное автору! Добавил в избранное!
Если расширить условие функции раскурывания/закрывания таким образом, поведение дерево станет более юзабельным: для раскрытия узла не нужно целиться в крестик, а можно нажать на его имя.
В статье сделан именно крестик, т.к на клик на имени часто вешается что-то другое, например, открытие страницы с этим именем.
Хотя, конечно, в вашем случае может быть целесообразно расширить дерево именно так
Что-то у меня такой вариант не работает, а очень нужно, не могу понять почему?
В статье http://javascript.ru/ajax/tutorial/intro приведён пример ajax-бесконечного дерева. Его просто создать, когда подгрузка узлов осуществляется без анимации.
Хотелось бы знать, как оптимально сделать, чтобы список дочерних узлов «выезжал» вниз или вверх (при сворачивании)?
Это делается через некую глобальную переменную типа width и через setTimeout вызывается функция, которая её меняет для данного тага?
Именно про анимацию. Чем достигается появление результата ответа не сразу, а через «выкатывание» вниз?
Там изменение height + setTimeout.
попробовал твой сокращённый вариант, работает отлично. на IE, а вот на firefox дерево получается кривое, в чём может быть проблемма?
Да, действительно, FF делает отступ между маркером списка и текстом. Хотя, я бы не сказал, что дерево сильно кривое, просто лишний пробел перед текстом.
Но, тем не менее, пофиксить можно так:
Придется завернуть текст в спаны.
И в CSS добавить хак для FF.
Да, это помогло, спасибо!
Вы пишите, что (цитата):
А у меня выполнено в виде плагина к jQuery, который хавает кошерный DOM-элемент из списков и строит древовидный javascript объект jsTree (или сразу из json).
Статья превосходна в техническом плане. Но в плане практики у меня остались вопросы. Ведь мне например понадобится не только показать это дерево, но и произвести с ним какие-либо операции, например отметить какой то элемент дерева галкой и отправить данные из моей формы с учетом выбранной галки на сервер. Таким образом получаем новое требование: массив дерева должен содержать не только «Имя_элемента», но и «ID_элемента». Без ID никак нельзя, сами понимаете, ведь в имени могут быть и русские буквы, к которым сервер может иметь неприязнь, так еще и имена из соседних веток дерева могут совпадать. Надеюсь доступно объяснил свою мысль. Вобщем нужно прикрутить массив, в котором не один ключ, а два и более: [id] и [name]
интересует реализация такого дерева на php + javascript
Уважаемые, построил дерево, используя указанный код (мой опыт JS близок к нулю).
Задача: передать в скрытую форму значение, содержащееся в классе ‘Content’ для дальнейшей обработки.
Проблема: не передает IE 6.0 и Опера 9.61, но с Фоксом и Сафари работает.
Подскажите как правильно.
я в java плохо разбераюсь. Подскажите как сделать сворачивание открытой ветки при при открытии другой ветки, что бы у дерева постоянно была открыта только одна ветка.
Парни подскажите плиз как сделать автоперенос текста на новую строку? Ну ограничить ширину меню. Использую все стандартное.
да, наверно так и есть
автору огромное спасибо за статью!!
от себя бы дополнил, что класс IsLast можно смело заменить на следующее и не засорять ваш js код ненужной ф-ностью :
Обьясните пожалуйста эту строку:
function tree_toggle(event) <
event = event || window.event // Здесь ясно создаем обьек собитие
var clickedElem = event.target || event.srcElement // Здесь получаем имя инициатора события
// Node, на который кликнули
var node = clickedElem.parentNode
if (hasClass(node, ‘ExpandLeaf’)) <
return // клик на листе
>
return new RegExp(«(^|\\s)»+className+»(\\s|$)»).test(elem.className)
Есть еще строка
var re = /(^|\s)(ExpandOpen|ExpandClosed)(\s|$)/
node.className = node.className.replace(re, ‘$1’+newClass+’$3’) что значит символ доллара с 1 и 3
ОБЬЯСНИТЕ ПОЖАЛУЙСТА как и что значит в этой функции, плииз, нашел все в нете но в голове каша! я понимаю что меняется что т оместами но как задается условие немогу понять,СПАСИБО СПАСИБО
Вопрос к автору. Можно ли данный код использовать в коммерческом проекте? Есть ли лицензия?
Спасибо за исходник! Очень выручает!
вот пример кода на JQuery для отметки всех дочерних checkbox’ов
Когда писал свое дерево не додумался использовать классы для изменения картинок и закрытия и открытия LI, но как убирается продолжение бекгроунда первого (коренного LI) элемента, если последний из его детей сам имеет детей и раскрыт. Ведь коренной LI охватывает все элементы, и соответсвенно его бекгроунд тоже продолжается до конца. хотя должен закончится на последнем ребенке?
Люди добрые, а как сделать, чтобы сам корень Root не отображался. Т.е. дерево сразу показывало ветки, без корня.
Сделаю закладочку. Очень толково написано.
Спасибо за хороший и простой и понятный скрипт
использовал версию с чекбоксами
не могли бы посоветовать как реализовать такю фунцию
нужно поставить чекбокс на верхние уровни папок с таким расчетом чтобы при его выбори выбирались все в ветке
Как сделать все элементы в сетке, пробовал в container style=border:1px solid black
обводятся элементы, а дерева в сетке не получается
В таком виде всё открывается в Mozilla Firefox, а в Internet Explorer 2й список не открывается. Подскажите пожалуйста как это исправить?
Grab — новый интерфейс для работы с DOM-деревом HTML-документа
Исторический экскурс
Ранее я уже писал на хабре о Grab — фреймворке для написания парсеров сайтов: раз, два, три, четыре. В двух словах, Grab это удобная оболочка поверх двух библиотек: pycurl для работы с сетью и lxml для разбора HTML-документов.
Библиотека lxml позволяет совершать XPATH-запросы к DOM-дереву и получать результаты в виде ElementTree объектов, имеющих кучу полезных свойств. Несколько лет назад я разработал несколько простых методов, которые позволяли применять xpath-запросы к документу, загруженному через граб. Проиллюстрирую кодом:
По сути это аналогично следующему коду:
Удобство метода xpath_text заключается в том, что он автоматически применяется к загруженному через Grab документу, не нужно строить дерево, это делается автоматически, также не нужно вручную выбирать первый элемент, метод xpath_text делает это автоматически, также этот метод автоматически извлекает текст из всех вложенных элементов. Далее я привожу все методы библиотеки Grab с их кратким описанием:
Не обошлось и без конфузов. Метод grab.xpath — возвращает первый элемент выборки, в то время как метод xpath ElementTree объекта возвращает весь список. Народ неоднократно натыкался на эту граблю. Также хочу заметить, что был точно такой же набор методов для работы с css запросами т.е. grab.css, grab.css_list, grab.css_text и т.д., но я лично отказался от CSS-выражений в пользу XPATH т.к. XPATH более мощный инструмент и часто есть смысл использовать его и я не хотел видеть в коде мешанину из CSS и XPATH выражений.
У вышеописанных методов был ряд недостатков:
В-третьих, хотя это скорее проблема расширений фреймворка, но, так или иначе, область имён объекта Grab засоряется множеством вышеописанных методов.
А, да, и четвёртое. Меня заколебали вопросом о том, как получить HTML код элементов, найденных с помощью методов grab.xpath и grab.xpath_list. Народ не хотел понимать, что grab это просто обёртка вокруг lxml и что нужно просто прочитать мануал на lxml.de
Новый интерфейс для работы с DOM-деревом призван устранить эти недостатки. Если вы пользуетесь фреймворком Scrapy, то нижеописанные вещи будут вам уже знакомы. Я хочу рассказать о селекторах.
Селекторы
Селекторы, что это? Это обёртки вокруг ElementTree элементов. Изначальное в обёртку заворачивается всё DOM дерево документа т.е. обёртка строится вокруг корневого html элемента. Далее мы можем с помощью метода select получить список элементов, удовлетворяющих XPATH выражению и каждый такой элемент будет опять завёрнут в Selector обёртку.
Давайте посмотрим, что мы можем делать с помощью селекторов. Для начала сконструируем селектор
Теперь сделаем выборку методом select, получим список новых селекторов. Мы можем обращаться к нужному селектору по индексу, также есть метод one() для выбора первого селектора. Обратите внимание, чтобы получить доступ непосредственно к ElementTree элементу, нам нужно обратиться к атрибуту node у любого селектора.
Какие действия доступны над найденными селекторами? Мы можем извлечь текстовое содержимое, попытаться найти числовое содержимое и даже применить регулярное выражение.
Обратите внимание, мы используем индекс, чтобы обращаться к первому найденному селектору. Все эти записи можно сократить, не указывая индекс, тогда, по-умолчанию, будет использоваться первый найденный селектор.
Что ещё? Метод html для получения HTML-кода селектора, метод exists для проверки существования селектора. Также вы можете вызывать метод селект у любого селектора.
Как работать с селектором непосредственно из Grab объекта? C помощью аттрибута doc вы можете получить доступ к корневому селектору DOM-дерева и далее использовать метод select для нужной выборки:
Текущая реализация селекторов в Grab ещё достаточно сырая, но понять и оценить новый интерфейс, я думаю уже можно.
В версии Grab, доступной через pypi селекторов пока нет. Если хотите поиграться с селекторами, ставьте Grab из репозитория: bitbucket.org/lorien/grab. Конкретно реализация селекторов находится тут
Я представляю компанию GrabLab — мы занимаемся парсингом сайтов, парсим с помощью Grab и не только. Если ваша компания использует Grab, вы можете обращаться к нам по поводу доработки Grab под ваши нужды.