Tiffin Consulting

digital business transformation

Dynamic Menus

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…