17

Getting Bootstrap 3 NavBar Working with WordPress wp_nav_menu()

Posted (Updated ) in PHP

Bootstrap 3’s NavBar component has a convoluted markup that makes it difficult to integrate into WordPress’s wp_nav_menu() function but with the help of a custom Walker and a filter it’s quite possible to get happening.

 

Target Markup

We’re aiming to replicate the NavBar markup from the BS3 documentation. Here it is in full. Specifically wp_nav_menu() will cover the highlighted section.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<nav class="navbar navbar-default" role="navigation">
	<!-- Brand and toggle get grouped for better mobile display -->
	<div class="navbar-header">
		<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-ex1-collapse">
			<span class="sr-only">Toggle navigation</span>
			<span class="icon-bar"></span>
			<span class="icon-bar"></span>
			<span class="icon-bar"></span>
		</button>
		<a class="navbar-brand" href="#">Brand</a>
	</div>
 
	<!-- Collect the nav links, forms, and other content for toggling -->
	<div class="collapse navbar-collapse navbar-ex1-collapse">
		<ul class="nav navbar-nav">			<li class="active"><a href="#">Link</a></li>			<li><a href="#">Link</a></li>			<li class="dropdown">				<a href="#" class="dropdown-toggle" data-toggle="dropdown">Dropdown <b class="caret"></b></a>				<ul class="dropdown-menu">					<li><a href="#">Action</a></li>					<li><a href="#">Another action</a></li>					<li><a href="#">Something else here</a></li>					<li><a href="#">Separated link</a></li>					<li><a href="#">One more separated link</a></li>				</ul>			</li>		</ul>	</div><!-- /.navbar-collapse -->
</nav>

 

Step 1: Add the frontend markup

Drop this into your header.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<nav id="nav" class="navbar navbar-default" role="navigation">
	<!-- Brand and toggle get grouped for better mobile display -->
	<div class="navbar-header visible-xs">
		<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-ex1-collapse">
			<span class="sr-only">Toggle navigation</span>
			<span class="icon-bar"></span>
			<span class="icon-bar"></span>
			<span class="icon-bar"></span>
		</button>
		<a class="navbar-brand" href="#">Kawaii Walls</a>
	</div>
 
	<!-- Collect the nav links, forms, and other content for toggling -->
	<div class="collapse navbar-collapse navbar-ex1-collapse">
		<?php wp_nav_menu(array(
			'container_class' => 'menu-header',
			'theme_location' => 'primary',
			'items_wrap' => '<ul id="%1$s" class="%2$s nav navbar-nav">%3$s</ul>',
			'walker' => new BS3_Walker_Nav_Menu,
		)); ?>
	</div><!-- /.navbar-collapse -->
</nav>

 

Step 2: Submenu classes

The meat of this blog post is here. Due to a bug, WP is missing the flexability we need so I’ve written a custom walker that adds functionality to the built in one.ย Drop the following inย functions.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
class BS3_Walker_Nav_Menu extends Walker_Nav_Menu {
	/**
	 * Traverse elements to create list from elements.
	 *
	 * Display one element if the element doesn't have any children otherwise,
	 * display the element and its children. Will only traverse up to the max
	 * depth and no ignore elements under that depth. It is possible to set the
	 * max depth to include all depths, see walk() method.
	 *
	 * This method shouldn't be called directly, use the walk() method instead.
	 *
	 * @since 2.5.0
	 *
	 * @param object $element Data object
	 * @param array $children_elements List of elements to continue traversing.
	 * @param int $max_depth Max depth to traverse.
	 * @param int $depth Depth of current element.
	 * @param array $args
	 * @param string $output Passed by reference. Used to append additional content.
	 * @return null Null on failure with no changes to parameters.
	 */
	function display_element( $element, &$children_elements, $max_depth, $depth, $args, &$output ) {
		$id_field = $this->db_fields['id'];
 
		if ( isset( $args[0] ) && is_object( $args[0] ) )
		{
			$args[0]->has_children = ! empty( $children_elements[$element->$id_field] );
 
		}
 
		return parent::display_element( $element, $children_elements, $max_depth, $depth, $args, $output );
	}
 
	/**
	 * @see Walker::start_el()
	 * @since 3.0.0
	 *
	 * @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. Used for padding.
	 * @param int $current_page Menu item ID.
	 * @param object $args
	 */
	function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
		if ( is_object($args) && !empty($args->has_children) )
		{
			$link_after = $args->link_after;
			$args->link_after = ' <b class="caret"></b>';
		}
 
		parent::start_el($output, $item, $depth, $args, $id);
 
		if ( is_object($args) && !empty($args->has_children) )
			$args->link_after = $link_after;
	}
 
	/**
	 * @see Walker::start_lvl()
	 * @since 3.0.0
	 *
	 * @param string $output Passed by reference. Used to append additional content.
	 * @param int $depth Depth of page. Used for padding.
	 */
	function start_lvl( &$output, $depth = 0, $args = array() ) {
		$indent = str_repeat("\t", $depth);
		$output .= "\n$indent<ul class=\"dropdown-menu list-unstyled\">\n";
	}
}

 

Step 3: Finishing Touches

Finally we need to make the top level links open their respective drop downs. This is possible thanks to Step 2. Drop the following below where you placed the code in Step 2:

1
2
3
4
5
6
7
8
9
add_filter('nav_menu_link_attributes', function($atts, $item, $args) {
	if ( $args->has_children )
	{
		$atts['data-toggle'] = 'dropdown';
		$atts['class'] = 'dropdown-toggle';
	}
 
	return $atts;
}, 10, 3);

All done! Good luck with your fancy new Bootstrap 3 WordPress theme!

  • Alan Heric

    excelent work! i just have an issue that parent link is not clickable it just triggers the drop down menu?

    • Name

      If theirs a workaround for that is issue, please share it. Looking for months for such a solution.

      • Alan Heric
        • digital-doodle

          I wanted this function for the parent link so i had to add an else condition
          if ( $args->has_children )
          {
          $atts[‘data-toggle’] = ‘dropdown’;
          $atts[‘class’] = ‘dropdown-toggle’;
          }
          else{
          $atts[‘data-toggle’] = ‘tab’;
          $atts[‘class’] = ‘dropdown-toggle’;
          }

          Hope this helps:)

          • Tabula Rasa

            I might be really new here, but where did you get the “$atts” from?

            I changed only the following lines

            function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {

            if ( is_object($args) && !empty($args->has_children) ){ //old line
            $link_after = $args->link_after; //old line
            $args->link_after = ‘ ‘; //old line

            array_push ($item->classes,’dropdown-toggle’ ); //new line
            $item->url = ‘#’; //new line

            $atts[‘data-toggle’] = ‘dropdown’; //this doesn’t work because $atts doesn’t exists

            }

            • flynsarmy

              You referring to this?

              add_filter('nav_menu_link_attributes', function($atts, $item, $args) {
              if ( $args->has_children )
              {
              $atts['data-toggle'] = 'dropdown';

              It’s written right there where it comes from…

              $atts isn’t even mentioned in start_el() in my code above?

  • Debendra Maharjan

    it says syntax error in this line:

    $output .= “n$indentn”;

    • emnfernandez

      try
      $output .= $indent . ”;

      • Debendra Maharjan

        Yes i did the same, but still i get no result, step 2 should be kept at the theme function right ?? i kept there but i see no result, so i directly went to wp-includes>nav-template and i modified there, it worked for me, but i know if we update wordpress then again it will create problem ๐Ÿ™

        • Nathan Payne

          $output .= “n$indentn”;

          Is what the line should be.

  • Fabio

    I got an error with both snippets. The first one I solved like this: $output .= $indent . ”; and works perfect. The second snippet is throwing the following error: Parse error: syntax error, unexpected T_FUNCTION (which points to the first line, add_filter(‘nav_menu_link_attributes’, function($atts, $item, $args) {

    any ideas how to make this work without any errors?

    • flynsarmy

      You’re on an outdated version of PHP (Probably 5.3). You’ll need to instead use


      add_filter('nav_menu_link_attributes', 'my_func_name');
      function my_func_name($atts, $item, $arts) {

      • Fabio

        wow, that was fast, thank you ๐Ÿ™‚

        However, with that code now I get the following error:

        Parse error: syntax error, unexpected ‘,’ in functions.php on line 220

        and if I take the commas, I get the following:
        Warning: Missing argument 2 for my_func_name() in functions.php on line 212

        Warning: Missing argument 3 for my_func_name() in functions.php on line 212

        that line 212 is }, 10, 3);

        • flynsarmy

          Can you paste your code please? You should be able to debug/fix basic PHP syntax errors yourself though…

  • Matt Lane

    OK, I’ve got this all in place and working (sorted the two errors fabio had myself) and i can see the child dropdown being made (in code) it adds an arrow to the parent item but when i click it the parent menu disappears – so when i try to open my child submenu the parent menu closes

    any ideas? thanks in advance

  • syed akber

    Thanks a lot its working for me.

  • anil jangid

    function menu_set_dropdown( $sorted_menu_items, $args ) {
    $last_top = 0;
    foreach ( $sorted_menu_items as $key => $obj ) {
    // it is a top lv item?
    if ( 0 == $obj->menu_item_parent ) {
    // set the key of the parent
    $last_top = $key;
    } else {
    $sorted_menu_items[$last_top]->classes[‘dropdown’] = ‘dropdown’;
    }
    }
    return $sorted_menu_items;
    }
    add_filter( ‘wp_nav_menu_objects’, ‘menu_set_dropdown’);

    I Am facing this issue : Missing argument 2 for menu_set_dropdown()

    Please Resolve this issue thanks