Archive for November, 2012

Customising wp_nav_menu()

Friday, November 2nd, 2012

Ever wanted to use the convenient built-in menu support of WordPress, but been frustrated or confused by the inflexibility of the wp_nav_menu() structure? WordPress actually allows much deeper customisation when you dig a little deeper, with its custom Walker functions.

For minor customisations, such as removing the navbar’s container, wp_nav_menu has an array of options that can be passed to it which makes the task quite simple. For adding a css class to specific links in the navbar, you can use WordPress’s powerful ‘add_filter’ functionality. The shortcomings of these methods, though, is that they don’t offer a way to manipulate the actual order or structuring of the unordered list itself – in order to put in an extra <li> with a subheading, for instance, you’d need to resort to javascript or empty ‘custom links’ in the menu interface.

To allow control of this sort of thing, WordPress allows to you create customised ‘walker functions’. WordPress actually uses these functions to handle many of its looping structures – pages, categories, comments, and more. A great introduction for editing these other kinds of structures can be found
The walker function I’m going to be focusing on, though, is the WP_Nav_Walker function, located in wp-includes/nav_menu_template.php around line 174. Remember, though, as with Magento, you should never edit core files, because if you upgrade to the latest version of wordpress in the future, your customisations may be overwritten. Instead copy all the code for the Walker_Nav_Menu, go to your functions.php file, and paste it in there. Change Walker_Nav_Menu to your whatever you’d like, i.e. mytheme_nav_walker, and change ‘extends Walker’ to ‘extends Walker_Nav_Menu’.
Now you have a copy of the walker function that you can edit to your heart’s content. A good explanation and an example on editing this file can be found in the wordpress codex, at . This example shows you how to add conditional classes based on the ‘depth’ (for submenus). It overrides start_lvl() (called each time the walker encounters a new submenu), end_lvl() (called at the end of each submenu), start_el() (called for each item) and end_el() (called at the end of each item). As you can tell by looking at them, end_lvl() and end_el() are quite simple in themselves, merely adding a closing <ul> or <li> tag after indenting the code (for readability). Note that if you want to edit these functions, you should copy the original from nav_menu_template.php (as you have done) and edit that, since you don’t want to make it from scratch but simply modify it.
This example is great, but it didn’t tell me everything I needed to know. I wanted to take a wp_nav_menu, and wraps the links in groups of 3 without simply making a submenu and using ‘position: absolute’ to force a layout, like so:Screen shot 2012 11 02 at 11.13.211 Customising wp nav menu()
I also didn’t want to hard-code the menu; as it was for a CMS site, I wanted them to have a certain amount of control over it.
So I needed a way to keep track of how many elements had been processed – I needed access to the function actually controlling the loop, so I could have a counter. After a bit of digging, I found this: . This showed a ‘walk()’ function, that did exactly what I wanted, but looking in nav_menu_templates.php, I could see no reference to it at all. This was a problem, since (as mentioned earlier) I needed to copy the original function, and edit it. I searched through wp-includes until I found the function in class-wp-walker.php – this is the base walker class that all the other WordPress Walkers (including the Walker_Nav_Menu class we’re editing) inherit. I copied the walk() function into my functions.php, inside my custom class with the other functions. Here’s my edited function:

function walk( $elements, $max_depth) {
		$args = array_slice(func_get_args(), 2);
		$output = '';
		$counter = 1;
		if ($max_depth < -1) //invalid parameter 			return $output; 		if (empty($elements)) //nothing to walk 			return $output; 		$id_field = $this->db_fields['id'];
		$parent_field = $this->db_fields['parent'];
		// flat display
		if ( -1 == $max_depth ) {
			$empty_array = array();
			foreach ( $elements as $e )
				$this->display_element( $e, $empty_array, 1, 0, $args, $output );
			return $output;
		}
		/*
		 * need to display in hierarchical order
		 * separate elements into two buckets: top level and children elements
		 * children_elements is two dimensional array, eg.
		 * children_elements[10][] contains all sub-elements whose parent is 10.
		 */
		$top_level_elements = array();
		$children_elements  = array();
		foreach ( $elements as $e) {
			if ( 0 == $e->$parent_field )
				$top_level_elements[] = $e;
			else
				$children_elements[ $e->$parent_field ][] = $e;
		}
		/*
		 * when none of the elements is top level
		 * assume the first one must be root of the sub elements
		 */
		if ( empty($top_level_elements) ) {
			$first = array_slice( $elements, 0, 1 );
			$root = $first[0];
			$top_level_elements = array();
			$children_elements  = array();
			foreach ( $elements as $e) {
				if ( $root->$parent_field == $e->$parent_field )
					$top_level_elements[] = $e;
				else
					$children_elements[ $e->$parent_field ][] = $e;
			}
		}
		foreach ( $top_level_elements as $e )
		{
			if ($counter >=4)
			{
				$counter = 0;
				$output .= "</ul>\n";	
			}
			//start new list if necessary
			if ($counter == 0)
			{
				$counter++;
				$output .= "<ul class='menu'>";
			}
			$this->display_element( $e, $children_elements, $max_depth, 0, $args, $output );
			$counter++;
		}
		/*
		 * if we are displaying all levels, and remaining children_elements is not empty,
		 * then we got orphans, which should be displayed regardless
		 */
		if ( ( $max_depth == 0 ) && count( $children_elements ) > 0 ) {
			$empty_array = array();
			foreach ( $children_elements as $orphans )
				foreach( $orphans as $op )
					$this->display_element( $op, $empty_array, 1, 0, $args, $output );
		 }
		 return $output;
	}

The changes I made were relatively simple – at the beginning of the function, I initialised my counter: ‘$counter = 1;’
Then, I edited the foreach loop that handled the top-level elements (as I only wanted to split those, and leave submenus untouched):

foreach ( $top_level_elements as $e )
		{
			if ($counter >=4)
			{
				$counter = 0;
				$output .= "</ul>\n";	
			}
			//start new list if necessary
			if ($counter == 0)
			{
				$counter++;
				$output .= "<ul class='menu'>";
			}
			$this->display_element( $e, $children_elements, $max_depth, 0, $args, $output );
			$counter++;
		} 

The display_element function is the function that calls start_lvl, end_lvl, start_el, and end_el, as necessary. Each time it runs for the top level elements, I’ve simply incremented it, and for every 3rd element, I’ve closed off the list and opened a new one on the next iteration. The reason I do this in the following iteration and not at the end of the current one is to prevent extra </ul> tags
So that’s the editing done. Remember, you can remove start_lvl(), etc., from functions.php if you’re not editing them – your class will just inherit the default from nav_menu_temlate.php. So how do you use it now? Well, that’s the easy part. In my template:

<?php wp_nav_menu( array('menu' => 'Footer Nav', 'walker' => new mytheme_walker_nav_menu )); ?>

Contact Us Today...

If you have any questions about our services or want to arrange a free no obligation consultation contact us today or call 02920 290 080 for Cardiff and 01179 000 482 for Bristol.

Bit Torrent Study Discovers Most File Sharers Are Now Monitored

Ever heard of torrents or used Bit Torrent to download movies, music or shared files over the internet? Researchers have discovered that anyone who does use Bit Torrent to download files over the World Wide Web will most probably be monitored. Studies were carried out by Birmingham University and they have discovered that if someone [...]
» more

Cardiff: 02920 290 080 Bristol: 01179 000 482