Time: 15 minutes
Difficulty: Easy

Recently I needed to assign a different menu item (secondary navigation) to selected pages within a WordPress theme. I decided write up how I did this because sharing is caring and we are nice like that!

In this post I will be covering

  1. Adding a Meta box to the add/edit pages of the admin pages
  2. Creating two different submenu’s in Appearance > Menus
  3. Grab our newly created menu content from the DB
  4. Display the correct menu in page.php

Download the code for this tutorial

1. Creating the Meta Box

In our admin screen we will be making the following meta box called ‘Secondary Navigation’ which is shown here in the top right. The box will contain a html select element that contains a list of all the menu’s created in Appearance > Menus.

Themesto.re

Create a file called meta-menu.php and add the following array of options to the top and these three other empty functions underneath:

/*
* meta-menu.php
*/
$meta_box_menu = array(
    'id' => 'themestore-meta-menu',
    'title' => 'Secondary Navigation',
    'page' => 'page',
    'context' => 'side',
    'priority' => 'high',
    'fields' => array(
        array(
            'id' => 'themestore-meta-menu-name',
            'type' => 'select',
            'std' => 'none'
        ),
    ),
);

/*
* This function will register the meta box with WordPress
*/
function themestore_add_box() {

}

/*
* This function will produce the html needed to display our meta box in the admin area
*/
function themestore_meta_menu_html() {

}

/*
* This function will save our preferences into the database
*/
function themestore_save_data() {

}

In lines 4 – 17 we setup the configuration for our meta box. Information about all the options can be found in the WordPress codex. The most important thing to remember here is to make sure you give the id’s a unique value that wont conflict with any other plugins or theme names. Here I’ve prefixed them with themestore. The fields array of arrays is where you decide how many bits of data you will be saving. In our case we only need one. We call it themestore-meta-menu-name. This key is what is used to identify it when getting it out of the database so it is essential to make this unique to.

Now lets fill in the other functions. themestore_add_box() creates the meta box using the add_meta_box() function and we pass it the config options we created above. We then register the function with the admin_init action hook.

/*
* This function will register the meta box with WordPress
*/
function themestore_add_box() {
	global $meta_box_menu;
	add_meta_box($meta_box_menu['id'], $meta_box_menu['title'], 'themestore_meta_menu_html', $meta_box_menu['page'], $meta_box_menu['context'], $meta_box_menu['priority']);
}
add_action('admin_init', 'themestore_add_box');

We now need to tell WordPress what to display when it adds the box. Lets fill in the function called themestore_meta_menu_html().

/*
* This function will produce the html needed to display our meta box in the admin area
*/
function themestore_meta_menu_html() {
    global $meta_box_menu, $post;
 
    $output = '<p style="padding:10px 0 0 0;">'.__('Choose which menu shows up in the secondary spot for this page. This will override any selections made in Appearance > Menus', 'themestore').'</p>';
    $output .= '<input type="hidden" name="sf_meta_box_nonce" value="'. wp_create_nonce(basename(__FILE__)). '" />';

    echo $output;
}

At this point we should have something to see. Lets save our file meta-menu.php to the root of our theme and include it from functions.php like this

/*
* functions.php
*/

include_once('meta-menu.php');

Now load up the admin interface and head over to Page > Add New to see the box in action.

It should look like this
Meta Box Example

2. Creating a Couple of Custom Menu’s

Now as you can see our box is missing the menu choices. Head over to Appearance > Menus and create a couple of menu’s. Lets keep it really simple and call the first one ‘Sub Menu One’ and the second one ‘Sub Menu Two’. Put a couple of example links into each one.

It should look something like this.

themestore sub menu example 1

Next we need to make these menu’s appear in the meta boxes we created above so they can be associated with a page.

Grab our Menu Content from the DB

Open the functions.php file and add the following two functions. themestore_get_all_menus() grabs all menus that you have created and returns an array of objects. The next function themestore_generate_menu_from_items() iterates through a list of menu items and outputs some html. We’ll see how that is used shortly.

/*
* functions.php
*/
function themestore_get_all_menus() {
  $menu_obj = get_terms( 'nav_menu' );
  return $menu_obj;
}

function themestore_generate_menu_from_items($items) {
  if(count($items)>0): 
    $menu_list = '<ul id="menu-' . $menu_to_use . '">';
      foreach ( (array) $items as $key => $menu_item ) {
        $title = $menu_item->title;
        $url = $menu_item->url;
        $menu_list .= '<li>' . $title . '</li>';
      }
    $menu_list .= '</ul>';
    return $menu_list;
  endif;
}

Now lets use these two functions. Firstly head back to themestore_meta_menu_html() in meta-menu.php so we can finish it. We need to display the two menu’s we created so the user can associate them with the post or page. All we need to do is grab a list of menu items using themestore_get_all_menus() which we just created and then go through them building a select box.

The function should now look like this:

/*
* meta-menu.php
*/
function themestore_meta_menu_html() {
  global $meta_box_menu, $post;
 
  $output = '<p style="padding:10px 0 0 0;">'.__('Choose which menu shows up in the secondary spot for this page. This will override any selections made in Appearance > Menus', 'themestore').'</p>';
  $output .= '<input type="hidden" name="sf_meta_box_nonce" value="'. wp_create_nonce(basename(__FILE__)). '" />';

  $output .= '<table class="form-table">';

  foreach ($meta_box_menu['fields'] as $field) {
    $meta = get_post_meta($post->ID, $field['id'], true);

    /*
    *	Get out all our menus using the function from functions.php
    */
    $menus = themestore_get_all_menus();

    /*
    *	Grab out saved data for edit mode
    */
    $meta = get_post_meta($post->ID, $field['id'], true);

    $output .= '<select name="'.$field['id'].'" class="widefat">';
    $output .= '<option value="none">- none -</option>';
    if(is_array($menus)):
      foreach($menus as $k => $v):
        if($meta==$v->slug):
          $output .= '<option selected="selected" value="' . $v->slug .'">' . $v->name . '</option>';
        else:
          $output .= '<option value="' . $v->slug .'">' . $v->name . '</option>';
        endif;
      endforeach;
    endif;
    $output .= '</select>';

  }

  $output .= '</table>';

  echo $output;
}

We also add another function to meta-menu.php. It is called themestore_save_data() and it saves the meta box data we send to it. We need to register it with the save_post action hook so WordPress knows what do do with it.

/*
* meta-menu.php
*/
function themestore_save_data($post_id) {

  global $meta_box, $meta_box_menu;

    foreach ($meta_box_menu['fields'] as $field) {
      $old = get_post_meta($post_id, $field['id'], true);
      $new = $_POST[$field['id']];

      if ($new && $new != $old) {
        update_post_meta($post_id, $field['id'], stripslashes(htmlspecialchars($new)));
      } elseif ('' == $new && $old) {
        delete_post_meta($post_id, $field['id'], $old);
      }
    }
}
add_action('save_post', 'themestore_save_data');

Display the correct menu in page.php

The very last thing we need to do is add some code to the front end of the site to pull out and format into HTML the menu on page.php

<div class="column">

<?php
  $id = get_the_ID();

  /*
  *	Grab the saved menu name that we saved with our Meta Box
  */
  $menu_to_use = get_post_meta($id, 'themestore-meta-menu-name', 1);

  /*
  *	Get the menu item elements for this out menu
  */
  $items = wp_get_nav_menu_items( $menu_to_use );	

  /*
  *	Print the html 
  */
  echo themestore_generate_menu_from_items( $items );
?<

</div>

Thats it!. Thats all we have to do to be able to assign any menu created in WordPress to any of our pages. I’ve found this especially useful when I need slightly different sub menus or secondary navigation on some pages. Create as many menu’s as you like and assign them to whatever page you like.

There is also a Git repository containing our code.

There are a ton of ways we could improve this and add features which I might write about later. Feel free to let us know about any topics you want covered.

Why not leave your ideas in the comments below?




blog comments powered by Disqus