Okay. We’ve got a couple of other posts on this as well, but now with more experience we’ve made some additions and changes to the original code.
With a few lines of PHP code, we can alter the menu markup genesis creates to match Twitter Bootstrap’s navbar (or nav-pills, nav-tabs – see below) as your primary navigation menu. Let’s go through the code step by step and then at the end, we’ll post the whole thing. For your reference, here is sample bootstrap navbar markup.
First, create a menu in the WordPress dashboard and make sure you check it off to make it your primary navigation.
All the code we discuss can all go in your functions.php file.
First we need to turn off the genesis creation of the primary menu/navigation by using remove_action:
remove_action( 'genesis_after_header', 'genesis_do_nav');
Next we need to direct WordPress to use our function for the primary navigation:
add_action( 'genesis_after_header, 'my_do_nav');
function my_do_nav(){
// we will fill this in below
}
The my_do_nav function in the end needs to call wp_nav_menu($args). $args is an array of options. For the purposes of simplicity, this code will assume we want the navbar component of Twitter Bootstrap. At the end of the article we’ll discuss adding in the other nav-pills and nav-tabs components. The following is my_do_nav function. First it makes sure that a primary navigation menu is set before continuing. Next it fills in the wp_nav_menu $args to match what a navbar should have.
function my_do_nav() {
//If menu has not been assigned to Primary Navigation, abort.
if ( ! has_nav_menu( 'primary' ) ) {
return;
}
//set the $args
$container_class = 'navbar navbar-default navbar-static-top';
$menu_class = 'nav navbar-nav navbar-right';
$items_wrap = '';
wp_nav_menu( array(
'container' => 'nav',
'container_class' => $container_class,
'menu_class' => $menu_class,
'items_wrap' => $items_wrap,
'theme_location' => 'primary'
) );
}
This code will get us partly to the same markup. We still need to label dropdown menus and add the ‘active’ class on active menu items since genesis uses the class “sub-menu” and “current-menu-item.” Lets first add the active and dropdown classes to the <li> tags of our menu. We do this by hooking into the WordPress filter, ‘nav_menu_css_class.’
add_filter('nav_menu_css_class', 'my_add_dropdown_active',10);
/** adds the necessary classes bootstrap expects for menu items, , with children and current-menu-* items */
function bfg_add_dropdown_active($classes) {
if(in_array('menu-item-has-children',$classes) ){
$classes[] = 'dropdown';
}
if(in_array('current-menu-item',$classes) ){
$classes[] = 'active';
}
if(in_array('current-menu-parent',$classes) ){
$classes[] = 'active';
}
return $classes;
}
We are much closer now. Bootstrap has the class of ‘dropdown-toggle’ and the attribute ‘data-toggle’ set in the anchor tags/links of menu items that have a submenu. We can hook to another filter to add these. (These filters are found in WordPress’s Walker_Nav_Menu class). WordPress does not add the attributes class or data-toggle to the anchor tag so the function below doesn’t have save any previous value contained in $atts[‘class’] or $atts[‘data-toggle’] variables.
add_filter('nav_menu_link_attributes', 'my_link_attributes',10,2);
/** adds attributes to anchor, , of a dropdown menu */
function my_link_attributes($atts,$item) {
if(in_array('menu-item-has-children',$item->classes)) {
$atts["class"] = "dropdown-toggle";
$atts["data-toggle"] = "dropdown";
}
return $atts;
}
A couple more things to get the basic navbar markup. The <ul> element for a dropdown has the class ‘sub-menu’ in it, but we need it to be ‘dropdown-menu.’ This is added using the Walker_Nav_Menu and there are no filters we can hook into. So we have to extend this class BUT we only have to overwrite a small function. Once we do this we pass in our walker in the $args of wp_nav_menu().
/** Extends the Walker_Nav_menu and overrides the start_lvl function to add class to sub-menu */
class my_Walker_Nav_Menu extends Walker_Nav_Menu {
function start_lvl(&$output, $depth) {
$indent = str_repeat("\t", $depth);
$output .= "\n$indent
Now just alter the function my_do_nav and add another key to the array passed to wp_nav_menu. Here’s what calling wp_nav_menu in my_do_nav should now look like:
wp_nav_menu( array(
'container' => 'nav',
'container_class' => $container_class,
'menu_class' => $menu_class,
'items_wrap' => $items_wrap,
'walker' => new my_Walker_Nav_Menu(),
'theme_location' => 'primary'
) );
Assuming you’ve also enqueued the bootstrap.css and bootstrap.js files in your theme, your primary navigation should be a bootstrap navbar, complete with responsiveness.
Now the navbar can also contain a brand and a search bar. We can add that functionality to our my_do_nav function by adding a couple of filter hooks and hooking into them.
Here we only have to declare an empty string called $navbar_content and add the filter hooks to our $items_wrap assignment in my_do_nav:
$container_class = 'navbar navbar-default navbar-' . sanitize_text_field( $bfg_navbar_type );
$menu_class = 'nav navbar-nav navbar-' . sanitize_text_field( $bfg_navbar_align );
$navbar_content = '';
$items_wrap = '';
Now hook into them and add your content how you see fit. Here’s what we did to add the nav-brand:
add_filter('my_navbar_brand_content', 'my_navbar_brand');
function my_navbar_brand($navbar_content) {
$image = get_stylesheet_directory_uri() . '/images/logo.png';
list($width, $height, $type, $attr) = getimagesize($image);
$url = get_home_url();
$brand = get_bloginfo('name');
$navbar_content .= '';
$navbar_content .= '
';
if ($height > 50) { //center the menu items in navbar if brand is tall.
$navbar_content .= '';
}
return $navbar_content;
}
So this is how we integrated the Twitter Bootstrap navbar into the Genesis Framework. It is actually a small part of a project where we are integrating other Bootstrap components into Genesis. Here’s a link to that github repo. Also, we are adding articles on adding the different components on this blog such as: Integrating Bootstrap’s Breadcrumbs into Genesis Theme and Using WordPress Genesis Framework with Bootstrap CSS for layout
Here’s the code on adding the menu’s. It’s a work in progress, but this code offers you the ability to place the menu at other locations by removing some comments and changing the type of menu by changing some variables. Let us know what you think.
/** Integrating Bootstrap NAVs
*
* Tab and Pill navs require role='presentation' as an attribute to the of a menu item
* Currently, there is no filter to add an attribute to the (only added id or class)
* One way to do this would be to override the start_el function but seems like a lot of code to
* copy to only add an attribute. Created a simple jQuery solution in script.js.
*
* fixed-top and fixed-bottom require padding-top and padding-bottom, respectively. This is also
* added via jQuery in script.js since we can get the computed height of the nav and add the appropriate
* padding.
*/
//Menu variables:
//An admin interface could be created to change these values on the dashboard
$bfg_genesis_menu = false;
$bfg_menu_type = 'navbar'; //Possible values: tab, pill, navbar
$bfg_navbar_type = 'static-top'; //Possible values: static-top, fixed-top, fixed-bottom
$bfg_navbar_align = 'right';
//
if (!$bfg_genesis_menu) {
//Take over nav
remove_action( 'genesis_after_header', 'genesis_do_nav' );
remove_action( 'genesis_after_header', 'genesis_do_subnav' );
//TODO test adding subnav
//Needed for all Bootstrap menus
add_filter('nav_menu_link_attributes', 'bfg_link_attributes',10,2);
add_filter('nav_menu_css_class', 'bfg_add_dropdown_active',10);
if ( $bfg_navbar_type === 'fixed-bottom' ) {
add_filter( 'body_class', 'bfg_fixed_bottom_body_class' );
}
if ( $bfg_navbar_type === 'fixed-top' ) {
add_filter( 'body_class', 'bfg_fixed_top_body_class' );
}
//TODO add a admin user interface for these options.
//************Uncomment a single section to alter location of Primary Nav - Primary 1 thru 5
//Primary 1***********Uncomment the following for Bootstrap primary nav replacing header*****************
//Genesis | Theme Settings | Header settings determines if nav_brand is shown or site_title is shown
// "Image Logo" will show nav_brand and site_title will be there but hidden
/* remove_action( 'genesis_header', 'genesis_do_header' ); //remove genesis header
add_action( 'genesis_header', 'bfg_custom_primary_do_nav', 5 );
add_filter('genesis_structural_wrap-header', 'bfg_no_wrap'); //remove structural wrap
add_filter('bfg_navbar_brand_content', 'bfg_navbar_brand_header'); //add brand*/
//Primary 2*********Uncomment the following for Bootstrap primary nav after Genesis Header****************
add_action( 'genesis_after_header', 'bfg_custom_primary_do_nav', 5 );
//Primary 3*********Uncomment the following for Bootstrap primary nav before footer**********************
/* add_action( 'genesis_before_footer', 'bfg_custom_primary_do_nav'); */
//Primary 4*********Uncomment the following for Bootstrap primary nav as footer**********************
/* remove_action('genesis_footer','genesis_do_footer');
add_action( 'genesis_footer', 'bfg_custom_primary_do_nav');
add_filter('genesis_structural_wrap-footer', 'bfg_no_wrap'); //remove structural wrap
add_filter('bfg_navbar_brand_content', 'bfg_navbar_brand'); //add brand*/
//Primary 5*********Uncomment the following for Bootstrap primary nav after Genesis Footer****************
/* add_action( 'genesis_after_footer', 'bfg_custom_primary_do_nav' );*/
// Uncomment out if you want a search bar in the navbar
// add_filter('bfg_navbar_content', 'bfg_navbar_search_form');
}
function bfg_fixed_bottom_body_class( $classes) {
$classes[] = 'fixed-bottom';
return $classes;
}
function bfg_fixed_top_body_class( $classes) {
$classes[] = 'fixed-top';
return $classes;
}
/** Create primary nav using bootstrap. */
function bfg_custom_primary_do_nav() {
global $bfg_menu_type, $bfg_navbar_type, $bfg_navbar_align;
//If menu has not been assigned to Primary Navigation, abort.
if ( ! has_nav_menu( 'primary' ) ) {
return;
}
//Default values
$container_class = '';
$menu_class = 'nav nav-pills';
$items_wrap = ' %3$s
';
switch ( $bfg_menu_type ) {
case 'pill':
$menu_class = 'nav nav-pills';
$items_wrap = ' %3$s
';
break;
case 'tab':
$menu_class = 'nav nav-tabs';
$items_wrap = ' %3$s
';
break;
case 'navbar':
$container_class = 'navbar navbar-default navbar-' . sanitize_text_field( $bfg_navbar_type );
$menu_class = 'nav navbar-nav navbar-' . sanitize_text_field( $bfg_navbar_align );
$navbar_content = '';
$items_wrap = '';
break;
}
wp_nav_menu( array(
'container' => 'nav',
'container_class' => $container_class,
'menu_class' => $menu_class,
'items_wrap' => $items_wrap,
'walker' => new bfg_Walker_Nav_Menu(),
'theme_location' => 'primary'
) );
}
/** Extends the Walker_Nav_menu and overrides the start_lvl function to add class to sub-menu */
class bfg_Walker_Nav_Menu extends Walker_Nav_Menu {
function start_lvl(&$output, $depth) {
$indent = str_repeat("\t", $depth);
$output .= "\n$indent