Search code examples
htmlcssresponsive-designwordpress-themingwp-nav-walker

Convert responsive WordPress <ul> menu to <select> for small screens sans JS


I'm trying to build a responsive header for my WordPress theme on my site. Depending on the screen resolution, I'd like to go from a typical WordPress unordered list menu (larger screens), to a custom select dropdown menu (phone screens). I'd like to do this without relying on any additional JS so please no JavaScript or jQuery solutions.

While researching how to do this, I found a few useful resources, such as this question from the WordPress stack exchange site. The problem with this answer is that you'll notice it doesn't include the actual <select> tag around the <option> tags, so nothing appears on the site, although if you view the source code you will see that the options are being generated inside of a <ul> tag. I read through this one too but it relies on jQuery, which is not what I want.

I have made a 2nd menu called "select-menu-nav" and I plan on simply toggling the CSS display property for this menu and the "main-menu" with media queries depending on the width of the screen.

Here is the pertinent code -

HTML:

<header>
    <div id="logo-area">
        <img id="logo" src="<?php bloginfo('template_directory'); ?>/assets/imgs/db-logo.png" alt="Digital Brent Logo">
        <h2 id="site-title">Digital<br/>Brent.com</h2>
    </div>

    <div id="nav-area">
        <?php
        $walker = new alpha_nav_walker;
        wp_nav_menu( array( 'theme_location' => 'main-nav', 'walker' => $walker) );

        wp_nav_menu( array( 'theme_location' => 'select-main-nav', 'walker' => new alpha_dropdown_nav) );
        ?>
    </div>

    <div id="news-area">
    </div>

    <div id="search-area">
        <?php get_search_form(); ?>
    </div>
</header>

CSS:

@media(max-width: 1100px){
#menu-main-menu{
    display: none;
}

#menu-small-screen-menu{
    display: block;
}
}

Custom Navigation Function (dropdown):

class alpha_dropdown_nav extends Walker_Nav_Menu{

    public function start_lvl(&$output, $depth){}

    public function end_lvl(&$output, $depth){}

    public function start_el(&$output, $item, $depth, $args){

        $item->title = str_repeat("&nbsp;", $depth * 4) . $item->title;

        parent::start_el(&$output, $item, $depth, $args);
        $output = str_replace('<li', '<option', $output);
    }

    public function end_el(&$output, $item, $depth){
        $output .= "</option>\n";
    }
}

^ Note: This is the second usage of a custom menu walker function in the same file. The two functions are different (the first is referenced in the main menu call in the header) but I don't know if that's a problem, or if it's bad practice or not.

How can I replace the <ul> tag being generated around the option tags with a <select>?

Additionally, I welcome any feedback on my methodology. If anyone knows a way to do this that is more efficient, or considered better practice, I would be happy to learn a more effective way of doing this. Thanks!


Solution

  • I think this article includes exactly what you need!

    WordPress menus display as unordered lists by default. You might want to display them as a select menu though. Below is an example of creating a menu ‘walker’ that will render it as a select menu. When you are displaying your menu using wp_nav_menu(), include 'walker' => new Walker_Nav_Menu_Dropdown() to tell WordPress to use this type of formatting rather than the default.

    functions.php

    <?php
    // Nav Menu Dropdown Class
    include_once( CHILD_DIR . '/lib/classes/nav-menu-dropdown.php' );
    /**
     * Mobile Menu
     *
     */
    function be_mobile_menu() {
        wp_nav_menu( array(
            'theme_location' => 'mobile',
            'walker'         => new Walker_Nav_Menu_Dropdown(),
            'items_wrap'     => '<div class="mobile-menu"><form><select onchange="if (this.value) window.location.href=this.value">%3$s</select></form></div>',
        ) );    
    }
    add_action( 'genesis_before_header', 'be_mobile_menu' );
    

    nav-menu-dropdown.php

    class Walker_Nav_Menu_Dropdown extends Walker_Nav_Menu {
        function start_lvl(&$output, $depth){
            $indent = str_repeat("\t", $depth); // don't output children opening tag (`<ul>`)
        }
        function end_lvl(&$output, $depth){
            $indent = str_repeat("\t", $depth); // don't output children closing tag
        }
        /**
        * Start the element output.
        *
        * @param  string $output Passed by reference. Used to append additional content.
        * @param  object $item   Menu item data object.
        * @param  int $depth     Depth of menu item. May be used for padding.
        * @param  array $args    Additional strings.
        * @return void
        */
        function start_el(&$output, $item, $depth, $args) {
            $url = '#' !== $item->url ? $item->url : '';
            $output .= '<option value="' . $url . '">' . $item->title;
        }   
        function end_el(&$output, $item, $depth){
            $output .= "</option>\n"; // replace closing </li> with the option tag
        }
    }
    

    Good luck! :)