Jump to content
Damienov

Bootstrap 3 Navigation with multiple level/tier fix

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

Share this post


Link to post
Share on other sites

If you want multi level navigation, the system on Foundation works better since it allows a way to navigate back through the levels.

But a useful fix you have done here.

Share this post


Link to post
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

Share this post


Link to post
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?

Share this post


Link to post
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

Share this post


Link to post
Share on other sites

I have to say, I am tending to avoid complicated menus entirely now.

I know, and simple (2 tier) keeps mobile compatibility.

Share this post


Link to post
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

Share this post


Link to post
Share on other sites

Thats kind of cute!

Cute :)

I actually thought it was pretty functional - nicer to be able to click on the parent item or expand it to access the children.

You not sold on the idea?

Share this post


Link to post
Share on other sites

Over in the uk, Cute used like this is saying it is good.

Possibly a London thing ... not sure.

  • Like 2

Share this post


Link to post
Share on other sites

Well the definition of cute that I remember from my Aussie upbringing was:

"Ugly, but interesting"

Glad we got that cleared up :)

  • Like 1

Share this post


Link to post
Share on other sites

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 :/

Share this post


Link to post
Share on other sites

Hi,

how can i change the deepness? I only want to go to level 1 (Tier2). I only need the parent and the child's.

How can i do that?

Share this post


Link to post
Share on other sites

@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

Share this post


Link to post
Share on other sites

Hi flydev,

the code returns an internal server error. I will try to get it to work. Thank you.

Share this post


Link to post
Share on other sites

I did a test on a fresh install and it work fine. How did you implemented it ?

 

The tree

Sans titre0.png

 

Result with $maxDepth = 1

Sans titre1.png

 

Result with $maxDepth = 2

Sans titre2.png

 

  • Like 3

Share this post


Link to post
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

Share this post


Link to post
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

  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...