Создайте дерево, которое по клику на заголовок скрывает-показывает детей:
Исходный документ: tutorial/browser/events/tree-src/index.html.
Требования:
- Использовать делегирование.
- Клик вне текста заголовка (на пустом месте) ничего делать не должен.
- При наведении на заголовок — он становится жирным, реализовать CSS.
- При двойном клике на заголовке — его текст не должен становиться выделенным.
P.S. При необходимости HTML/CSS дерева можно изменить.
Дерево устроено как вложенный список.
Клики на все элементы можно поймать, повесив единый обработчик onclick на внешний UL.
Как поймать клик на заголовке? Элемент LI является блочным, поэтому нельзя понять, был ли клик на тексте, или справа от него.
Например, ниже — участок дерева с выделенными рамкой узлами. Кликните справа от любого заголовка. Видите, клик ловится? А лучше бы такие клики (не на тексте) игнорировать.
<style>
li { border: 1px solid green; }
</style>
<ul onclick="alert(event.target || event.srcElement)">
<li>Млекопетающие
<ul>
<li>Коровы</li>
<li>Ослы</li>
<li>Собаки</li>
<li>Тигры</li>
</ul>
</li>
</ul>
В примере выше видно, что проблема в верстке, в том что LI занимает всю ширину. Можно кликнуть справа от текста, это все еще LI.
Один из способов это поправить — обернуть заголовки в дополнительный элемент SPAN, и обрабатывать только клики внутри SPAN'ов.
Мы могли бы это сделать в HTML, но давайте для примера используем JavaScript. Следующий код ищет все LI и оборачивает текстовые узлы в SPAN.
var treeUl = document.getElementsByTagName('ul')[0];
var treeLis = treeUl.getElementsByTagName('li');
for(var i=0; i<treeLis.length; i++) {
var li = treeLis[i];
var span = document.createElement('span');
li.insertBefore(span, li.firstChild); // добавить пустой SPAN
span.appendChild(span.nextSibling); // переместить в него заголовок
}
Теперь можно отслеживать клики на заголовках.
Так выглядит дерево с обёрнутыми в SPAN заголовками и делегированием:
<style>
span { border: 1px solid red; }
</style>
<ul onclick="alert((event.target||event.srcElement).tagName)">
<li><span>Млекопетающие</span>
<ul>
<li><span>Коровы</span></li>
<li><span>Ослы</span></li>
<li><span>Собаки</span></li>
<li><span>Тигры</span></li>
</ul>
</li>
</ul>
Так как SPAN — инлайновый элемент, он всегда такого же размера как текст. Да здравствует SPAN!
В реальной жизни дерево должно быть сразу со SPAN, чтобы не нужно было исправлять структуру. Если HTML-код дерева генерируется на сервере, то это несложно. Если дерево генерируется в JavaScript — тем более просто.
Получение узла по SPAN
Для делегирования нужно по клику понять, на каком узле он произошел.
В нашем случае у SPAN нет детей-элементов, поэтому не нужно подниматься вверх по цепочке родителей. Достаточно просто проверить event.target.tagName == 'SPAN', чтобы понять, где был клик, и спрятать потомков.
var tree = document.getElementsByTagName('ul')[0];
tree.onclick = function(e) {
e = e || event;
var target = e.target || e.srcElement;
if (target.tagName != 'SPAN') {
return; // клик был не на заголовке
}
var li = target.parentNode; // получить родительский LI
// получить UL с потомками -- это первый UL внутри LI
var node = li.getElementsByTagName('ul')[0];
if (!node) return; // потомков нет -- ничего не надо делать
// спрятать/показать
node.style.display = node.style.display ? '' : 'none';
}
Жирные узлы при наведении
Узел выделяется при наведении при помощи CSS-селектора :hover.
Невыделяемость при клике
На всё дерево можно поставить обработчик, отменяющий выделение при клике
tree.onselectstart = tree.onmousedown = function() {
return false; // делаем узлы невыделяемыми
}
Полное решение вы можете увидеть здесь: tutorial/browser/events/tree/index.html.