Digital Tiffin Consulting

Digital business transformation

Digital Tiffin Consulting

Digital business transformation

Keep calm, keep coding and keep walking

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…