There are many good tutorials to build custom menus for logged in/logged out users, see also our own post on the ajax login menu. There are more complex approaches in order to customise the individual menu items using a the wordpress Walker class. An excellent review of a custom walker can be found on the WordPress StackExchange.
However, I was looking to actually add menu items dynamically depending on the state of the site. In this case, we have a client, a research centre at a university who asked us to redevelop their site as a portal of all their conference sites. We created a WordPress MU portal which we customised to enable them to create new conference sites based on a template site that is pre-configured, enabling registration forms, abstract submission forms, payment gateways to be created for each new conference at the press of a button in the Dashboard.
One of the challenges was to dynamically add new conference sites to the main site menu as when they are created. Furthermore, the conference menu has a Past sub-menu in which old conferences need to be listed.
I decided to use the Walker extension solution by adding a little customisation. ย I created a new class, AddExtraMenuItems_Walker
, which extends the WordPress core ย Walker_Nav_Menu class.
Full code
/**
* Walker Navigation Menu class to insert dynamic content into menus
* use the constructor at creation to pass the dynamic menu content
*/
class AddExtraMenuItems_Walker extends Walker_Nav_Menu{
public $insertMenus = array();
protected $nextMenu = '';
/**
* Constructor takes an array
*
*@param array $menu_insert an array of key=>value pairs with
*each key being the parent menu underwhich to insert the extra menu items,
*and the value being an array containing menu items as array values of the following form,
*array( 'url' => permalink for the menu item (default ='#'),
* 'title' => menu title (default = '<i> Missing Title</i>'),
* 'id' => css id value for the <li> term (default = ''),
* 'class' => ',' delimited string of css classes for the <li> term (default='') ), each
*menu will be wrapped in <a> tags that will open in a new window.
*/
function __construct($menu_insert = array()) {
$this->insertMenus = $menu_insert;
}
function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
$menu_item = apply_filters( 'the_title', $item->title, $item->ID );
if(array_key_exists($menu_item,$this->insertMenus)){
//check if to be listed or inserted as sub-menu
$menuTree = $this->insertMenus[$menu_item];
$subMenu = ! empty($menuTree['sub-menu']) ? true:false;
/*
*if this is a sub-mneu insert, ;let's make sure our current
*menu-item has the required class to identify it as such
*/
if($subMenu && ! empty($menuTree['class'])) $item->classes[]=$menuTree['class'];
//call parent function
parent::start_el($output, $item, $depth, $args);
//now if we have a sub-menu tree we need to insert it here
if($subMenu){
$classes = ! empty($menuTree['ul-class']) ? ' class="'.$menuTree['class'].'"' : ' class="sub-menu"';
$output .= '<ul'.$class.'>';
foreach($menuTree['sub-menu'] as $menu_item){
$classes = ! empty($menu_item['class']) ? '" class="'.$menu_item['class'].'"' : '';
$id = ! empty($menu_item['id']) ? ' id="'.$menu_item['id'].'"' : '';
$url =! empty($menu_item['url']) ? $menu_item['url'] : '#';
$title=! empty($menu_item['title']) ? $menu_item['title'] : '<i>Missing Title</i>';
$output .= '<li'.$id.$classes.'><a href="'.$url.'" target="_blank">'.$title.'</a></li>';
}
$output .= '</ul>'; //let's close our sub-menu
}else $this->nextMenu = $menuTree; //else we pass the menu items to be inserted as list items in the start call
}else parent::start_el($output, $item, $depth, $arg);
}
function start_lvl( &$output, $depth = 0, $args = array()) {
parent::start_lvl($output, $depth, $args);
if($this->nextMenu){
foreach($this->nextMenu as $menu_item){
$classes = ! empty($menu_item['class']) ? '" class="'.$menu_item['class'].'"' : '';
$id = ! empty($menu_item['id']) ? ' id="'.$menu_item['id'].'"' : '';
$url =! empty($menu_item['url']) ? $menu_item['url'] : '#';
$title=! empty($menu_item['title']) ? $menu_item['title'] : '<i>Missing Title</i>';
$output .= '<li'.$id.$classes.'><a href="'.$url.'" target="_blank">'.$title.'</a></li>';
}
$this->nextMenu=''; //reset
}
}
}
So the Walker class is simply called in the wordpress function to display a menu structure,
wp_nav_menu( array('walker'=> new AddExtraMenuItems_Walker($extra_menu), 'theme_location' => 'primary-menu', 'container' => '', 'fallback_cb' => '', 'menu_class' => 'my-menu-class', 'menu_id' => 'top-menu', 'echo' => false ) );
Remains to define the $extra_menu array arguments that are passed to the walker in order to insert it into the right menu. Here is an example. It assumes that there is a primary menu created in the Dashboard with the top level menu item ‘Conferences’ and a sub-item below this called ‘Past’,
$extra_menu = array(
'Conferences'=>array(array('title'=>'ISOl 2015',
'url' => 'http://syllogic.asia/2015/',
'id' => 'menu_item_isol_2',
'class'=> 'menu-item menu-item-type-custom menu-item-object-custom'),
array('title'=>'ISOl Chicago',
'url' => 'http://syllogic.asia/2015/',
'id' => 'menu_item_isol_3',
'class'=> 'menu-item menu-item-type-custom menu-item-object-custom')),
'Past'=>array('ul-class'=>'sub-menu',
'class'=>'menu-item-has-children left-sub-menu',
'sub-menu'=>array(array('title'=>'ISOl 2015',
'url' => 'http://syllogic.asia/2015/',
'id' => 'menu_item_isol_2',
'class'=> 'menu-item menu-item-type-custom menu-item-object-custom'),
array('title'=>'ISOl Chicago',
'url' => 'http://syllogic.asia/2015/',
'id' => 'menu_item_isol_3',
'class'=> 'menu-item menu-item-type-custom menu-item-object-custom'))));
There is 2 type of menu being inserted here, the first one under ‘Conferences’ menu item, are 2 extra sub-item which are simply listed within the existing sub-menu list of ‘Conferences’.
The second set of menu is an entire sub-menu structure being replaced instead of an existing menu-item. In other words, ‘Past’ is a sub-item of top menu item ‘Conferences’. As such ‘Past’ does not have any children. This 2nd set of dynamic menu are placed below the ‘Past’, making it into a sub-menu.
Menu structures are dined as an array with,
array('title'=>the text of the menu item,
'url' => the permalink for the menu,
'id' => a css id for the <li> element,
'class'=> css classes for the <li> element)
To add an entire sub-menu to a given item, you need to specify some extra arguments, namely the classes that changes an item into a sub-menu (on hover the sub-menu will appear) if you are using pure CSS for your menus. This is done using the following structure,
array('ul-class'=>the css class for the sub-menu
element,
'class'=>the css class for the menu item that will change to a sub-menu structure,
'sub-menu'=>array( sub-menu items as arrays, as defined above )
That’s it. Feel free to comment, query, clarify below…