Как стать автором
Обновить

Комментарии 44

toggleClass нужно заюзать в паре мест, вместо hasClass(...)? removeClass(...): addClass(...)
Хорошо переделаю с ним.
спасибо, сподвигли на дальнейшее узучение жквери. Все просто!
Вот на «чистом JS», проверил в FF3.0, IE6

        <script type="text/javascript">
            /*<![CDATA[*/
            (function() {
                var tree_init = function() {
                    this.clearWhitespace = function(element) {
                        var i, array = [];
                        for (i=0; i<element.childNodes.length; i++) {
                            if (element.childNodes[i].nodeType == 3) {
                                element.childNodes[i].nodeValue.match(/^[\s\r\n]+$/m) &&
                                    array.push(element.childNodes[i]);
                            } else {
                                this.clearWhitespace(element.childNodes[i]);
                            }
                        }
                        for (i=0; i<array.length; i++) {                            
                            array[i].parentNode.removeChild(array[i]);
                        }
                    }
                    this.addMarker = function() {
                        var ul_item = this.tree.getElementsByTagName("ul");
                        for (var i=0; i<ul_item.length; i++) {
                            var li_item = ul_item[i].getElementsByTagName("li");
                            for (var j=0; j<li_item.length; j++) {
                                var child = li_item[j].getElementsByTagName("ul");
                                if (child.length > 0 && li_item[j].parentNode == ul_item[i]) {
                                    var a_item = li_item[j].getElementsByTagName("a");
                                    if (a_item.length > 0 && a_item[0].parentNode.parentNode == li_item[j]) {
                                        a_item[0].innerHTML = '<em class="marker"></em>' + a_item[0].innerHTML;
                                    }
                                }
                            }
                        }
                    }
                    this.addEvent = function() {
                        var span_item = this.tree.getElementsByTagName("span");
                        var thisCopy  = this;
                        for (var i=0; i<span_item.length; i++) {
                            span_item[i].onclick = function() { thisCopy.click(this) };
                        }
                    }
                    this.setCurrent = function(a) {
                        if (this.current)
                            this.current.className =
                            this.current.className.replace("current", "");

                        this.current = a;
                        a.className += " current";
                    }
                    this.toggleClass = function(elem, classname) {
                        if (elem.className.indexOf(classname) != -1) {
                            elem.className = elem.className.replace(classname);
                            elem.className = elem.className.replace("  ", " ");
                        } else {
                            elem.className += " " + classname;
                        }
                    }
                    this.click = function(span) {
                        var subtree;
                        var a  = span.firstChild;
                        var li = span.parentNode;
                        var ul = span.nextSibling;
                        var em = a.firstChild;
                        this.setCurrent(a);
                        
                        if (li.nextSibling == null) {
                            li.hasChildNodes() && (subtree = li.childNodes[1]);
                            for (var i=0; i<subtree.childNodes.length; i++)
                                subtree.childNodes[i].className = "last";
                        }
                        if (ul && ul.nodeName.toLowerCase() == "ul")
                            ul.style.display = ul.offsetWidth > 0 ? "none" : "block";
                        if (em && em.nodeName.toLowerCase() == "em")
                            this.toggleClass(em, "open");
                    }

                    this.tree = document.getElementById('multi-derevo');
                    this.clearWhitespace(this.tree);
                    this.addMarker();
                    this.addEvent();
                }
                if (window.attachEvent) {
                    window.attachEvent("onload", tree_init);
                } else {
                    window.addEventListener("load", tree_init, false);
                }
            })()
            /*]]>*/
        </script>
Вау! Мегатруд!
Кстати как бы сравнить скорости варианта jQuery и чистый. Это вроде можно сделать в Firebug, но я с ним знаком ровно столько сколько и с jQuery, т.е. маловато и не использовал его для профайлинга ни разу еще.
В функции на this.click можно заменить
if (li.nextSibling == null) {
    li.hasChildNodes() && (subtree = li.childNodes[1]);
    for (var i=0; i<subtree.childNodes.length; i++)
        subtree.childNodes[i].className = "last";
}

хотя бы на
if (li.nextSibling == null && li.hasChildNodes() && (subtree = li.childNodes[1])) {
    subtree.className = "last";
}

и стиль прописывать не для li.last, а для ul.last li
ul.last li пробовал сделать?
Побочные эффекты не появились? Например last применяется к вложенным узлам?

Спаршиваю, ибо не могу сейчас проверить сам.
Да, есть такое дело, увидел только после того, как написал.
Потому я и ограничил действие li.last.

Можно вообще убрать класс li.last и просто каждом нужному li писать css('boarder','none');, но это не существенно.
Хотелось бы спросить у автора, какой-нибудь стресс-тест не проводили дереву? Просто в одном проекте приходится выводить порядка 1500-2000 узлов дерева. И хотелось бы узнать как оно по производительности.
Пробовал на ~270 элементах li.
Вложенность тоже может повлиять на работу.
Я думаю при таком количестве узлов необходимо дорабатывать код чтобы вложенные узлы загружались не сразу, а подкачивались при раскрытии, иначе я думаю будет слишком медленно.

Или есть реальная необходимость прямо все 1500-2000 узлов сразу вывести на страницу?
Иногода из-за отсутствия параметров у объектов не получается их группировать. У меня просто в дерево выводятся объекты газтранспортной системы. И данные не всегда все по ним имеются.
Конечно бывают особенные ситуации, под них нужно что-то переделывать.
Все таки не обязательно делать вывод всего сразу, я думаю. Можно сделать подгрузку по мере прокрутки вниз например. Также уместен будет поиск.
Тут вы правы, вполне возможно сказывается нехватка опыта. :)
Проблемы будут в любой реализации т.к. объем html кода будет ужасающим, а браузеру это все еще и обработать надо… У меня такая проблема с select на 3000 опций. 2 таких селекта на странице и про производительность можно забыть.
Вот и я о том же. И не удобно это пользователю видеть эти 3000 элементов сразу, нужно разбивать на части, как обычно делают с длинными статями, а снизу ставят указатель страниц.
Вообще круто, спасибо автору.
Потестил пример, есть одно замечание к юзабилити дерева: кликая мышкой по родительскому нет возможности выделить элемент чтобы ветка не сворачивалась :)
НЛО прилетело и опубликовало эту надпись здесь
Я честно говоря даже не думал, что такое может понадобиться. Ведь если его делать, то придется усложнять скрипт и раскрытие делать по двойному клику, но пользователю это совсем не привычно тыкать по ссылке дважды.

Это конечно можно и доработать, давай еще предложения какие есть. Приделу же нет совершенства )
Молодец, хорошо реализовал.
a.hasClass('current')?a.removeClass('current'):a.addClass('current');
замените на toggleClass

Чем $('#multi-derevo li:has(«ul»)').find('a:first')
лучше чем $('#multi-derevo li:has(«ul») a:first')?
Это мой первый скрипт на jQuery, я еще не понял до конца что как использовать. Делал по принципу «сделать любым способом — лишь бы работало».
Не хочу вас упрекать, но насколько мне известно и проверено — это совершенно разные объекты будут.
$('#multi-derevo li:has(«ul»)').find('a:first') — вернет мне все первые ссылки вложенные в li. В результате маркеур будет у всех ссылок для узлов с вложением,
а
$('#multi-derevo li:has(«ul») a:first') — вернет первую ссылку вложенную в li, и в результате маркер будет только у 1й первой ссылки вложенной в li.

Инфа find( expr ) , :first
Различия от использования видно на примерах в документации.
Ну, как видно второй мой абзац — вопрос. Именно это я и хотел узнать, спасибо.
Извини. Я этот вопрос не заметил. Кстати ответ на твой вопрос я узнал попробовав применить просто, а только потом по докам подтвердил.
Есть очень интересный плагин к jQuery для дерева: Treeview, он конечно немного «потяжелее» но и более гибок и в настройках и в событиях.
Видел я его, буквально сижу и смотрю. Скрипт там в несколько раз тяжелее, кому посложнее надо будет возьмет может его — от задачи зависит.
согласен. К тому же в большинстве случаев совершенно не нужен такой функционал. Да если и нужно что-то извращенное, то все-равно проще дописать (или найти готовую) функцию для этого, нежели впихивать библиотеку, потенциал которой будет использоваться на 10%. Плюс большое количество работающих скриптов на странице ведет к ужасающим лагам в IE, а иногда даже в FF.

Надоело уже как-то постоянно поддерживать баланс между размером кода, его производительностью и функциональностью…

Единственное, неплохо было бы кукисы реализовать…
На что кукисы поясни? Запоминать развернутый узел полагаю?
ага. Например пользователь развернул пару узлов, а потом перезагрузил страницу. Без кукисов узлы свернутся, а с кукисами останутся развернутыми. Мелочь, но приятно =)
Хм… в JS сильно забыл, скрипт для этого примера писал аж 2 дня, хотя jQuery говорят легко и все такое.
Короче поддержку кукисов я еще буду наверно неделю прилаживать (
Хотя, что интересно на одном из сайтов как раз мне это дерево-то и нужно сейчас с запоминанием положения.
Хорошая реализация дерева, но есть одно «но».
Чаще всего при клике на текст узла дерева нужно переходить на другую страницу (из одной категории в другую). В таком случае такой вариант реализации не подойдет т.к. по клику открывается поддерево узла. Для решения этой проблемы нужно будет переместить открытие узла на стрелку-открывалку, а саму ссылку оставить быть ссылкой на страницу. Но тут могут возникнуть проблемы.

Для своей реализации я использовал вот эту статью: javascript.ru/unsorted/tree + адаптировал под jQuery
Кстати, данная реализация имеет один большой недостаток в js: onclick вешается на КАЖДЫЙ элемент дерева, что убьет любой браузер при большом количестве элементов.

Сейчас попробую адаптировать под это дерево более производительный вариант
> «данная реализация „
Какая данная? Что ты привел в своем предыдущем сообщении или моя?

Я на вопросах производительности не заморачивался, когда его делал. Оптимизация решений — это путь после получения первичного результата.
твоя. Оптимизировать довольно просто. Нужно заменить все onclick events на 1.
1.
<div id="multi-derevo">
заменяем на
<div id="multi-derevo" onclick="treeMenu(event)">

2. js код для открытия-закрытия:
function treeMenu(event)
{
	event = event || window.event;
	var clickedElem = event.target || event.srcElement;
	// достаем узел на который нажали
	var node = jQuery(clickedElem.parentNode);
	// у зла нет поддерев? (узел-лист)		
	if (!node.hasClass('marker'))
		return; 	
	// анимация
	node.children('ul').slideToggle(200);
	node.toggleClass('open');
}

PS: код на работоспособность конкретно для этой реализации не проверял, но основная идея в том, что остается всего ОДИН onclick event на все дерево сколько бы там не было вложений.
PSS: думаю будет достаточно просто адаптировать эту функцию, если она все-таки не работает.
насколько я понимаю через онклик можно вообще сделать без jQuery на чистом Javascript с почти такой же сложностью кода за исключением анимации (вот её не знаю как на чистом js делать).
изначально оно и было на чистом js, но поскольку jQuery в проекте и так подключена, то я адаптировал функцию под нее и добавил анимацию.

На чистом js есть здесь: javascript.ru/unsorted/tree
Думаю можно попробовать перевесить jQuery на #multi-derevo, что то подобное видел в предыдущей теме.
Я попробую.
Ничего не мешает на открытие повесить и переход на страницу, однако я не заявляю это как готовый к использованию компонент. Чтобы сделать его пригодным для встраивания надо ним надо еще поработать.
Однажды я использовал подобный скрипт вместе с плагином jQuery Cookie. Подключение куков помогает запомнить положение элементов дерева после перезагрузки страницы. Мне кажется будет удобнее увидеть ветку, которую развернул, именно развёрнутой.

А вот демка плагина treeView, который также можно использовать вместе с куками.
Конечно хорошо бы сделать запоминание.
Я придумал пока такой механизм без кук — сравнивать текущий адрес и адрес в ссылке узла, если совпали, то развернем все до этого узла.

P.S. jQuery Cookie — посмотрю по возможности привинчу.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации