How to change theme header to support multiple nav menus?

I’ll try to be brief….

I have a theme that only supports one navigation menu. At this point I’d like to add 2 or 3 more menus. I have successfully registered my menus in functions.php with register_nav_menus and I am able to switch the header.php code manually between my different menus.

Read More

My problem…
Since my theme uses a single header (header.php) I can’t assign my pages or posts the navigation menu I want without changing all the other pages navigation menus.

Can anyone help me out on this?
Thanks in advance.
Greg

Related posts

Leave a Reply

1 comment

  1. First off, some things you should know about nav menus:

    1. Menus are simply terms in a taxonomy called nav_menu.
    2. Menu items are a special post type

    In short: menus are just like nearly every other piece of content in WordPress, they just have a custom UI.

    With that out of the way, the task is fairly simple: put a custom meta box on your edit screen that shows options to choose nav menus. On the front end, switch the nav menu out to whatever you set in the admin area. The only thing to worry about is what the theme_location value is for your theme’s call to wp_nav_menu. Where do you want to switch the menu out?

    This is example will use Twenty Twelve, which uses the theme location primary.

    A call to wrap everything up in:

    <?php
    class PerPostNavMenu
    {
        const NONCE = 'wpse85243_nav_nonce';
        const FIELD = '_wpse85243_per_post_menu';
        const LOC   = 'primary'; // the location for twenty twelve
    
        private static $ins = null;
    
        public static function instance()
        {
            if (is_null(self::$ins)) {
                self::$ins = new self;
            }
    
            return self::$ins;
        }
    
        public static function init()
        {
            add_action('plugins_loaded', array(self::instance(), '_setup'));
        }
    
        public function _setup()
        {
            // we'll add actions/filters here later
        }
    }
    

    Now let’s add our meta box. Since this is covered in detail a great many places, I’ll give the cliff notes explanation: hook into add_meta_boxes, call add_meta_box. In the meta box callback function, output a nonce and your field(s).

    To save the values, hook into save_post, check to make sure we are where we want to be (no autosave, nonce validates) and that the current user can edit the post, save (or delete) the value with (update|delete)_post_meta.

    <?php
    class PerPostNavMenu
    {
        const NONCE = 'wpse85243_nav_nonce';
        const FIELD = '_wpse85243_per_post_menu';
        const LOC   = 'primary'; // the location for twenty twelve
    
        // snip snip
    
        public function _setup()
        {
            add_action('add_meta_boxes', array($this, 'addBox'));
            add_action('save_post', array($this, 'save'), 10, 2);
        }
    
        public function addBox($post_type)
        {
            if (!post_type_exists($post_type)) {
                return;
            }
    
            add_meta_box(
                'wpse85243_nav_menu',
                __('Nav Menu', 'wpse'),
                array($this, 'boxCallback'),
                $post_type,
                'side',
                'low'
            );
        }
    
        public function boxCallback($post)
        {
            $menu = get_post_meta($post->ID, static::FIELD, true);
    
            wp_nonce_field(static::NONCE . $post->ID, static::NONCE, false);
    
            printf(
                '<label for="%s">%s</label>',
                esc_attr(static::FIELD),
                esc_html__('Nav Menu', 'wpse')
            );
    
            echo '<br />';
    
            printf('<select name="%1$s" id="%1$s">', esc_attr(static::FIELD));
    
            foreach ($this->getNavMenus() as $id => $name) {
                printf(
                    '<option value="%s" %s>%s</option>',
                    esc_attr($id),
                    selected($menu, $id, false),
                    esc_html($name)
                );
            }
    
            echo '</select>';
        }
    
        public function save($post_id, $post)
        {
            if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
                return;
            }
    
            if (
                !isset($_POST[static::NONCE]) ||
                !wp_verify_nonce($_POST[static::NONCE], static::NONCE . $post_id)
            ) {
                return;
            }
    
            $type = get_post_type_object($post->post_type);
            if (!current_user_can($type->cap->edit_post, $post_id)) {
                return;
            }
    
            $menu = isset($_POST[static::FIELD]) ? $_POST[static::FIELD] : false;
    
            if ($menu && '-' !== $menu) {
                update_post_meta($post_id, static::FIELD, absint($menu));
            } else {
                delete_post_meta($post_id, static::FIELD);
            }
        }
    
        private function getNavMenus()
        {
            $terms = get_terms('nav_menu');
    
            $menus = array('-' => __('Default', 'wpse'));
            if ($terms && !is_wp_error($terms)) {
                foreach($terms as $t) {
                    $menus[$t->term_id] = $t->name;
                }
            }
    
            return apply_filters('per_post_nav_menus_list', $menus);
        }
    }
    

    Notice the helper method to retrieve the nav menus. Not much, just a convenient helper that runs the result through a filter.

    Finally, we just need to switch out the menu. To do that, hook into wp_nav_menu_args and, if we’re on a singular page and have the correct theme location, switch out the menu as appropriate. Some extra filters are included to make this a bit more extensible.

    <?php
    class PerPostNavMenu
    {
        const NONCE = 'wpse85243_nav_nonce';
        const FIELD = '_wpse85243_per_post_menu';
        const LOC   = 'primary'; // the location for twenty twelve
    
        // snip snip
    
        public function _setup()
        {
            add_action('add_meta_boxes', array($this, 'addBox'));
            add_action('save_post', array($this, 'save'), 10, 2);
            add_filter('wp_nav_menu_args', array($this, 'switchMenu'));
        }
    
        // snip snip
    
        public function switchMenu($args)
        {
            // we can only deal with singular pages
            if (!is_singular()) {
                return;
            }
    
            $switch = apply_filters(
                'per_post_nav_menus_switch',
                isset($args['theme_location']) && static::LOC === $args['theme_location'],
                $args
            );
    
            // if we're allowed to switch, the the `menu` argument to
            // the correct menu ID.
            if ($switch) {
                $menu = get_post_meta(get_queried_object_id(), static::FIELD, true);
    
                if ('-' !== $menu) {
                    $args['menu'] = absint($menu);
                }
            }
    
            return $args;
        }
    
        // snip snip
    }
    

    All of the above as a plugin.