Navigationsmenüs unter WordPress

Mit der Version 3 von Wordpress kam ein ziemlich cooles Feature hinzu, welches einem gerade auf nicht-nur-Blog-Seiten sehr gelegen kam: die „Nav-Menus“ oder: die Möglichkeit die Navigation aus dem Backend zu steuern. Endlich muss ich nicht mehr in den Templates rumschreiben, wenn ich die Navigation ändern will. Die dazu passende Template-Funktion heißst wp_nav_menus(), wird im Header- oder Sidebar-Template (oder wo man es halt möchte) untergebracht und nimmt wie gewohnt einige Parameter in Form einens Arrays entgegen um z.B. HTML-Attribute wie Titel, ID und Klassen zu vergeben. Das role-Attribut ist leider nicht vorgesehen. Ich wollte schon nach den passenden Filtern suchen, um dieses Attribut mit einfügen zu können, als ich spaßeshalber folgendes probierte:

wp_nav_menu ( 
	array (
		'menu'       =>; 'main_navigation',
		'container'  =>; '',
		# Seit Version 3.1 geht das hier nicht mehr, Nachtrag beachten!
		'menu_id'    =>; 'navigation" role="navigation',
		'menu_class' =>; ''
	)
);

Zu meinem Erstaunen funktioniert das so einwandfrei. Ich kann mir zwar kaum vorstellen, dass das so gewollt ist und dauerhaft funktionieren wird, aber wenn es so einfach ist, braucht es vorerst keinen zusätzlichen Filter. (Zur Erläuterung: die Funktion zeigt das Menü mit dem Namen „main_navigation“ ohne umschließendes Element und ohne eine Klasse an, allerdings mit dem ID-Wert: navigation" role="navigation woraus der Quelltext

<ul id="navigation" role="navigation"></ul>

resultiert.) Der Wert wird also ungefiltert übernommen, also aufpassen, was man reinschreibt.

Nachtrag:

Achtung! Spätestens seit Version 3.1 funktioniert das nicht mehr (wahr auch zu erwarten). Hier kann man zu folgender kurzer Funktion greifen, die man in den Filter wp_nav_menu einhängt:

/**
 * fügt ein role-Attribut mit dem Wert 'navigation'
 * dem Ersten öffnenden HTML-Tag von $a
 * hinzu
 */
function mytheme_add_nav_role( $a ) {

    $a = preg_replace(
		'#^\s*<([a-z]+)([^>]*)>#i',
		'<${1}${2} role="navigation">',
		$a
	);

    return $a;
}
add_filter( 'wp_nav_menu', 'mytheme_add_nav_role' );

Seitenauswahl einrücken

Jetzt aber ab ins Backend und das Menü anlegen um zu sehen, wie das funktioniert. In den meisten Fällen wird die Navigation sicher die Stuktur der angelegten Seiten abbilden. Warum die Seiten also nicht in ihrer hirarchischen Anordnung sondern in einer einfachen Liste, ihrem Titel nach sortiert, dargestellt werden, ist mir ein Räzel. Spätestens wenn die Seiten nicht mehr allein durch ihren Titel sondern durch die Beziehung zu anderen Seiten identifizierbar sind, steht man vor einem echten Problem.

Dabei ist der Core-Code nur eine Zeile von der Lösung entfernt. Da in diesen untiefen des Wordpress-Codes keine Möglichkeit besteht über die Plugin-API einzugreifen, habe ich die entsprechende Funktion und eine Klasse kurzerhand in einem Plugin geklont. Jetzt existiert eine weitere Metabox in der das Chaos weitgehend gebannt ist.

Das Plugin trägt den schönen Namen „nav-menu-page-tree“ und kann hier herunter geladen werden:

nav-menu-page-tree.zip.

Nachtrag
Mit Version 3.2 werden die existierenden Seiten von WordPress selbst auch hirarchisch geordnet dargestellt, womit das Plugin obsolet geworden ist.

Markup aufräumen

Das durch wp_nav_menus() erzeugte Markup der Navigation im Frontend ist gewohnt tadellos, allerdings reichlich überladen. Jedes Listenelement hat eine ID und mehrere Klassen. Darauf kann ich getrost verzichten. Im Falle der ID geht das ganz unkompliziert mit folgendem Filter:

add_filter( 'nav_menu_item_id', create_function( '$a', ' return ""; ') );

Bevor Worpress das ID-Attribut in den Quelltext einfügt, wird geprüft, ob der Wert überhaupt existiert. Wir haben ihn hier gerade gelöscht. Bei den Klassen funktioniert das seltsamer weise nicht. Nachdem ein Filter die Klassen mit einem leeren String überschrieben hat, müssen vor der Ausgabe des Markups die leeren Attribute entfernt werden:

add_filter( 'nav_menu_css_class', create_function( '$a',' return array();' ) );
add_filter( 'wp_nav_menu', create_function( '$a', 'return str_replace( " class=\"\"","", $a );' ) );

Diese zwei Maßnahmen sparen unter Umständen einen ganze Menge Quelltext. Man kann sich jetzt drüber streiten, ob die Einsparung der Datenmenge die längere Scriptlaufzeit aufhebt, aber als Freund sauberen Quellcodes ist mir das allemal lieber.

Die Funktion wp_nav_menus() schicke ich in den Templates überdies noch durch Thomas' Funktion remove_self_links() um Links zu entfernen die auf die aktuell angezeigte Seite zeigen. Das klappt hoffentlich auch irgendwann mal von Hause aus.

Kommentare

  1. 01) 04.12.2010
    mike

    Prima Plugin, gerade getestet, und läuft (auf 3.0.1).

    Wo ich einmal hier bin, passt thematisch nicht ganz, aber auf meiner Suche nach einer Lösung für mein Problem bin ich aus Versehen genau hier gelandet ;)

    Ich suche eine Möglichkeit mittels nav-menus (als widget) nur Parents anzuzeigen und die Kinder nur bei aktiven Punkten.
    Hast Du da eine Idee dazu?

  2. 02) 04.12.2010
    David

    Von der trivialen und eher geflickschusterten Lösung via CSS abgesehen, dachte ich spontant an wp_list_pages. Ich erinnerte mich an solche Parameter wie „exclude_tree“, leider gibt es kein gegenteiligen Parameter à la „include_tree“.
    Das wäre ja auch zu schön gewesen.

    Man müsste wohl einen eigenen Walker schreiben, der die gewünschte Tiefe nur dort auslotet, wo die Parent-ID gleich der ID der angeschauten Seite ist. So genau hab ich mich in diese Walker-Thematik aber noch nicht eingelesen.

    Das Widget würde dann auf das entsprechende Nav-Menu mit eben dieser neuen Walker-Klasse zugreifen. Theoretisch.

  3. 03) 05.12.2010
    mike

    Vielen Dank für Deine Rückmeldung dazu.
    Klingt etwa so, wie ich es mir vorgestellt hatte; es gibt bei WP(3) hybsche und einfache Klickibunti-lösungen für Chef-sekretärinnen, die nicht wirklich was taugen.
    Schade. Mal schauen ob ich noch was brauchbares finde in der Richtung, gebe dann hier Feedback.

  4. 04) 05.12.2010
    David

    Ja, man könnte auch sagen, WordPress ist praxisorientiert umgesetzt. Die Hauptnavigation würde ich auf jeder Seite gleich lassen. Entweder man setzt gleich auf eine mehrdimensionale Navigation und gestaltet diese z.B. als Dropdown, oder man zeigt die Unterseiten als gesonderte Liste an. Das ist für meine Begriffe logischer, als wenn sich die Hauptnavigation von Seite zu Seite ändert.

    Letzeres wäre auch leicht mit einem Widget umzusetzen, dass die Funktion wp_list_pages aufruft:

    $page_parents = get_the_ancestors();
    if(!empty($page_parents)) {
          $top_parent_id = $page_parents[0];
    }
    else {
          $top_parent_id = get_the_ID();
    }
    wp_list_pages( array ( 'child_of' => $top_parent_id) );
    

Fragen, Ideen oder Kritik? – Hier ist Platz dafür!

Dein Kommentar

Um ein Kommentar abzugeben, reicht der Text im Kommentarfeld. Die Angabe eines Namens wäre nett, ist aber nicht erforderlich.

Du darfst folgenden HTML-Code verwenden, musst aber nicht:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>