Joss Posted January 31, 2013 Share Posted January 31, 2013 Oh, gawd.... its one of my Bootstrap moments again! I have been pretty determined to do a better version of my boostrap menu markup - and, well, THAT was a steep learning curve! And for the most part I have got there. The bootstrap menu has an issue - it tends to change class names as you go down the list! So, the UL has the class dropdown-menu (and all the lower ones do too) <li> that has no child has no class name but <li> that have children have the class dropdown. <a> That trigger the drop down have a ton of stuff And <li> that are children of the original <li> and have children get the class - dropdown-submen. Here is a rendered example from one of my demos (you can tell its mine because the words Food and Bar appear in it) <ul class="nav"> <li class="active"><a href="/">Home</a></li> <li><a href="/food/">Food</a></li> <li><a href="/the-bars/">The Bars</a></li> <li><a href="/news/">News & Events</a></li> <li class="dropdown open"><a class="dropdown-toggle" data-toggle="dropdown" href="/about/">About<b class="caret"></b></a> <ul class="dropdown-menu"> <li class="dropdown-submenu"><a href="/about/about-us/">About Us</a> <ul class="dropdown-menu"> <li><a href="/about/about-us/about-us-1-2/">about us 1 - 2</a></li> <li><a href="/about/about-us/about-1-1/">about 1 - 1</a></li> </ul> </li> <li class=""><a href="/about/about-us-2/">about us 2</a></li> </ul> </li> </ul> Now, I am very please to say that I have come up with a nice new clean version of the code that does the above. But it only goes as far as the first set of sub-menus. In theory you can keep adding sub-menus to sub-menus till you break the browser, but I don't know how to write it so that from where the sub-menu starts, it keeps rolling out children and grandchildren and greatgrandchildren and ..... Here is the code so far - I have commented it heavily: <?php $homepage = $pages->get("/"); $children = $homepage->children; $children->prepend($homepage); echo "<ul class='nav'>"; foreach($children as $child) { // Some variables $aclass = count($child->children) ? "dropdown-toggle' data-toggle='dropdown" : ''; $dropclass = count($child->children) ? "dropdown" : ''; $dropcaret = count($child->children) ? "<b class='caret'></b>" : ''; $class = $child === $page->rootParent ? " active" : ''; // first level <li> // Start by printing off those that actually have children and are not the homepage. if($child->numChildren > 0 && $child<>$homepage){ echo "<li class='$class $dropclass'><a class='$aclass' href='{$child->url}'>{$child->title}$dropcaret</a>"; // Drop down of second level if(count($child->children)){ echo "<ul class='dropdown-menu'>"; foreach($child->children as $subchild){ $subdropclass = count($subchild->children) ? "dropdown-submenu" : ''; echo "<li class='$subdropclass'><a href='{$subchild->url}'>" . $subchild->title . "</a>"; // Second level list // HELP WITH THIS SECTION // This next bit is the sub menu and needs to go on for infinity! // I dont know how to do that. if(count($subchild->children)){ echo "<ul class='dropdown-menu'>"; foreach($subchild->children as $babychild){ echo "<li ><a href='{$babychild->url}'>" . $babychild->title . "</a>"; // 3rd level list echo "</li>"; } echo "</ul>"; } // End of the sub menu bit that I need help with. echo "</li>"; // end second level list } echo "</ul>"; } // end second level echo "</li>"; // End first level // And finally, do the top level menu items that dont have children and chuck in the homepage. }else{ $class = $child === $page->rootParent ? " class='active'" : ''; echo "<li$class><a href='{$child->url}'>{$child->title}</a></li>"; } // end the loop } echo "</ul>"; So: 1. Can some nice person show me how to keep the bit I have marked with "Help with this section" going ad-infinitum? 2. Is there a better way of doing this? I will make the person a cup of really good espresso next time they pop round! Honest! Joss Link to comment Share on other sites More sharing options...
netcarver Posted February 1, 2013 Share Posted February 1, 2013 Hey Joss, Not a total solution, more of an idea to get you started; when you hear phrases like "ad-infinitum" in the context of computation you probably need to be thinking of using recursion rather than iteration. In most programming languages this is handled by having a function that calls itself as many times as needed to get the job done. Something like this perhaps... function renderChildrenOf($parent) { $output = ''; $children = $parent->children; foreach ($children as $child) { // Render this child. NB use of .= which is a PHP shortcut to // concatenate stuff at the end of existing $output. // Adjust this to suit your needs $output .= '<li>' . $child->title . '</li>'; // If this child is itself a parent, then render it's children in their own menu too... if (count($child->children)) { $output .= '<ul class="dropdown-menu">' . renderChildrenOf($child) . '</ul>'; } } return $output; } $homepage = $pages->get("/"); // Setup the start of the top-level menu... $menu = '<ul class="nav">'; // Append something for the homepage (as menus usually squish the root node in with its children anyway)... $menu .= '<li><a href="/">' . $homepage->title . '</a></li>'; // Set the ball rolling... $menu .= renderChildrenOf($homepage); // Close the top-level menu... $menu .= '</ul>'; // Show the result to the world... echo $menu; Ok, that's off the top of my head and is meant to give you a starting point to address your ad-infinitum issue. You'll need to experiment some and adjust the markup as needed for bootstrap. Hope that helps. 1 Link to comment Share on other sites More sharing options...
Joss Posted February 1, 2013 Author Share Posted February 1, 2013 Thanks Mr Carver I did play with recursion and processwire chucked me a huge error - but then, I think I was doing it wrong. I will have a play once I have sorted out the little bit of work I have just got from my leaflet run Joss Link to comment Share on other sites More sharing options...
netcarver Posted February 1, 2013 Share Posted February 1, 2013 Ah, ok. If you need any more help with that I should be around in #processwire later this evening. Link to comment Share on other sites More sharing options...
JeffS Posted February 2, 2013 Share Posted February 2, 2013 @joss - here is an example function for rendering a full top bar with bootstrap. https://gist.github.com/4699020 2 Link to comment Share on other sites More sharing options...
Joss Posted February 2, 2013 Author Share Posted February 2, 2013 Hi Jeff I will check that - thanks! Link to comment Share on other sites More sharing options...
Joss Posted February 7, 2013 Author Share Posted February 7, 2013 Hi Netcarver Okay, I am a bit of the way there. What I need to do is say something like: If the child <li> is a child of the homepage (or belongs to the toplevel <ul>), then $dropclass = ' dropdown'; else, $dropclass = ' dropdown-submenu'; I will need to do similar to the <a> tag that needs to go within the li I Basically, the first level of <li> that have children need the class "dropdown" but lower levels of <li> that have chidren need the class "dropdown-submenu" Likewise, the <a> of top level <li> that have children need a class, where as lowever level ones need something else Sorry I have been so slow on this! Joss Link to comment Share on other sites More sharing options...
Soma Posted February 7, 2013 Share Posted February 7, 2013 Joss what you can do is use $page->parents to evaluate the level. if(count($child->parents) == 1) { // top level page } Link to comment Share on other sites More sharing options...
Joss Posted February 7, 2013 Author Share Posted February 7, 2013 Okay, now that has got me much closer! Link to comment Share on other sites More sharing options...
Joss Posted February 7, 2013 Author Share Posted February 7, 2013 One last thing! I can't get this to work: $class = $child === $page->rootParent ? " active" : ''; I need to put the class active into the first level (only) <li> tags when the page is, umm, active. But this doesn't seem to want to work in the code by Netcarver above. It isn't returning anything. Link to comment Share on other sites More sharing options...
Soma Posted February 7, 2013 Share Posted February 7, 2013 $class = ($child === wire("page") && count($child->parents) == 1) ? " active" : ''; if it's inside a function the scope doesn't have $page. Also I think the logik was wrong too. Maybe I'm wrong. Link to comment Share on other sites More sharing options...
Joss Posted February 7, 2013 Author Share Posted February 7, 2013 Okay, it now works and the top level <li> get the active class when their link is active. However, they are not yet getting the class if a child is active. This is using my old menu: http://pwdemo2.stonywebsites.co.uk/about/about-us/ When the sub item is clicked the parent gets an active class, this is not happening with the new menu ..... yet! This is the complete code so far, thanks guys! EDIT: Of course, the home page doesn't get active either because it is outside of the loop.... <?php /* * Thanks to Netcarver and Soma for sorting this out for me */ $homepage = $pages->get("/"); function renderChildrenOf($parent) { $output = ''; $children = $parent->children; foreach ($children as $child) { if(count($child->parents) == 1){ $atoggle = 'class="dropdown-toggle" data-toggle="dropdown" '; // The toggle for the top level <a> tags when there are children $dropclass = ' dropdown'; // The class for the top level <li> tabs when they have children } else { $dropclass = ' dropdown-submenu'; // The class for lower level <li> that also have children } $class = ($child === wire("page")) ? " active" : ''; // Makes the top level <li> acive when on that page // If this child is itself a parent, then render it's children in their own menu too... if (count($child->children)) { $output .= '<li class=" ' . $dropclass . '' . $class . ' "><a href="' . $child->url .'" '. $atoggle .'>' . $child->title . '</a>'; $output .= '<ul class="dropdown-menu">' . renderChildrenOf($child) . '</ul>'; $output .= '</li>'; } else { $output .= '<li class=" '. $class .' "><a href="' . $child->url .'" >' . $child->title . '</a></li>'; } } return $output; } // Setup the start of the top-level menu... $menu = '<ul class="nav">'; // Append something for the homepage (as menus usually squish the root node in with its children anyway)... $menu .= '<li><a href="/">' . $homepage->title . '</a></li>'; // Set the ball rolling... $menu .= renderChildrenOf($homepage); // Close the top-level menu... $menu .= '</ul>'; // Show the result to the world... echo $menu; Link to comment Share on other sites More sharing options...
Soma Posted February 7, 2013 Share Posted February 7, 2013 Joss, I rewrote your script to actually work and did some refactoring. I think you'll live it. Let me know if it works. Here's the code: https://gist.github.com/somatonic/4743011 Which gives you this: <ul class='nav'> <li><a href='/'>Home</a></li> <li class='dropdown active'><a href='/about/' class="dropdown-toggle" data-toggle="dropdown">About 2</a> <ul class='dropdown-menu'> <li><a href='/about/what/'>Child page example 1</a></li> <li class='dropdown-submenu'><a href='/about/background/'>Child page example 2</a> <ul class='dropdown-menu'> <li><a href='/about/background/another-child-page/'>Another Child Page</a></li> <li><a href='/about/background/another-child-page-2/'>Another Child Page 2</a></li> <li><a href='/about/background/another-page/'>Another Page</a></li> </ul> </li> </ul> </li> <li class='dropdown'><a href='/templates/' class="dropdown-toggle" data-toggle="dropdown">Templates</a> <ul class='dropdown-menu'> <li><a href='/templates/asdasd/'>asdasd</a></li> </ul> </li> <li><a href='/site-map/'>Site Map</a></li> </ul> 2 Link to comment Share on other sites More sharing options...
Joss Posted February 7, 2013 Author Share Posted February 7, 2013 Absolutely fantastic! Not sure what else to say really Right, well that make the Bootstrap basic profile complete, since that was the last element to get properly right. I will also post this on the wiki to replace the current, out of date version that is on there. Next thing is I need to study it and understand how it works - and so I will learn a little bit more..... Thanks mate Joss Link to comment Share on other sites More sharing options...
Soma Posted February 7, 2013 Share Posted February 7, 2013 Your welcome. Ok, I updated the script above a little, so it also would works for pages not on top level but further down on a branch. The second parameter can be used to hand over the prepended root page, this is to avoid it being used for it's children again, since it will result in endless recursion. So you can for example now do this: // bundle up the first level pages and prepend the root page $root = $pages->get("/about/"); $pa = $root->children; $pa = $pa->prepend($root); // Set the ball rolling... echo renderChildrenOf($pa,$root); Link to comment Share on other sites More sharing options...
Joss Posted February 7, 2013 Author Share Posted February 7, 2013 Okay - I will update my install. Thanks again Soma Edit: The Nav2 and dropdown-menu2 need to be changed to nav and dropdown-menu or it wont work. Link to comment Share on other sites More sharing options...
Soma Posted February 7, 2013 Share Posted February 7, 2013 Yep I updated the code again above. I had to use other classes because some css there is already hides the sublevels... Link to comment Share on other sites More sharing options...
Joss Posted February 8, 2013 Author Share Posted February 8, 2013 Hi Soma Just noticed that the Home link is receiving the $atoggle variable, so it cant link. Not sure how I missed that before Joss Link to comment Share on other sites More sharing options...
Joss Posted February 8, 2013 Author Share Posted February 8, 2013 Okay, solved it! I added && $child !== $root as a condition to line 17 So the final final script is (I hope): :: soma: removed script to keep thread clean :: :: see final script here https://gist.github.com/somatonic/4743011 :: Link to comment Share on other sites More sharing options...
Joss Posted February 8, 2013 Author Share Posted February 8, 2013 Oh rats, not quite there. If I create a child page to a top level page and then make it unpublished and/or hidden, then its parent gets a UL and the classes, even though there are no <li> generated. Link to comment Share on other sites More sharing options...
Soma Posted February 9, 2013 Share Posted February 9, 2013 Nice catch joss! Yeah that was missing. The problem was that $child->numChildren isn't access aware so I use now count($child->children). numChildren also count unpublished and hidden pages. It's now fixed. I also rewrote some if else in there to keep thing short. Since I'm sick updating it here I created a snippelt. Also for Sublime2 Fetchers and myself in case I need it once https://gist.github.com/somatonic/4743011 Link to comment Share on other sites More sharing options...
Soma Posted February 9, 2013 Share Posted February 9, 2013 Watch out for the nav2 malware just updated the gist... 1 Link to comment Share on other sites More sharing options...
Joss Posted February 9, 2013 Author Share Posted February 9, 2013 Yeeee Haw!!!! By George I think you've done it! Thanks Soma 1 Link to comment Share on other sites More sharing options...
ryan Posted February 9, 2013 Share Posted February 9, 2013 The problem was that $child->numChildren isn't access aware so I use now count($child->children). numChildren also count unpublished and hidden pages. It's now fixed. I also rewrote some if else in there to keep thing short. If dealing with potential large quantities of pages, a fairly efficient way to check if the page has visible children: if($page->child->id) { // page has children } else { // page has no children // or you are paginating and none are left } If you are going to be using $page->children either way, then it's better just to use it the way you are though. If pagination is involved (whether with the children or something else), then it's better to $page->children->getTotal(); to eliminate the possibility of count() returning 0 due to being at the end of a pagination set. 1 Link to comment Share on other sites More sharing options...
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now