Piero V.

Come fare un menu "espandibile"

C’è un mio amico che si diletta a creare siti web e poi visto che io sono più esperto di lui mi chiede spesso cosa ne penso.

L’altro giorno ha aggiunto al suo sito un menu che con javascript apriva e chiudeva i sottomenu.

Quando mi ha chiesto: “Cosa ne pensi?” gli ho risposto “Una schifezza. Di grafica è anche bello però è anti accessibile…”.

È vero che ormai tutti i browser hanno javascript sempre abilitato, però c’è chi javascript non ce l’ha ed è molto più intransigente dei normali utenti: i motori di ricerca.

Visto che spesso mi fa delle sfide ho deciso di far vedere come creerei io un menu con questo articolo.

Userò un po’ di HTML, la proprità display di CSS e Javascript, precisamente il DOM e l’evento onclick.

La struttura HTML è molto semplice, anche se deve essere per forza fatta così: ogni menu è una lista ul e i sottomenu hanno un elemento span che fa da loro titolo.

È necessario che l’ul principale abbia un id, il resto viene estrapolato col DOM. Perciò si possono fare infiniti sotto menu.

Ecco un HTML di esempio:

<ul id="miomenu">
	<li>
		<span>Guide</span>
		<ul>
			<li>
				<span>Programmazione</span>
				<ul>
					<li>C</li>
					<li>C++</li>
				</ul>
			</li>
			<li>
				<span>Web Developing</span>
				<ul>
					<li>
						<span>HTML</span>
						<ul>
							<li><a href="cominciare.html">Per cominciare</a></li>
							<li><a href="tag.html">I tag</a></li>
						</ul>
					</li>
					<li>
						<span>PHP</span>
						<ul>
							<li><a href="interprete.html">L'interprete</a></li>
							<li><a href="database.html">I database</a></li>
						</ul>
					</li>
				</ul>
			</li>
		</ul>
	</li>
	<li><a href="forum.html">Forum</a></li>
	<li><a href="contattami.html">Contattami</a></li>
</ul>

Questo codice è stato validato con successo dal W3C e se lo mettete ora come ora in un browser non è male, basta personalizzarlo un po’ con i CSS.

Ora viene la parte di javascript.

Visto che ci servirà più volte aprire o chiudere un menu, creiamo una funzione apposita che possa riciclare il codice.

Per motivi pratici la farò che si basa sullo span: è molto semplice trovare così l’ul del menu e ogni volta assegnerò una classe: aprimenu e chiudimenu.

Questa funzione se i menu sono aperti li chiude, mentre se sono chiusi li apre, con un’eccezione che potremo passare con un parametro alla funzione. Voi fidatevi 😉

/**
 * Questa funzione apre o chiude un menu partendo dallo span
 * che è il suo  titolo.
 *
 * @param object span: Lo span
 * @param boolean rimani: Forzare il menu a rimanere aperto?
 */
function aprichiudiMenu(span, rimani) {
	// Analizziamo il parametro rimani
	if(typeof(rimani)=='undefined') {
		rimani=false;
	}

	// Otteniamo il li genitore
	li=span.parentNode;

	// Otteniamo il menu
	menu=li.getElementsByTagName('ul')[0];

	// In base alla proprietà CSS display lo apriamo/chiudiamo
	if(menu.style.display=='none') {
		menu.style.display='block';
		span.className='chiudimenu';
	} else if(!rimani) {
		menu.style.display='none';
		span.className='aprimenu';
	}
}

Il codice è commentato, perciò non lo descrivo ulteriormente.

Poi c’è la funzione da eseguire all’inizio per chiudere tutti i sottomenu: anche questa è commentata passo-passo, perciò leggete i passaggi direttamente lì:

/**
 * Questa funzione eseguita all'avvio chiude tutti i menu e associa
 * agli span una callback per l'evento click.
 */
function inizializzaMenu() {
	// L'id del menu
	id='miomenu';

	// Con l'id trovo il menu
	menu=document.getElementById(id);

	// Ottengo tutti i menu con gli span
	span=menu.getElementsByTagName('span');

	for(i=0; i<span.length; i++) {
		// Chiudo i menu
		aprichiudiMenu(span[i]);
		// Associo all'evento click dello span una callback
		span[i].onclick=clickMenu;
	}
}

Come vedete fa uso della funzione che avevo scritto sopra ma non solo: aggiunge il callback agli span nell’evento click che consente di aprire e chiudere i menu: eccola qua: (ringrazio Quirksmode perché è loro il codice per trovare lo span cliccato)

/**
 * La callback per il click sugli span.
 *
 * @param object e: L'evento
 */
function clickMenu(e) {
	// Otteniamo lo span cliccato.
	// Codice di http://www.quirksmode.org/js/events_properties.html
	if(!e) {
		e=window.event;
	}
	if(e.target) {
		targ=e.target;
	} else if(e.srcElement) {
		targ=e.srcElement;
	}
	// Bug di safari
	if(e.nodeType==3) {
		targ=targ.parentNode;
	}

	// Ora che abbiamo lo span chiamiamo la solita funzione
	aprichiudiMenu(targ);
}

Come vedete, una volta rintracciato lo span cliccato è una cavolata darlo in pasto alla prima funzione che ho scritto.

La parte script volendo è quasi terminata: ora basta aggiungere sotto al menu nell’HTML una chiamata alla funzione inizializzaMenu:

<script type="text/javascript" >
inizializzaMenu();
</script>

Tuttavia noi vogliamo andare oltre: fare in modo che in certe pagine si apra un menu: allora ci serve una funzione abbastanza “economica” e facile da chiamare che faccia questo lavoro.

Ho pensato che si può passare una stringa con dei numeri separati da . che simboleggiano ogni sotto menu.

Per esempio se voglio aprire il Guide -> Web Developing -> PHP devo passare alla funzione la stringa 1.2.2.

Fate attenzione però perché qui dovete contare, partendo da 0, anche le voci finali (quelle con il link anziché con lo span).

Ecco la funzione:

/**
 * Questa funzione permette di aprire o chiudere un menu partendo da
 * una stringa di identificazione.
 *
 * La stringa deve essere nel formato N[.N.N] dove N è il numero di li.
 * Questo numero parte da 1 e bisogna anche contare i li con i link normali.
 *
 * @param string menu: L'id del menu (vedi la descrizione)
 */
function apriMenuDaId(id) {
	// L'id del menu principale
	idmenu='miomenu';

	// Otteniamo il menu
	principale=document.getElementById(idmenu);

	// Otteniamo i menu su cui lavorare
	lavoro=id.split('.');

	// Ogni volta che scendiamo di livello aggiorniamo questa variabile
	menu=principale; // Però per il momento mettiamo il menu principale

	for(i=0; i<lavoro.length; i++) {
		// Il li che contiene lo span e il menu
		k=0;
		j=1;
		while(j<=lavoro[i]) {
			li=menu.childNodes[k];
			// Solo i veri li
			if(li.nodeType==1) {
				j++;
			}
			k++;
		}
		// Lo span da usare con la solita funzione
		span=li.getElementsByTagName('span')[0];
		aprichiudiMenu(span, true);
		// Scendiamo di livello del menu
		menu=li.getElementsByTagName('ul')[0];
	}
}

Questa funzione è leggermente più complicata: come al solito ho trovato lo span. Però per farlo prima ottengo un array dall’id (con split) quindi con un for analizzo questo array.

Per trovare gli span ho deciso di usare il childNodes perché il getElementsByTagName non restituisce solo i figli. Però per trovare il li col childnodes ho dovuto utilizzare quel while perché avevo qualche problema.

Dopo ottenuto il li è molto facile ottenere lo span e l’ul, che sarà il menu che analizzeremo nella successiva iterazione del for.

Un’ultima considerazione: è in questa funzione che ho utilizzato il secondo parametro di aprichiudiMenu così risulta possibile aprire più menu.

Ecco un esempio (da aggiungere sotto il menu o altrimenti quando il dom è pronto):

<script type="text/javascript" >
inizializzaMenu();
apriMenuDaId('1.2.1');
apriMenuDaId('1.2.2');
</script>

Come vedete il codice è abbastanza semplice.

Se al posto dell’id avreste voluto usare una classe avreste dovuto usare una delle molte funzioni per trovare gli elementi con la classe.

Altrimenti, datevi a Sizzlejs, lo script per i selettori usato da jQuery 😊

2 commenti