Jump to content

Bootstrap 3 Navigation with multiple level/tier fix


Damienov
 Share

Recommended Posts

Hi everyone, I've made a snippet update for the bootstrap 2.2.2 navbar by Soma to Bootstrap 3.x. I also added a multi level menu fix since it was removed from Bootstrap 3.

Screenshot:

0Tgpq5P.jpg

topnav_inc

<?php
/*
Navigation for ProcessWire using the Bootstrap 2.2.2 markup
This menu was written by Soma based on work by NetCarver and a bit thrown in by Joss

Navigation Bootstrap 3 update by Damienov, with multi level dropdown support fix
*/

function renderChildrenOf($pa, $output = '', $level = 0)
{
    $output = '';
    $level++;

    foreach ($pa as $child) {
        $atoggle = '';
        $class = '';

        if ($child->numChildren && count($child->parents) == 1) {
            $class .= 'dropdown';
            $atoggle .= ' class="dropdown-toggle" data-toggle="dropdown"';
        } else if ($child->numChildren && count($child->parents) > 1 ) {
            $class .= 'dropdown-submenu';
            $atoggle .= ' class="dropdown-toggle"';
        } else if ($child->numChildren && $child->id != 1) {
            $class .= 'dropdown-menu';
        }

        // Makes the current page and it's top level parent add an active class
        $class .= ($child === wire("page") || $child === wire("page")->rootParent) ? " active" : '';
        $class = strlen($class) ? " class='" . trim($class) . "'" : '';

        if ($child->numChildren && count($child->parents) == 1) {
            // Add Caret if have children
            $output .= "<li$class><a href='$child->url'$atoggle>$child->title <b class='caret'></b></a>";
        } else if ($child->numChildren && count($child->parents) > 1) {
            $output .= "<li$class><a tabindex='-1' href='$child->url'$atoggle>$child->title</a>";
        } else {
            $output .= "<li$class><a href='$child->url'$atoggle>$child->title</a>";
        }

        // If this child is itself a parent and not the root page, then render it's children in their own menu too...
        if ($child->numChildren && $child->id != 1) {
            $output .= renderChildrenOf($child->children, $output, $level);
        }
        $output .= '</li>';
    }
    $outerclass = ($level == 1) ? "nav navbar-nav" : 'dropdown-menu';
    return "<ul class='$outerclass'>$output</ul>";
}

// bundle up the first level pages and prepend the root home page
$homepage = $pages->get(1);
$pa = $homepage->children;
$pa = $pa->prepend($homepage);

// Set the ball rolling...
echo renderChildrenOf($pa);

Add to your CSS

/* Bootstrap 3 navbar with multi level fix */
.dropdown-submenu{
    position:relative;
}

.dropdown-submenu > .dropdown-menu
{
    top:0;
    left:100%;
    margin-top:-6px;
    ;
    -webkit-border-radius:0 6px 6px 6px;
    -moz-border-radius:0 6px 6px 6px;
    border-radius:0 6px 6px 6px;
}

.dropdown-submenu:hover > .dropdown-menu{
    display:block;
}

.dropdown-submenu > a:after{
    display:block;
    content:" ";
    float:right;
    width:0;
    height:0;
    border-color:transparent;
    border-style:solid;
    border-width:5px 0 5px 5px;
    border-left-color:#cccccc;
    margin-top:5px;
    margin-right:-10px;
}

.dropdown-submenu:hover > a:after{
    border-left-color:#ffffff;
}

.dropdown-submenu .pull-left{
    float:none;
}

.dropdown-submenu.pull-left > .dropdown-menu{
    left:-100%;
    margin-left:10px;
    -webkit-border-radius:6px 0 6px 6px;
    -moz-border-radius:6px 0 6px 6px;
    border-radius:6px 0 6px 6px;
}

note:

My php skills are crappy :-X , so feel free to add some fixes on the code ;)

  • Like 4
Link to comment
Share on other sites

Currently, if you have a header item called About Us that links to a page, if it has a child, the item cannot be linkable because that wont work on mobile devices.

What would be nice is in those circumstances to duplicate the About Us link and make it the first child of itself. So you get:

About Us

     About Us

     Child

Link to comment
Share on other sites

Currently, if you have a header item called About Us that links to a page, if it has a child, the item cannot be linkable because that wont work on mobile devices.

What would be nice is in those circumstances to duplicate the About Us link and make it the first child of itself. So you get:

About Us

     About Us

     Child

Since changing it would break the menu on mobile, perhaps adding an empty template+ empty placeholder trough processwire would be best?

Link to comment
Share on other sites

Yes, my way round it previously has been to make parent items nothing. You can actually add a bit of code so that that if an item has children then $childUrl beomes #

I added this to a foundation version (based on a later version of the bootstrap menu, I think - cant quite remember now)

        $has_children = count($child->children) ? true : false;

        if($has_children && $child !== $root) {

                $class .= 'has-dropdown'; // sub level Foundation dropdown li class
                $childUrl = "#"; // stop parents being clickable

        } else {
            $childUrl = $child->url; // if does not have children, then get the page url
        }
 
But repeating the parent item as a child would be the ultimate - then use media queries to show or not to show. That way you can preserve your tree structure but still have it work.
 
I have to say, I am tending to avoid complicated menus entirely now.
  • Like 1
Link to comment
Share on other sites

Currently, if you have a header item called About Us that links to a page, if it has a child, the item cannot be linkable because that wont work on mobile devices.

What would be nice is in those circumstances to duplicate the About Us link and make it the first child of itself. So you get:

About Us

     About Us

     Child

I actually love the way meanmenu handles this issue:

http://www.meanthemes.com/demo/meanmenu/demo.html

  • Like 1
Link to comment
Share on other sites

  • 2 months later...

EDITED OUT - found my mistake.

How to add piece of html at the end of menu, before final </ul>? It doesn't execute additional echo at the end of the function. (want to add separate menu button with Call To Action)

Not knowing php sucks :/

Link to comment
Share on other sites

  • 2 years later...

@Marcel Epp  You could add a new parameter to the function.

Assuming you are using the code shown in the first post, the following code should work (not tested) :

function renderChildrenOf($pa, $maxDepth = 1, $output = '', $level = 0) // MODIFIED_CODE
{
    $output = '';
    $level++;

    foreach ($pa as $child) {
        $atoggle = '';
        $class = '';

        if ($child->numChildren(true) && count($child->parents) == 1) {
            $class .= 'dropdown';
            $atoggle .= ' class="dropdown-toggle" data-toggle="dropdown"';
        } else if ($child->numChildren(true) && count($child->parents) > 1 ) {
            $class .= ($maxDepth > 0) ? 'dropdown-submenu' : ''; // MODIFIED_CODE
            $atoggle .= ($maxDepth > 0) ? ' class="dropdown-toggle"' : ' '; // MODIFIED_CODE
        } else if ($child->numChildren(true) && $child->id != 1) {
            $class .= 'dropdown-menu';
        }

        // Makes the current page and it's top level parent add an active class
        $class .= ($child === wire("page") || $child === wire("page")->rootParent) ? " active" : '';
        $class = strlen($class) ? " class='" . trim($class) . "'" : '';

        if ($child->numChildren(true) && count($child->parents) == 1) {
            // Add Caret if have children
            $output .= "<li$class><a href='$child->url'$atoggle>$child->title <b class='caret'></b></a>";
        } else if ($child->numChildren(true) && count($child->parents) > 1) {
            $output .= "<li$class><a tabindex='-1' href='$child->url'$atoggle>$child->title</a>";
        } else {
            $output .= "<li$class><a href='$child->url'$atoggle>$child->title</a>";
        }

        // If this child is itself a parent and not the root page, then render it's children in their own menu too...
        if ($child->numChildren(true) && $child->id != 1 && $maxDepth > 0) { // MODIFIED_CODE
            $maxDepth--;   // MODIFIED_CODE
            $output .= renderChildrenOf($child->children, $maxDepth, $output, $level); // MODIFIED_CODE
        }
        $output .= '</li>';
    }
    $outerclass = ($level == 1) ? "nav navbar-nav" : 'dropdown-menu';
    return "<ul class='$outerclass'>$output</ul>";
}

// bundle up the first level pages and prepend the root home page
$homepage = $pages->get(1);
$pa = $homepage->children;
$pa = $pa->prepend($homepage);

// Set the ball rolling...
echo renderChildrenOf($pa);

Search the comments  MODIFIED_CODE to see whats going on in the function.

 

Edited by flydev
code tested// added @Horst suggestion : numChildren(true)
  • Like 2
Link to comment
Share on other sites

On 5.8.2016 at 2:27 PM, flydev said:

Assuming you are using the code shown in the first post, the following code should work (not tested) :

@flydev: only one suggestion: it should be

$child->numChildren(true)

instead of

$child->numChildren // what is the shortcut to $child->numChildren(false)

 

Read here why: https://processwire.com/talk/topic/13950-show-image-childrens-children/#comment-125410

 

  • Like 3
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...