Dropdown menus & Walker_Nav_Menu

| Comments

Spent the day learning a little more about the walker class that handles WordPress 3.x’s nav_menu structure. I can’t say it was easy. Having to extend Walker_Nav_Menu and overwrite the instance methods just to do something as simple as adding a <span> element within a list item seems a little counterintuitive. But for now, this is the best and most extensible way of handling menus that the user may need to update, so it seems worth knowing about.

The first thing I wanted to do was add a class for top-level menu items. This function checks for a submenu under each item and adds the class name “has_children” to any

<li> with children:

function check_for_submenu($classes, $item) {
    global $wpdb;
    $has_children = $wpdb->get_var("SELECT COUNT(meta_id) FROM wp_postmeta WHERE meta_key='_menu_item_menu_item_parent' AND meta_value='".$item->ID."'");
    if ($has_children > 0) array_push($classes,'has_children');
    return $classes;

add_filter( 'nav_menu_css_class', 'check_for_submenu', 10, 2);

Not the smoothest way of doing it, and it looks like there must be a more direct route. The Walker_Nav_Menu class does define an instance field $db_fields['parent'], which seems like it could be used rather than a direct database query, but I couldn’t access it. And in any case, it looks as though its being set to the post parent, which is not necessarily the same as the menu item parent.

And finally, here’s the extension to Walker_Nav_Menu that takes the custom menu, checks for children and marks it up in the structure required for droplicious.js:

class dropdown_walker extends Walker_Nav_Menu
    var $link_counter=0;
    function start_lvl(&$output, $depth) {
        global $link_counter;
        $class = 'scriptaculously';
        $indent = str_repeat("\t",$depth);
        $output .= "\n$indent<ul class=\"$class\" id=\"sub-".$link_counter."\" style=\"display:none;\">\n";
      function start_el(&$output, $item, $depth, $args)
           global $wp_query;
           global $link_counter;
           $indent = ( $depth ) ? str_repeat( "\t", $depth ) : '';

           $class_names = $value = '';

           $classes = empty( $item->classes ) ? array() : (array) $item->classes;
           $class_names = join( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item ) );
           $class_names = ' class="'. esc_attr( $class_names ) . '"';
            if (in_array('has_children',$classes)) $counter++;

           $output .= $indent . '<li id="menu-item-'. $item->ID . '"' . $value . $class_names .'>';

           $attributes  = ! empty( $item->attr_title ) ? ' title="'  . esc_attr( $item->attr_title ) .'"' : '';
           $attributes .= ! empty( $item->target )     ? ' target="' . esc_attr( $item->target     ) .'"' : '';
           $attributes .= ! empty( $item->xfn )        ? ' rel="'    . esc_attr( $item->xfn        ) .'"' : '';
           $attributes .= ! empty( $item->url )        ? ' href="'   . esc_attr( $item->url        ) .'"' : '';
            if (strpos($class_names,'has_children')) {
                $attributes .= ' class="drops" id="link-'.$link_counter.'"';

            $item_output = $args->before;
            $item_output .= '<a'. $attributes .'>';
            $item_output .= $args->link_before .apply_filters( 'the_title', $item->title, $item->ID );
            $item_output .= $description.$args->link_after;
            $item_output .= '</a>';
            $item_output .= $args->after;

            $output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );

Don’t know if anyone else uses droplicious (I rarely do, but in this project I needed to use the prototype framework, and I hate loading more than one javascript framework per site), but essentially the same markup structure will work with superfish or a number of other dropdown menus.

If you’re looking to see more examples of what can be done with a custom walker class, kriesi.at has a really pretty example from one of their themes, as explained in this tutorial: Improve your WordPress Navigation Menu Output.