Przejdź do treści

Menu drzewiaste

Przykład

Kliknij foldery menu myszą:

Skrypt

Wymagana wiedza:

Aby uzyskać menu drzewiaste, utwórz plik tree.css w tym samym katalogu co strona i zapisz w nim:

ul.tree {
	display: block;
	margin-left: 0;
	padding-left: 0;
}

ul.tree ul {
	display: block;
	margin-left: 0;
	padding-left: 0;
	margin-top: 0;
	margin-bottom: 0;
}

ul.tree li {
	display: block;
	list-style-type: none;
	padding-left: 20px;
	background-image: url("document.gif");
	background-position: left top;
	background-repeat: no-repeat;
}

ul.tree li.closed {
	background-image: url("closed.gif");
	background-position: left top;
	background-repeat: no-repeat;
}

ul.tree li.opened {
	background-image: url("opened.gif");
	background-position: left top;
	background-repeat: no-repeat;
}

ul.tree li a {
	font-size: 13px;
	text-decoration: none;
	cursor: pointer;
}

ul.tree li a.folder {
	cursor: pointer;
}

ul.tree li a.active {
	font-weight: bold;
}

ul.tree li a:hover {
	text-decoration: underline;
}

Dodatkowo w tym samym katalogu utwórz plik tree.js i zapisz w nim:

/**
 * @author Sławomir Kokłowski {@link http://www.kurshtml.edu.pl}
 * @copyright NIE usuwaj tego komentarza! (Do NOT remove this comment!)
 */

function Tree(id)
{
	this.id = id;
	
	var url = unescape(window.location.href.replace(/#.*/, ''));
	var base = window.location.protocol + '//' + window.location.host + window.location.pathname.replace(/[^\/\\]+$/, '');
	
	this.click = function ()
	{
		for (var i = 0, el_node; i < this.parentNode.childNodes.length; i++)
		{
			el_node = this.parentNode.childNodes.item(i)
			if (el_node.nodeName.toLowerCase() == 'ul')
			{
				el_node.style.display = el_node.style.display == 'none' ? 'block' : 'none';
				this.parentNode.className = this.parentNode.className.replace(/(^| +)(opened|closed)( +|$)/g, ' ') + ' ' + (el_node.style.display == 'none' ? 'closed' : 'opened');
				return;
			}
		}
	}
	
	this.start = function (el)
	{
		for (var i = 0, el_node; i < el.childNodes.length; i++)
		{
			el_node = el.childNodes.item(i);
			if (el_node.nodeName.toLowerCase() == 'a')
			{
				el_node.onclick = this.click;
				for (var j = 0; j < el_node.parentNode.childNodes.length; j++)
				{
					if (el_node.parentNode.childNodes.item(j).nodeName.toLowerCase() == 'ul')
					{
						el_node.parentNode.className += ' closed';
						el_node.className = (el_node.className ? el_node.className + ' ' : '') + 'folder';
						break;
					}
					if (el_node.parentNode.childNodes.item(j).nodeName.toLowerCase() == 'li') break;
				}
				var active = el_node.href && unescape(el_node.href.replace(/#.*/, '')) == url;
				if (!active)
				{
					var rel = el_node.getAttribute('rel');
					if (rel)
					{
						var matches = (' ' + rel + ' ').match(/\s+Collection\(([^)]+)\)\s+/i);
						if (matches)
						{
							matches = matches[1].split(',');
							for (var k = 0, pos = -1; k < matches.length; k++)
							{
								if (matches[k].charAt(0) == '[' && (pos = matches[k].lastIndexOf(']')) > 0)
								{
									if (new RegExp(unescape(matches[k].substring(1, pos)), matches[k].substring(pos + 1)).test(url))
									{
										active = true;
										break;
									}
								}
								else
								{
									if (/^[\/\\]/.test(matches[k])) matches[k] = window.location.protocol + '//' + window.location.host + matches[k];
									else if (!/^[a-z0-9]+:/i.test(matches[k])) matches[k] = base + matches[k];
									if (unescape(matches[k].replace(/[\/\\]\.([\/\\])/g, '$1').replace(/[^\/\\]+[\/\\]\.\.[\/\\]/g, '').replace(/#.*/, '')) == url)
									{
										active = true;
										break;
									}
								}
							}
						}
					}
				}
				if (active)
				{
					el_node.className = 'active';
					var el_parentNode = el_node;
					do
					{
						el_parentNode = el_parentNode.parentNode;
						if (el_parentNode.nodeName.toLowerCase() == 'ul')
						{
							el_parentNode.style.display = 'block';
							if (document.getElementById(this.id) != el_parentNode) el_parentNode.parentNode.className = el_parentNode.parentNode.className.replace(/(^| +)(opened|closed)( +|$)/g, ' ') + ' opened';
						}
					}
					while (document.getElementById(this.id) != el_parentNode)
				}
			}
			else if (el_node.nodeName.toLowerCase() == 'ul') el_node.style.display = 'none';
			this.start(el_node);
		}
	}
	
	if (document.getElementById && document.childNodes) this.start(document.getElementById(this.id));
}

Następnie wklej w treści nagłówkowej strony <head>...</head> następujący kod:

<link rel="Stylesheet" type="text/css" href="tree.css" />
<script type="text/javascript" src="tree.js"></script>

Na koniec w wybranym miejscu strony - tam, gdzie ma się wyświetlać menu drzewiaste - wstaw kod oparty na technice zagnieżdżania wykazów (tylko wypunktowanie, czyli lista nieuporządkowana <ul>...</ul>). Sposób zagnieżdżania kolejnych punktów listy będzie automatycznie odzwierciedlał strukturę drzewa menu. Oto przykładowy kod:

<ul id="tree0" class="tree">
<li><a href="...">Dokument 1</a></li>
<li><a>Folder 2</a>
	<ul>
	<li><a href="...">Dokument 2.1</a></li>
	<li><a href="...">Dokument 2.2</a></li>
	</ul>
</li>
<li><a>Folder 3</a>
	<ul>
	<li><a href="...">Dokument 3.1</a></li>
	<li><a>Folder 3.2</a>
		<ul>
		<li><a href="...">Dokument 3.2.1</a></li>
		<li><a href="...">Dokument 3.2.2</a></li>
		<li><a href="...">Dokument 3.2.3</a></li>
		</ul>
	</li>
	<li><a href="...">Dokument 3.3</a></li>
	</ul>
</li>
<li><a href="...">Dokument 4</a></li>
</ul>

<script type="text/javascript">
// <![CDATA[
new Tree("tree0");
// ]]>
</script>

Rzeczy, które trzeba tutaj podkreślić:

  • Najbardziej zewnętrzny znacznik <ul>...</ul> powinien mieć przypisaną klasę CSS: class="tree". Dzięki temu punkty menu przyjmą odpowiedni wygląd.
  • Ten sam najbardziej zewnętrzny znacznik <ul>...</ul> musi posiadać atrybut: id="tree0"
  • Zaraz poniżej kodu menu musi się znajdować blok <script type="text/javascript">...</script>. Zauważ, że wyróżniony w nim identyfikator tree0 musi być identyczny z tym, co wpisano jako wartość atrybutu id="..." na początku menu!

UWAGA!
Wewnątrz wszystkich znaczników <li>...</li> muszą znajdować się znaczniki odsyłaczy, czyli: <a>...</a>.
Odsyłacze znajdujące się w węzłach menu (czyli tworzące otwierane i zamykane foldery) nie powinny posiadać atrybutu href="...". Alternatywnie można wpisać tam href="javascript:void(0)" - wtedy również w Internet Explorerze 7.0 i starszych foldery drzewa będą wyglądały i zachowywały się jak odnośniki, a nie jak zwykły tekst.

W jednym dokumencie nie może być dwóch elementów z takim samym atrybutem id="...". Dlatego jeżeli chcemy wstawić drugie menu na tej samej stronie, trzeba dla niego użyć już zmodyfikowanego kodu, np.:

<ul id="tree1" class="tree">
...
</ul>

<script type="text/javascript">
// <![CDATA[
new Tree("tree1");
// ]]>
</script>

Aby menu prezentowało się w pełni funkcjonalnie, trzeba jeszcze przygotować trzy grafiki ikon i zapisać je w tym samym katalogu, co skrypt menu. Na przykład:

  • Zamknięty folder closed.gif - przedstawia ikonę zamkniętego folderu menu,
  • Otwarty folder opened.gif - otwarty folder,
  • Dokument document.gif - dokument.

Jeżeli chcemy w jakiś sposób wyróżnić niektóre dokumenty na liście, można w tym celu przypisać im oddzielną ikonę, która wyraźnie odróżnia się od innych:

<ul id="tree0" class="tree">
<li><a>Folder 1</a>
	<ul>
	<li><a href="...">Dokument 1.1</a></li>
	<li><a href="...">Dokument 1.2</a></li>
	</ul>
</li>
<li><a href="...">Dokument 2</a></li>
<li style="background-image: url(document1.gif)"><a href="...">Dokument 3</a></li>
<li style="background-image: url(document2.gif)"><a href="...">Dokument 4</a></li>
</ul>

Na koniec warto dodać, że opisywany tutaj skrypt posiada system automatycznego wykrywania aktualnie załadowanej strony. Dzięki niemu po wczytaniu dokumentu wybranego na liście, gałąź w drzewie menu w której znajduje się odpowiadający link jest na starcie rozwinięta, a sam link zostaje wyróżniony przy użyciu klasy CSS pod nazwą active, co natychmiast wskazuje użytkownikowi, na której podstronie teraz się znajduje, a także ułatwia nawigację.

Kolekcje dokumentów

Zwykle kiedy publikowany artykuł jest dość długi, dzieli się go na osobne części, dzięki czemu czytelnik nie będzie musiał wczytywać całego tekstu od razu. W takim przypadku w menu umieszcza się najczęściej link tylko do pierwszej części artykułu, a na końcu treści wstawia nawigację stronicującą. Niestety ponieważ adres URL każdej kolejnej części podzielonego artykułu jest różny, właściwa gałąź menu zostanie otwarta i link zaznaczony tylko na pierwszej ze stron. Jeżeli jednak poinformujemy skrypt, które adresy wchodzą w skład kolekcji powiązanych dokumentów, po wejściu na kolejne części artykułu, wszystko będzie działać zgodnie z oczekiwaniami. Aby to zrobić, należy nadać właściwemu odnośnikowi z menu atrybut rel="Collection(...)", w którym podaje się listę adresów URL wszystkich części podzielonego artykułu:

<ul id="tree0" class="tree">
<li><a href="czesc1.html" rel="Collection(czesc2.html,czesc3.html)">Artykuł stronicowany</a></li>
<li><a href="...">Dokument 2</a></li>
<li><a href="...">Dokument 3</a></li>
</ul>

Zwracam uwagę, że w nawiasie nie może być żadnych spacji - nawet po znaku przecinka! Jeśli w nazwie plików z kolekcji znajduje się spacja, należy ją zastąpić przez: %20. Podobnie znaki przecinka, znajdujące się w nazwach plików, należy zastąpić przez %2C, nawias otwierający - %28, nawias zamykający - %29.

Wyrażenia regularne

Występują sytuacje, kiedy dokumentów w kolekcji może być bardzo dużo albo nie wiemy z góry ile, wszystkie jednak pasują do określonego wzorca - np.: /artykul/czesc1.html, /artykul/czesc2.html, /artykul/czesc3.html itd. W takim przypadku dogodniejsze może być dopasowanie adresu przy użyciu wyrażeń regularnych. Aby to zrobić, adres w kolekcji należy objąć nawiasami kwadratowymi, w których wpisuje się treść wyrażenia, np.: rel="Collection([/artykul/czesc\d+\.html])". Również w tym przypadku przypominam, że w nawiasie kolekcji nie może być żadnych spacji, dodatkowych przecinków ani nawiasów okrągłych!