Four-level collapsing menu

Feed 23 posts, 7 voices

Avatar
27 posts

Hello,

Having some problems with the Four-level collapsing menu from the navigation cookbook.
The problem is that if any part of the slug is the same as another slug, then it will show the sub-levels for both of them. For example:

- Start
- About ABC
- About CDE

And when I click “About ABC” it will show both sub-levels.

- Start
- About ABC
- - Lorem
- - Ipsum
- About CDE
- - Adespcing
- - Consectutuer

Is there any way to match the full combination of words?

 
Avatar
1493 posts

This might be a job for preg_match, and mtylerb is our resident pattern-matching pro. :) I can’t get to this just now, but will check it out in the next day or two — by which time hopefully it will already be sorted! (My cunning plan.)

 
Avatar
316 posts

Give me a bit to play around. Busy week, sorry I haven’t been around much.

ibuypink, could you list all the code you are using to create this menu? I’ll modify yours directly and then give you whatever solution I find with a bit of an explanation.

 
Avatar
27 posts

Here’s my code:

		<?php $subPageId = explode('/', $_SERVER['REQUEST_URI']); $level2=$subPageId[1]; $level3=$subPageId[2]; $level4=$subPageId[3]; ?><div class="sidebar-1">
			<ul class="nav-main">
				<li<?php echo url_match('/') ? ' class="active"': ''; ?>><a href="<?php echo URL_PUBLIC; ?>">Start</a></li>
				<?php foreach($this->find('/')->children() as $menu): ?>
				<li<?php echo (in_array($menu->slug, explode('/', $this->url)) ? ' class="active"': null); ?>><?php echo $menu->link($menu->title); ?>
			        <?php if ($level2 != '' && strpos($_SERVER['REQUEST_URI'],$menu->slug) == true) : ?>
			        <?php $page2 = $this->find($level2); ?>
			        <ul class="nav-sub-1">
				<?php foreach ($page2->children(array()) as $menu2): ?>
					<li<?php echo (in_array($menu2->slug, explode('/', $this->url)) ? ' class="active"': null); ?>><?php echo $menu2->link($menu2->title); ?>              
					</li>
				<?php endforeach; ?>
				</ul>
				<?php endif; ?>
				</li>
			<?php endforeach; ?> 
			</ul>
</div>
 
Avatar
316 posts

Thanks Ibuypink. I think I know where the problem lies. I’m just about to head off to work for the night, hopefully I’ll have a solution for you tomorrow, if someone doesn’t beat me to it. :-)

 
Avatar
8 posts

I decided that a 3 or 4 level collapsing menu wasn’t any good for me, so I wrote the following snippet that works for any number of levels, and doesn’t get confused by slug names:

<?php
function stackup($page, $level, &$astack)
  {
  $ancestor = $page->parent;
  if ($ancestor)
    {
    $astack[$level + 1] = $ancestor;
    stackup($ancestor, $level + 1, $astack);
    }
  }
function unstack($current, $max, $stack)
  {
  $here = $stack[$current];
  $next = $stack[$current + 1];
  $childs = $here->children();
  $count = count($childs);
  if ($count > 0)
    {
    echo '<ul>'."\n";
    foreach ($childs as $child)
      {
      if ($child->breadcrumb == $next->breadcrumb)
        {
        echo '<li>'.$child->link()."\n";
        if ($current < $max) {
          unstack($current + 1, $max, $stack);
          echo '</li>'."\n";
          }
        else echo '<li>'.$child->link().'</li>'."\n";
        }
      else echo '<li>'.$child->link().'</li>'."\n";
      }
    echo '</ul>'."\n";
    }
  }
$astack[0] = $this;
stackup($this, 0, $astack);
$bstack = array_reverse($astack);
$lev = count($bstack) - 1;
echo '<ul><li>'.$bstack[0]->link()."\n";
unstack(0, $lev, $bstack);
echo '</li></ul>'."\n";
?>

It does a recursive ascent from the current page to Home (stackup function) followed by a recursive descent writing nested <ul> menus (unstack function).

As I’m new to PHP the code might not be the best, but it does work.

 
Avatar
316 posts

Thanks peter_b! That’s actually the exact way I was heading! The only thing I might add a little later is the ability to set class=“current” to the current line item.

 
Avatar
1493 posts

@peter_b – very nice solution — thanks for this!

It is working beautifully for me in 0.9.4. I think there are still some wrinkles to be worked out in the current 0.9.[5-to-be] version in SVN, though. Both this code and my “inline nav” code break when Frog is in a subdir. Hopefully that will be sorted before it becomes a Release Candidate, though!

 
Avatar
27 posts

Thanks peter_b! Just what I needed. Although, as mtylerb alreadey noted, it would be nice with a class=“current”. Is there an easy fix?

 
Avatar
8 posts

It’s simple to do using the built-in feature of the link() function – the second optional argument is a string to be added into the <a> tag, so setting this to ‘class=“current”’ does it. See the modified code below.

<?php

function stackup($page, $level, &$astack)
  {
  $ancestor = $page->parent;
  if ($ancestor)
    {
    $astack[$level + 1] = $ancestor;
    stackup($ancestor, $level + 1, $astack);
    }
  }

function unstack($current, $max, $stack)
  {
  $here = $stack[$current];
  $next = $stack[$current + 1];
  $childs = $here->children();
  $count = count($childs);
  if ($count > 0)
    {
    echo '<ul>'."\n";
    foreach ($childs as $child)
      {
      if ($child->breadcrumb == $next->breadcrumb)
        {
        echo '<li>'.$child->link('', 'class="current"')."\n";
        if ($current < $max) {
          unstack($current + 1, $max, $stack);
          echo '</li>'."\n";
          }
        else echo '<li>'.$child->link().'</li>'."\n";
        }
      else echo '<li>'.$child->link().'</li>'."\n";
      }
    echo '</ul>'."\n";
    }
  }

$astack[0] = $this;
stackup($this, 0, $astack);
$bstack = array_reverse($astack);
$lev = count($bstack) - 1;
if ($lev == 0) echo '<ul><li>'.$bstack[0]->link('', 'class="current"')."\n";
else echo '<ul><li>'.$bstack[0]->link()."\n";
unstack(0, $lev, $bstack);
echo '</li></ul>'."\n";

?>
 
Avatar
1493 posts

@peter_b – I really like this system … but I think the adding the “current” class is going to be a little trickier! With the way you have set it out above, all the subpages in the current path (not just the current node) get the class="current" added. That is, in the following scenario, if “Sub1 B First” is the “active/displayed” page:

Home Page
 > Page 1             <---- also current
 > > Sub1 A
 > > Sub1 B           <---- also current
 > > > Sub1 B First   <---- current <---- Displayed page
 > > > Sub1 B Second
 > > Sub 1 C
 > Page 2
 > etc.

… then “Sub1 B First”, and “Sub1 B”, and “Page 1” will all be tagged with “current”.

Ideally, of course, you just want the current-current :) page to have the active state!

And how you do that has eluded me so far….

 
Avatar
27 posts

@peter_b – Thanks!

@David – I don’t really agree with you. It’s true there can only be one active page being viewed by the user, and thus only one page being the current and in your case that’s “Sub1 B Second”. But this page belongs to “Sub1 B” wich is also being viewed, since the menu folds out. And “Sub1 B” is a part of “Page 1”, also being active. And often you need to visually show the user the trail how to find the current page.

 
Avatar
1493 posts

@ibuypink – Yes, you’re right! I even have “multiple-current” states in another nav I built, so why I didn’t think of that … O_o … I don’t know!

Meanwhile, however, I did work out how to add "class='current'", but only to the currently displayed page (just in case anyone did want that functionality!):

<?php

function stackup($page, $level, &$astack)
  {
  $ancestor = $page->parent;
  if ($ancestor)
    {
    $astack[$level + 1] = $ancestor;
    stackup($ancestor, $level + 1, $astack);
    }
  }

function unstack($current, $max, $stack)
  {
  $here = $stack[$current];
  $next = $stack[$current + 1];
  $childs = $here->children();
  $count = count($childs);
  if ($count > 0)
    {
    echo '<ul>'."\n";
    foreach ($childs as $child)
      {
      if ($child->breadcrumb == $next->breadcrumb)
        { if ($current == $max - 1) { $class = 'class="current"'; } else { $class = ''; }
        echo '<li>'.$child->link('', $class)."\n";
        if ($current < $max) {
          unstack($current + 1, $max, $stack);
          echo '</li>'."\n";
          }
        else echo '<li>'.$child->link().'</li>'."\n";
        }
      else echo '<li>'.$child->link().'</li>'."\n";
      }
    echo '</ul>'."\n";
    }
  }

$astack[0] = $this;
stackup($this, 0, $astack);
$bstack = array_reverse($astack);
$lev = count($bstack) - 1;
if ($lev == 0) echo '<ul><li>'.$bstack[0]->link('', 'class="current"')."\n";
else echo '<ul><li>'.$bstack[0]->link()."\n";
unstack(0, $lev, $bstack);
echo '</li></ul>'."\n";
?>

The extra bit that recognizes the currently displayed page is if ($current == $max - 1)..., and then the appropriate $class is added only to that page.

Again, many thanks to peter_b for sorting this one out! I think it makes the “Four-level collapsing nav” in the “cookbook” obsolete!

 
Avatar
8 posts

Thanks for finding the bug in my code – poor testing on my part!

 
Avatar
27 posts

I use this navigation as the only navigation on a site and I get all the pages as a sub-level to the “Start”. Is there any way to modify this navigation, so I’ll have “Start” at the same level as “About” and “Contact”?

- Start
-- About
-- Contact

And also—considering other scenarios—is there a way to combine this navigation with a horizontal top-level navigation, so this navigation only will show sub-pages for the current section?

 
Avatar
8 posts

I’ve used the following CSS to make the top two levels look as if they’re at the same level:

#sidenav ul {
 padding: 0 0 0 0px;
 margin: 0;
 list-style-type: none;
}
#sidenav ul ul ul {
 padding: 0 0 0 10px;
 margin: 0;
 list-style-type: none;
}

So the first <ul> has zero left padding and so does the second <ul>, but the third and subsequent levels get 10px each (ie. 10px for the 3rd, 20px for the 4th, etc).

I haven’t tried to mix top & side menus, as I’ve used the top position for breadcrumbs.

 
Avatar
42 posts

Gonna correct myself.
How to do that if i Click on Page 1, then on sidebar i get only Sub1 A, Sub1 B, Sub1 B First, Sub1 B Second and Sub 1 C

What i mean:

Home Page             <---- don't want to show that one
> Page 1              <---- when i click on main menu on this link then...
> > Sub1 A            <---- ...this link is on sidebar
> > Sub1 B            <---- ...this link is on sidebar
> > > Sub1 B First    <---- ...this link is on sidebar
> > > Sub1 B Second   <---- ...this link is on sidebar
> > Sub 1 C           <---- ...this link is on sidebar
> Page 2              <---- don't want to show that one

Looking forward to any answers

 
Avatar
1493 posts

Hi Kristjan: it would be good to know a bit more about your layout, but it seems to me you want to use the unlimited levels and children in your sidebar, with the line

 $page = $this->find('/');

changed to something like

 $page = $this->find('/$startSlug');

where $startSlug is the slug of “Page 1”.

 
Avatar
58 posts

David, why you don’t see at “Translation” theme? ^^

 
Avatar
1493 posts

@Ax — sorry! I don’t understand!

 
Avatar
42 posts

David can i have your e-mail or Msn so we can chat live sometime? Don’t want to make lots of stupid post here :P

 
Avatar
42 posts

David yes the unlimited levels and children i almost what i want BUT it lists me all pages i need that i shows me only child pages of section Team. Look at the layout on address www.proklubi.ee

 
Avatar
1493 posts

Hi Kristjan – will check into this over the weekend. But if you want email, you can use madebyfrog at gmail dot com. That will find me! :)

 
Avatar
2 posts

peter_b gave me a great help in a menu I’m trying to change, however, I believe there’s a problem with this structure:

Home Page
> Page 1
> > Sub1 A
> > Sub1 B
> > > Sub1 B.1
> > > > Sub1 B.1.1
> > > > Sub1 B.1.2
> > > Sub1 B.2
> > Sub 1 C
> Page 2

When I click on Sub1 B.1 to open Sub1 B.1.1, it opens and closes at the same time and I really don’t know what to do… Can anyone help me please? I want it to stay open… :(