wp-bootstrap / wp-bootstrap-navwalker

A custom WordPress nav walker class to fully implement the Twitter Bootstrap 4.0+ navigation style (v3-branch available for Bootstrap 3) in a custom theme using the WordPress built in menu manager.
https://wp-bootstrap.github.io/wp-bootstrap-navwalker/
GNU General Public License v3.0
3.37k stars 1.94k forks source link

How to add an additional first item to all dropdown without using wordpress menu management. #519

Closed rianfloo closed 3 years ago

rianfloo commented 3 years ago

Dear wp-boostrap-navwalker contributors,

I would like to add a an additional first items to all dropdown working as a go back button on mobile.

I found the solution to add a new item in the wordpress menu management and hide it on desktop but I would like to create it by using the navwalker class.

Any tips to do it?

Thanks a lot :)

IanDelMar commented 3 years ago

Hi @rianfloo!

I don't think this is possible without adjusting the walker. The walker walks the items you provided via the menu management. Is that what you are asking for? How to adjust the walker to achieve this?

pattonwebz commented 3 years ago

Ian is correct, the walker doesn't allow this as it is built. You could look into the wp_nav_menu_items filter and maybe make what you need happen https://developer.wordpress.org/reference/hooks/wp_nav_menu_items/

rianfloo commented 3 years ago

Thanks for your answer :) I would like to update the walker to add the following behaviour.

<ul class="dropdown-menu" role="menu">
  <li><button class="go-back"> Go back (First item generated by updated walker with go back button)</button></li>
  <li><a href="#" class="go-back">Menu item (classic menu item)</button></li>
</ul>

I will have a look to wp_nav_menu_items.

Thanks a lot :)

IanDelMar commented 3 years ago

@rianfloo Put this inside the wp bootstrap walker class:

public function display_element( $element, &$children_elements, $max_depth, $depth, $args, &$output ) {
    if ( 1 === (int) $element->menu_order ) {
        $menu_item_title = __( 'Back to Home', 'theme_slug' );
        $class_to_hide_on_desktop = 'class-to-hide-on-desktop';

        $custom_element = new stdClass();
        $custom_element->ID = 0;
        $custom_element->post_title = $menu_item_title;
        $custom_element->post_status = 'publish';
        $custom_element->post_parent = 0;
        $custom_element->menu_order = 0;
        $custom_element->post_type = 'nav_menu_item';
        $custom_element->filter = 'raw';
        $custom_element->db_id = 0;
        $custom_element->menu_item_parent = '0';
        $custom_element->object_id = (string) get_option('page_on_front');
        $custom_element->object = 'custom';
        $custom_element->type = 'custom';
        $custom_element->title = $menu_item_title;
        $custom_element->url = get_home_url();
        $custom_element->target = '';
        $custom_element->attr_title = '';
        $custom_element->description = '';
        $custom_element->classes = array( $class_to_hide_on_desktop, 'menu-item', 'menu-item-type-custom', 'menu-item-object-custom' );
        $custom_element->xfn = '';
        $custom_element->current = false;
        $custom_element->current_item_ancestor = false;
        $custom_element->current_item_parent = false;
        parent::display_element( $custom_element, $children_elements, $max_depth, $depth, $args, $output );
    }

    parent::display_element( $element, $children_elements, $max_depth, $depth, $args, $output );
}

This mimics a nav item. The if statement ( 1 === (int) $element->menu_order ) tells the walker to put the item in front of the first item. If you really want that item to be the first in each dropdown - for whatever reason - try this if statement instead if ( 1 == $depth ).

I haven't tested this approach though.

The advantage over the approach suggest by @pattonwebz is that you don't have to care about the actual markup of the nav item but only about its content.

IanDelMar commented 3 years ago

I just had a look at wp_nav_menu() and found this hook: wp_get_nav_menu_items. If I'm not mistaken, that's how it should work without adjusting the walker:

add_filter( 'wp_get_nav_menu_items', 'slug_add_go_back_menu_item' );
function slug_add_go_back_menu_item( $items ) {
    $elements = array();
    $found_parents = array();
    $menu_order = 1;
    foreach ( $items as $item ) {
        $menu_item_parent = (int) $item->menu_item_parent;
        $item->menu_order = $menu_order;
        if ( 0 !== $menu_item_parent ) { // this is not a top-level item
            if ( ! in_array( $menu_item_parent, $found_parents, true ) ) { // this is the first child of the parent
                $custom_element = slug_get_go_back_menu_item( $menu_order, $menu_item_parent );
                ++$menu_order;
                $item->menu_order = $menu_order;
                $elements[] = $custom_element;
                $found_parents[] = $menu_item_parent;
            }
        }
        $elements[] = $item;
        ++$menu_order;
    }
    return $elements;
}

function slug_get_go_back_menu_item( $menu_order, $menu_item_parent ) {
    $menu_item_title = __( 'Back to Home', 'theme_slug' );
    $class_to_hide_on_desktop = 'class-to-hide-on-desktop';
    $custom_element = new stdClass();
    $custom_element->ID = 0;
    $custom_element->post_title = $menu_item_title;
    $custom_element->post_status = 'publish';
    $custom_element->post_parent = 0;
    $custom_element->menu_order = $menu_order;
    $custom_element->post_type = 'nav_menu_item';
    $custom_element->filter = 'raw';
    $custom_element->db_id = 0;
    $custom_element->menu_item_parent = $menu_item_parent;
    $custom_element->object_id = (string) get_option('page_on_front');
    $custom_element->object = 'custom';
    $custom_element->type = 'custom';
    $custom_element->title = $menu_item_title;
    $custom_element->url = get_home_url();
    $custom_element->target = '';
    $custom_element->attr_title = '';
    $custom_element->description = '';
    $custom_element->classes = array( $class_to_hide_on_desktop, 'menu-item', 'menu-item-type-custom', 'menu-item-object-custom' );
    $custom_element->xfn = '';
    $custom_element->current = false;
    $custom_element->current_item_ancestor = false;
    $custom_element->current_item_parent = false;
    return $custom_element;
}
rianfloo commented 3 years ago

@IanDelMar Thanks a lot! Better not adjusting the walker and simply use a function :)

IanDelMar commented 3 years ago

You're welcome! I consider this solved.