Understanding the Walker Class

Understanding the Walker Class

Tutorial Details
  • Program: WordPress
  • Version: 3.0
  • Difficulty: Easy
  • Estimated Completion Time: 30 minutes

Menu items, pages and (hierarchical) taxonomies are all examples of data with a tree like structure: terms can have parents, children and siblings. Usually we would like to reflect this structure in the HTML markup. For displaying a menu, for instance, we want the HTML to be of a list of ‘top level’ links, with nested lists of their children, which themselves contain nested lists of their children, and so on. This tutorial will guide you through a class WordPress provides which makes producing this mark-up extremely simple.


What Is the Walker Class?

The walker class is an abstract class designed to help traverse and display elements which have a hierarchical (or tree like) structure. It doesn’t actually ‘do’ (in the sense of generating HTML) anything. It simply traces each branch of your tree: it has to be extended by other classes which tell it what to do for each element it comes across. WordPress provides its own extending classes, such as:

Each of these classes extend the Walker class by simply dictating what the class outputs at each element and level of the tree. In order to de-mystify this class we shall look at its main methods and a couple of examples of how to use it. The class itself can be found here.


Walking the Tree

Walk

walk( $elements, $max_depth)

The walker class gets kicked off with the walk method and it’s this method which returns the HTML once it’s been generated. It accepts two arguments:

  1. An array of elements that we wish to display, which will have some sort of parent-child relationship
  2. $max_depth – sets how many generations we explore
  3. Ok 3… If you scratch the surface of this method you’ll find you can actually pass extra arguments which get gathered into an array: $args. This is then passed to other methods in the class

The walk method singles out the ‘top level’ elements – those without parents – and places them in one array. The rest, the children, are placed in a second array where the key is the ID of its parent (it’s a two dimensional array as one parent can have multiple children):

$children_elements = array(
	'1' => array() //Array of elements corresponding to children of 1,
	'4' => array() //Array of elements corresponding to children of 4
);

It then loops through each of the parent elements in turn and applies the method display_element.

Display_Element

display_element( $element, &$children_elements, $max_depth, $depth=0, $args, &$output )

As the name suggests display_element is responsible for displaying an element in our tree. In fact, it calls several functions to do this. These functions are deliberately left blank in the Walker class – and it’s these which are altered in the extending classes, as they determine the actual HTML returned. These include:

  • start_lvl – a function to return the HTML for the start of a new level. In the case of lists, this would be the start of a new ‘sub-list’, and so would be responsible for returning the <ul> tag
  • end_lvl – called when we have finished a level. In the navigation menu example, this function is responsible for ending the sub-list with a closing list tag </ul>
  • start_el – the function responsible for displaying the current element we are on. In the case of menus, this means the <li> tag and the item’s link.
  • end_el – the function called after an element and all it’s children have been displayed. For our menu example this means returning a closing </li> tag.

So what does display_element actually do? It’s actually where all the magic of the Walker class takes place. First lets take a look at what arguments it’s given:

  • $element – this is the element we are currently at on our tree
  • $children_elements – an array of all child elements (not just children of the element referred to above). This is the second array formed in the walk method and the keys are the IDs of the parent.
  • $max_depth – how far down we are allowed to explore
  • $depth – how far down we currently are
  • $args – optional arguments (mentioned earlier)
  • $output – The HTML thus far. This is added to as we explore more of the tree.

The display_element method first calls start_el which is responsible for displaying the element. Exactly how it does that depends on the context. For a drop-down menu it may be <select> Current Item or for a navigation menu it may <li> Current Item. Notice that there is no closing tag yet. If this element has children, we need to display them first so to that they are nested inside this item…

So next it checks if the current element we are on has any children and that we have not reached the maximum depth. If so, we explore each of the children in turn, by calling display_element for each of them (with the depth argument incremented by one). In this way the display_element recursively calls itself until we reach the bottom.

Suppose we have reached the ‘bottom’ (an element with no children or the maximum depth), then it calls end_el which adds the closing tag. There the current instance of display_element finishes and we move back up to the parent who applies display_element to the next child, until we’ve processed each of its children. When the parent has no more children left we move back up the tree, and so on until every branch is explored. Confused? He’s a diagram which I hope will clarify things:


Using the Walker Class: A Simple Example

Using the Walker class makes displaying custom hierarchal data very simple. Suppose you have an array of objects, with ‘label‘, ‘parent_id‘ and ‘object_id‘ properties that you wish to display a list of. This can now be easily be accomplished with a very simple class:

Note: The extending class is responsible for setting where to find an element’s ID and that of its parent.

class Walker_Simple_Example extends Walker {

	// Set the properties of the element which give the ID of the current item and its parent
	var $db_fields = array( 'parent' => 'parent_id', 'id' => 'object_id' );

	// Displays start of a level. E.g '<ul>'
	// @see Walker::start_lvl()
	function start_lvl(&$output, $depth=0, $args=array()) {
		$output .= "\n<ul>\n";
	}

	// Displays end of a level. E.g '</ul>'
	// @see Walker::end_lvl()
	function end_lvl(&$output, $depth=0, $args=array()) {
		$output .= "</ul>\n";
	}

	// Displays start of an element. E.g '<li> Item Name'
	// @see Walker::start_el()
	function start_el(&$output, $item, $depth=0, $args=array()) {
		$output. = "<li>".esc_attr($item->label);
	}

	// Displays end of an element. E.g '</li>'
	// @see Walker::end_el()
	function end_el(&$output, $item, $depth=0, $args=array()) {
		$output .= "</li>\n";
	}
}
$elements=array(); // Array of elements
echo Walker_Simple_Example::walk($elements);

Using the Walker Class: Advanced Example

You can extend the walker classes to change what content is displayed, alter the HTML generated or even prevent certain branches from being shown. Functions such as:

Provide an option to specify your own custom Walker class – allowing you to alter their appearance with relative ease by specifying your own custom walker class. In many instances it is actually easier to extend an appropriate walker extension, rather than the Walker class itself.

Suppose you want to have a secondary (sub) menu that is related to your primary menu. This may take the form of links that sit just below your primary menu or in a side-bar which show only the ‘descendant’ menu items of the current ‘top-level page’. As an example from the diagram above, if we’re on the ‘Archive’, ‘Author’, or ‘News’ sub pages, we would like to show all of the links below ‘Archive’. Since Walker_Nav_Menu does most of what we want, we shall extend that class rather than the Walker class. This saves us a lot of effort, since the Walker_Nav_Menu adds the appropriate classes (‘current‘, ‘current-ancestor‘ etc) to the relevant links. We shall extend the Walker_Nav_Menu walker class to alter the logic slightly, and prevent it from displaying any top-level links or any of the descendants of the ‘non-root’ pages.

Some Ground Work: Theme Locations

First of all, in your template files, we will use the wp_nav_menu() function twice, pointing to the same theme location (I shall call it ‘primary‘). If you don’t have a theme location registered already you should read this article. Whichever theme location you are using, you should save a menu to that location. We shall display this menu twice. First, wherever you want your ‘top-level’ menu to appear:

wp_nav_menu( array('theme_location'=>'primary','depth' => 1) );

Then again, with a custom walker, to display only the (relevant) child pages.

 wp_nav_menu( array('theme_location'=>'primary','walker' => new SH_Child_Only_Walker(),'depth' => 0) );

Extending the Walker

First of all we don’t want to display top-level parents. Recall that the function responsible for the opening <li> tag and the link is start_el and the function responsible for the closing </li> tag is end_el. We simply check if we are at the parent level. If we are, we don’t do anything. Otherwise, we continue ‘as normal’ and call the function from the Walker_Nav_Menu class.

// Don't print top-level elements
function start_el(&$output, $item, $depth=0, $args=array()) {
	if( 0 == $depth )
		return;
	parent::start_el(&$output, $item, $depth, $args);
}

function end_el(&$output, $item, $depth=0, $args=array()) {
	if( 0 == $depth )
		return;
	parent::end_el(&$output, $item, $depth, $args);
}

We extend the display_element. This function is responsible for traveling down the branches. We want to stop it in its tracks if we are at the top-level and not on the current root link. To check if the branch we are on is ‘current’, we check if the item has any of the following classes: ‘current-menu-item‘, ‘current-menu-parent‘, ‘current-menu-ancestor‘.

// Only follow down one branch
function display_element( $element, &$children_elements, $max_depth, $depth=0, $args, &$output ) {

	// Check if element as a 'current element' class
	$current_element_markers = array( 'current-menu-item', 'current-menu-parent', 'current-menu-ancestor' );
	$current_class = array_intersect( $current_element_markers, $element->classes );

	// If element has a 'current' class, it is an ancestor of the current element
	$ancestor_of_current = !empty($current_class);

	// If this is a top-level link and not the current, or ancestor of the current menu item - stop here.
	if ( 0 == $depth && !$ancestor_of_current)
		return;

	parent::display_element( $element, &$children_elements, $max_depth, $depth, $args, &$output );
}

We now extend the start_lvl and end_lvl functions. These are responsible for outputting the HTML that wraps a level (in this case the <ul> tags). If we are on the top level we don’t want to display these tags (after all the contents will not be shown).

// Don't wrap the top level
function start_lvl(&$output, $depth=0, $args=array()) {
	if( 0 == $depth )
		return;
	parent::start_lvl(&$output, $depth, $args);
}

function end_lvl(&$output, $depth=0, $args=array()) {
	if( 0 == $depth )
		return;
	parent::end_lvl(&$output, $depth, $args);
}

That class in full:

class SH_Child_Only_Walker extends Walker_Nav_Menu {

	// Don't start the top level
	function start_lvl(&$output, $depth=0, $args=array()) {
		if( 0 == $depth )
			return;
		parent::start_lvl(&$output, $depth,$args);
	}

	// Don't end the top level
	function end_lvl(&$output, $depth=0, $args=array()) {
		if( 0 == $depth )
			return;
		parent::end_lvl(&$output, $depth,$args);
	}

	// Don't print top-level elements
	function start_el(&$output, $item, $depth=0, $args=array()) {
		if( 0 == $depth )
			return;
		parent::start_el(&$output, $item, $depth, $args);
	}

	function end_el(&$output, $item, $depth=0, $args=array()) {
		if( 0 == $depth )
			return;
		parent::end_el(&$output, $item, $depth, $args);
	}

	// Only follow down one branch
	function display_element( $element, &$children_elements, $max_depth, $depth=0, $args, &$output ) {

		// Check if element as a 'current element' class
		$current_element_markers = array( 'current-menu-item', 'current-menu-parent', 'current-menu-ancestor' );
		$current_class = array_intersect( $current_element_markers, $element->classes );

		// If element has a 'current' class, it is an ancestor of the current element
		$ancestor_of_current = !empty($current_class);

		// If this is a top-level link and not the current, or ancestor of the current menu item - stop here.
		if ( 0 == $depth && !$ancestor_of_current)
			return

		parent::display_element( $element, &$children_elements, $max_depth, $depth, $args, &$output );
	}
}

Once you understand how the walker class works you can extend it (or WordPress’ existing extensions) to alter how your hierarchal data is displayed. For instance you can:

  • Include descriptions with menu links or category descriptions.
  • Exclude whole branches of a menu for logged-out users.
  • Include post meta in your list of pages.
Note: Want to add some source code? Type <pre><code> before it and </code></pre> after it. Find out more
  • http://www.innovativephp.com/tutorials/ Rakhitha Nimesh

    Exploring wordpress core is always been my dream. This article simplifies some of the advanced concepts in Walker class.

    Please include more practical usages and situations where we can use Walker class.

    Thanks for sharing and keep up the good work in the future.

    • http://stephenharris.info Stephen Harris
      Author

      Thanks! The walker class is useful whenever there is a parent-child structure to data that you wish to display. In addition to the above examples, I recently used the walker class to produce a breadcrumb which traced the path to the current page via the main menu (as opposed to a category or page breadcrumb).

      Of course, they’re are other ways of achieving this – but this method turned out to be rather simple.

  • http://www.damiencarbery.com/2011/10/adding-keywords-to-your-wordpress-menu/ Damien Carbery

    I used the walker_nav_menu_start_el filter to add keywords to the title attribute of all menu items. I saw some html with this done for SEO.
    // Append some text to the title attribute in menu links.
    add_filter( ‘walker_nav_menu_start_el’, ‘wpb_nav_menu_item’, 20, 4 );
    function wpb_nav_menu_item($item_output, $item, $depth, $args) {
    $extra_title = ‘EXTRA BIT’;

    if (strpos($item_output, ‘title=’)) {
    // Append $extra_title to the existing title text.
    return preg_replace(‘/title=\”([^\"]*)\”/’, ‘title=”\1 – ‘.$extra_title.’”‘, $item_output, 1);
    }
    else {
    // Create title text made up of menu text plus $extra_title.
    return str_replace(‘href=’, ‘title=”‘.$item->title.’ – ‘. $extra_title.’” href=’, $item_output);
    }
    }

    Using the filter was easier than extending the Walker_Nav_Menu. I didn’t use the “the_title” filter as that is used in other places in the WordPress core code.

    In another instance I had to extend Walker_Nav_Menu – I wanted to add a class to top level menu items and a different one for other menu items.
    There is a filter (nav_menu_css_class) to add css classes but it does not provide the menu item depth. I had to copy start_el function from nav-menu-template.php just to add the following few lines:

    // Add a class to the link, depending on a top level or sub menu item.
    if ($depth == 0)
    $attributes .= ‘ class=”top-a”‘;
    else
    $attributes .= ‘ class=”sub”‘;
    Some day I will submit a Trac change request to ask for $depth to be added to the nav_menu_css_class filter.

  • Pingback: Understanding the Walker Class | Qtiva

  • Pingback: WordPress: What you need to know about the Walker Class | WPAOT

  • http://twitter.com/twittem Edward McIntyre

    I recently hacked my way through the walker class to add the Twitter Bootstrap drop downs to my theme. It’s nice to see the walker structure explained.

    • http://stephenharris.info Stephen Harris
      Author

      Thanks! A third-party menu plug-in such as the Twitter Bootstrap drop-down, is an excellent example of when a custom walker class would be needed – since there aren’t the necessary filters in Walker_Nav_Menu.

    • http://28inch.co.uk 28inch

      Same here, I`ve seen a custom walker int the Roots theme first. It`s brilliant! I`m just about to release a very simple widget to display custom navigation in any widget area. To do so, I had to strip down all the menu specific classes and id`s to get a plain ul->li structure.

      Not to mention that you can add browser specific classis to your nav, like ‘last-child’ for IE.

      I love this article by polygonal labs on different data structures(tree,graph,queue,linked lists)
      http://lab.polygonal.de/?p=206

      The code samples are in action script but the explanation is still understandable.

  • http://www.damiencarbery.com Damien Carbery

    http://www.damiencarbery.com/2011/10/adding-keywords-to-your-wordpress-menu/
    I used the walker_nav_menu_start_el filter to add keywords to the title attribute of all menu items. I saw some html with this done for SEO.
    // Append some text to the title attribute in menu links.
    add_filter( ‘walker_nav_menu_start_el’, ‘wpb_nav_menu_item’, 20, 4 );
    function wpb_nav_menu_item($item_output, $item, $depth, $args) {
    $extra_title = ‘EXTRA BIT’;

    if (strpos($item_output, ‘title=’)) {
    // Append $extra_title to the existing title text.
    return preg_replace(‘/title=\”([^\"]*)\”/’, ‘title=”\1 – ‘.$extra_title.’”‘, $item_output, 1);
    }
    else {
    // Create title text made up of menu text plus $extra_title.
    return str_replace(‘href=’, ‘title=”‘.$item->title.’ – ‘. $extra_title.’” href=’, $item_output);
    }
    }

    Using the filter was easier than extending the Walker_Nav_Menu. I didn’t use the “the_title” filter as that is used in other places in the WordPress core code.

    In another instance I had to extend Walker_Nav_Menu – I wanted to add a class to top level menu items and a different one for other menu items.
    There is a filter (nav_menu_css_class) to add css classes but it does not provide the menu item depth. I had to copy start_el function from nav-menu-template.php just to add the following few lines:

    // Add a class to the link, depending on a top level or sub menu item.
    if ($depth == 0)
    $attributes .= ‘ class=”top-a”‘;
    else
    $attributes .= ‘ class=”sub”‘;
    Some day I will submit a Trac change request to ask for $depth to be added to the nav_menu_css_class filter.

    • http://stephenharris.info Stephen Harris
      Author

      Thank you for sharing. I agree – if you can, use the filters. It is much simpler, and you can always use the $args variable to restrict the alterations to a specific menu location.

      Of course the Walker_Nav_Menu class adds the class sub-menu to all ul elements that list child items – so if you only wanted to distinguish between items on different levels, then you could use that.

  • http://www.customicondesign.com CUSTOM ICON DESIGN

    it’s the deep tutorial in wp. I didn’t notice this before. I like the article like this.

  • http://madebyraygun.com Dalton

    Thanks, this came at just the right time for me, as I’m building a plugin that relies heavily on custom taxonomies and I need to output hierarchical menus all over the place. This tutorial was easy to follow and helped me a lot!

  • Pingback: WordPress Community Roundup for the Week Ending May 19 - Charleston WordPress User Group

  • Pingback: WordPress: Individuelle Menü-Ausgabe - Smart-Webentwicklung

  • Pingback: Function Examination: wp_nav_menu | Wptuts+

  • Pingback: Function Examination: wp_nav_menu | Wordpress Webdesigner

  • Pingback: Function Examination: wp_nav_menu | Shadowtek Hosting and Design Solutions

  • Pingback: My Stream | Function Examination: wp_nav_menu | My Stream

  • Pingback: Function Examination: wp_nav_menu | How to Web

  • Pingback: Wordpress Leaks » Function Examination: wp_nav_menu

  • BIOS

    First of all great tutorial! First time I’ve seen a clear explanation of the walker class so kudos!

    I am having issues with your class extension however. Instead of giving me a list of child pages of the current page as expected, this just blanks out my admin wp navigation as well as my footer navigation :?

  • BIOS

    Got it working! There are some typos in the above.

    In your function display_element:

    ‘$ancestor_of_current = !emptyempty($current_class); ‘

    And a missing semi-colon after the return in the final if statement.

    Thanks again for the awesome tutorial!! :)

    • http://stephenharris.info Stephen Harris
      Author

      Thanks for posting corrections! I’m glad you liked the tutorial. Something like ‘emptyempty’ appeared in another tutorial on this site – Japh mentioned that there is a bug in the syntax highlighting. They’re working on a fix – so it shouldn’t happen again!

      • http://wp.envato.com/ Japh Thomson
        Staff

        Quite right. Sorry about this being a continuing issue. You’d think fixing that up would be a straightforward issue, but it’s actually more complex than it seems and we’re still working on it.

  • BIOS

    I have one question. How can I modify the walker to play just third class menu items?

    I’ve tried adding:

    if( $depth == 0 || $depth == 1)

    to all the if conditionals to hide the second level items but this produces no output even with third level items in my navigation.

    Strangely if i change the conditional to:

    if($depth == 1)

    I outputs the top level menu items and the third level menu items hiding the the second level menu items :?

    Any help would be greatly appreciated!

    Cheers

    BIOS

    • http://stephenharris.info Stephen Harris
      Author

      You mean you want to display only items three levels deep? You don’t want to use the conditional in <code. display_element since you still want to traverse the tree (or at least one branch of it). But you want to use the conditional in functions start_lvl, start_el etc to add output only when you've descended to the third level.

      Hope that helps!

  • Pingback: Wordpress 深度解析wp_nav_menu函数功能 – 开源共享

  • Sacha

    Wonderful is enough?
    Simple and clean, the kind of stuff I like. Thanks for the tutorial.

    Ciao

  • Mike Garrett

    Syntax error line 21 of Using the Walker Class: A Simple Example.

  • David Plunkett

    This example is exactly what I am looking for, but I cannot get it working :-( When I paste the code into my functions.php I get an error that says:

    SCREAM: Error suppression ignored for
    ( ! ) Fatal error: Call-time pass-by-reference has been removed

    The problem is being caused by:

    parent::start_lvl(&$output, $depth,$args);

    Any ideas?

  • Sambinomio

    Awesome tutorial… thank you for this.

  • http://www.edwardbeckett.com Edward Beckett

    Excellent tutorial … great work …

    I just want to add that calling the parent::method isn’t necessary when over-riding existing class methods …

    I.E.

    class My_Nav_Walker extends Walker_Nav_Menu {

    function start_lvl( &$output, $depth = 0, $args = array() ) {
    $indent = str_repeat(“\t”, $depth);
    $output .= “\n$indent\n”;

    }

    }

    • http://stephenharris.info Stephen Harris

      Hi Edward,

      You’re right, its not necessary. But I didn’t really want to over-ride the parent class, just prevent methods from triggered in certain contexts. By using parent::method it means the underlying parent class can be changed – and those changes will propagate through to the child. This way you can easily port the class to other parents.

      • http://www.edwardbeckett.com Edward Beckett

        Great work buddy … the double menu idea is pretty slick … :-) I’m working on a pretty tough walker right now … I need child ‘containers’ to have siblings … I.E. parent->child->child-container->sib-1->sib-2->sib3 … it’s kinda tough … I was thinking about making a nav_menu_item_group object so I can filter siblings elements …

        I come from a ColdFusion background and I’m not all that hot at php yet … I’ll get quite a learning on this one … :-)

        Awesome post!

  • gomymusic

    How I can add a class only to the top level parent category?

  • Brandon

    Are there any source Files. I can’t figure out what files to save the examples into my theme or what is the expected output…

    Please be of some help,

    Brandon

    • David Hlouch

      Brandon, try to put the custom walker class SH_Child_Only_Walker extends Walker_Nav_Menu { ... in your functions.php file in your active theme.

      Then just call the menu within your theme, e.g. in header.php with


      wp_nav_menu( array('theme_location'=>'primary','walker' => new SH_Child_Only_Walker(),'depth' => 0) );

      • Brandon

        Okay, all is well except I get this error from my functions.php file

        Missing argument 2 for Walker::walk()

        The two lines of code :

        $elements=array(); // Array of elements
        echo Walker_Simple_Example::walk($elements);

        … and do i need to add $children_elements into functions.php ?

        Thanks alot.

  • Niels

    Hi,

    I expect it’s a nice walker; but it doesn’t work at all..
    I don’t use the “menu” option but use pages as menu…Maybe that’s the difference???

    The error message is:
    Warning: array_intersect() [function.array-intersect]: Argument #2 is not an array in…..

    When I do a print_r of the $element array in the Function display_element, the result is:
    WP_Post Object
    (
    [ID] => 29
    [post_author] => 1
    [post_date] => 2013-02-08 15:24:41
    [post_date_gmt] => 2013-02-08 15:24:41
    [post_content] => SomeContent
    [post_title] => SomeContent
    [post_excerpt] =>
    [post_status] => publish
    [comment_status] => open
    [ping_status] => open
    [post_password] =>
    [post_name] => somecontent
    [to_ping] =>
    [pinged] =>
    [post_modified] => 2013-02-12 14:53:14
    [post_modified_gmt] => 2013-02-12 13:53:14
    [post_content_filtered] =>
    [post_parent] => 0
    [guid] => url/?page_id=29
    [menu_order] => 0
    [post_type] => page
    [post_mime_type] =>
    [comment_count] => 0
    [filter] => raw
    )

    So there are no classes to find ???

    I use WordPress 3.5.1…..

    Anyone an idea?

    Thanx
    Niels

    • Stakey

      Getting the exact same problem, did you find a solution?

      • Niels

        Nop……. still searching…

  • Pingback: Fun With User Tailored Wordpress Menus: Part 1 | Peter R Knight

  • http://www.gravitationalfx.com/ Gravitational FX Web Design

    On line 42 you are missing a semicolon after the return function.

    This stops the function working.

    Wil.

  • Candide.ca

    There is a PHP syntax error, line 21 : $output. = (it’s $output .= instead, dot and = together)