Jump to content


Photo

Page level and sub-navigation


  • Please log in to reply
36 replies to this topic

#1 Soma

Soma

    Hero Member

  • Moderators
  • 5,041 posts
  • 3794

  • LocationSH, Switzerland

Posted 21 October 2011 - 07:47 AM

How can I create a level dependent subnavigation? Like I want to only echo the 3-level of the page tree relative to the current page.

I tried to creates something, but it gets to complicated for my liking.

Maybe I'm missing the obvious. Following code I check how many parents there are form the root and depending on that output childs or siblings.

<?php 
// start building content navigation on top
$content_nav = '';

// build 4th level navigation only
foreach($page->parents as $level => $p){
	
	// build when on 2th level
	if(count($page->parents) == 3){
	
		if($level == 2 and count($page->children) > 0){
			
			$p3 = $page;
			
			$out = '';	
			$found = $p3->children("template=sub-page|sub-page-wide|list-sub-page|sub-page-iframe");
			
			if($found){
				foreach($found as $child){
					if($child === $page) $class = " on"; else $class= '';
					$out .= "\n\t\t<a class='ajaxy-page{$class}' href='{$child->url}'>$child->title</a> | ";
				}
				$content_nav = "\n\t\t<div class='content-nav'>";
				// remove last 2 chars from string to remove last "|"
				$content_nav .= substr($out,0,strlen($out)-2)."</div>"; 
				echo $content_nav;
			}
		}
		
	}
	// build when on 3th level
	if(count($page->parents) == 4){
		
		if($level == 3){
			
			$p3 = $p;
			
			$out = '';
			$found = $p3->children("template=sub-page|sub-page-wide|list-sub-page|sub-page-iframe");
			if($found){
				foreach($found as $child){
					if($child === $page) $class = " on"; else $class= '';
					$out .= "\n\t\t\t<a class='ajaxy-page{$class}' href='{$child->url}'>$child->title</a> | ";
				}
				$content_nav = "\n\t\t<div class='content-nav'>";
				// remove last 2 chars from string to remove last "|"
				$content_nav .= substr($out,0,strlen($out)-2)."</div>"; 
				echo $content_nav;
			}
		}
	}
	
}


I have a simpler example which is better I think, but I would love to may hear alternatives, or ideas although I can't think of anything else atm.

<?php
if($page->parents->count() == 3 and $page->numChildren > 0) { 
	echo "<ul>";
	foreach($page->children as $child){
		$class = $child === $page ? 'class="active"' : '';
		echo "\n<li><a href='$child->url' {$class}>{$child->title}</a></li>";
	}
	echo "</ul>";
}
if($page->parents->count() > 3) { 
	echo "<ul>";
	foreach($page->parent->children as $child){
		$class = $child === $page ? 'class="active"' : '';
		echo "\n<li><a href='$child->url' {$class}>{$child->title}</a></li>";
	}
	echo "</ul>";
}


@somartist | modules created | support me, flattr my work flattr.com


#2 ryan

ryan

    Reiska

  • Administrators
  • 7,783 posts
  • 6527

  • LocationAtlanta, GA

Posted 21 October 2011 - 08:41 AM

I'll focus on your simple example because I was able to understand that one better. The most obvious way to simplify this would be to avoid repeating the same code by bundling it into a function.

<?php

function renderPageList($children) { 
    $out = "<ul>";
    foreach($children as $child) {
        $class = $child === $page ? 'class="active"' : '';
        $out .= "\n<li><a href='{$child->url}' $class>{$child->title}</a></li>";
    }
    return $out . "</ul>";
}

if(count($page->parents) == 3 && $page->numChildren > 0) echo renderPageList($page->children);
if(count($page->parents) > 3) echo renderPageList($page->parent->children);


#3 slkwrm

slkwrm

    Sr. Member

  • Members
  • PipPipPipPip
  • 317 posts
  • 184

  • LocationTallinn, Estonia

Posted 21 October 2011 - 08:52 AM

Also, maybe you'll find this topic helpful.

#4 Soma

Soma

    Hero Member

  • Moderators
  • 5,041 posts
  • 3794

  • LocationSH, Switzerland

Posted 21 October 2011 - 09:07 AM

Thanks a lot Ryan, I know it can be simplyfied using a function like you showed.
The main problem I think is with more complicated scenarios with more sublevels and such, if having to always going relatively from the current page it's really hard to get the whole logic right.

@somartist | modules created | support me, flattr my work flattr.com


#5 Soma

Soma

    Hero Member

  • Moderators
  • 5,041 posts
  • 3794

  • LocationSH, Switzerland

Posted 21 October 2011 - 09:10 AM

Also, maybe you'll find this topic helpful.


Thanks slkwrm, but I know that and I have no problems with straight recursive from top navigations. maybe I could try the navigation function from apeisa to only output a certain level depending on the current page "tree path"...


EDIT:
I simplyfied my first horror code example from a few weeks ago to the following. :)
One minor thing Ryan, the $page var doesn't work inside the scope of the function.

<?php

$templates = "template=sub-page|sub-page-wide|list-sub-page|sub-page-iframe";

function renderPageList($children,$page){
	$out = "<div class='content-nav'>";
	foreach($children as $child){
		$class = $child === $page ? " on" : '';
		$out .= "\n\t\t<a class='ajaxy-page{$class}' href='{$child->url}'>$child->title</a> | ";
	}
	$out = substr($out,0,strlen($out)-2) . "</div>"; 
	return $out;
}

if(count($page->parents) == 3 && $page->numChildren > 0) echo renderPageList($page->children($templates),$page);
if(count($page->parents) == 4 && $page->parent->numChildren > 0) echo renderPageList($page->parent->children($templates),$page);


@somartist | modules created | support me, flattr my work flattr.com


#6 ryan

ryan

    Reiska

  • Administrators
  • 7,783 posts
  • 6527

  • LocationAtlanta, GA

Posted 21 October 2011 - 10:19 AM

The main problem I think is with more complicated scenarios with more sublevels and such, if having to always going relatively from the current page it's really hard to get the whole logic right.


I usually use something recursive as a simple solution if having to deal with unknown levels. I think in this case it might also help to see the full scope of results you are trying to achieve, whether visually or in markup form. But I also think the solution you posted most recently seems reasonable and good, if that achieves what you are trying to do.

I thought it was interesting how you are limiting the nav to certain templates. I usually use the hidden status on a page to direct whether it should appear in that type of navigation or not. But what you are doing gives a different type of control by delegating it to the output generation, and the template, rather than the page. Nothing wrong with that, I just hadn't tried that myself yet.

One minor thing Ryan, the $page var doesn't work inside the scope of the function.


Oops I meant for that to be wire('page'), though passing it in the function as an argument like you did is just as good.

#7 BDH

BDH

    Newbie

  • Members
  • Pip
  • 3 posts
  • 1

Posted 21 October 2011 - 10:39 AM

I was looking for a navigation for something similar, to only display the child pages when their parent is active.

<ul>
<li>Team</li>
<li>Offices</li>
<ul>
  <li>London office</li>
  <li>New York office</li>
  <li>Berlin office<li>
  </ul>

<li>History</li>
  <ul>
  <li>Photographs</li>
  <li>Archive</li>
</ul>

<li>Sales</li>
</ul>

I could get the navigation to output all child pages, but I only wanted the active parent's child pages to be shown.

Hope that makes sense!

#8 slkwrm

slkwrm

    Sr. Member

  • Members
  • PipPipPipPip
  • 317 posts
  • 184

  • LocationTallinn, Estonia

Posted 21 October 2011 - 11:30 AM

If it's still relevant. I took Apeisa's code from the above-mentioned topic and modified it a bit and it seems to work the way you wanded, Soma  :)

<?php

function treeMenu(Page $page = null, $depth = 1, $id = null) {

            $depth -= 1;
            
            if(is_null($page)) $page = wire('page');
            if(!is_null($id)) $id = " id='$id'";

            $out = "\n<ul$id>";

            $parents = $page->parents;

            // This is where we get pages we want. You could just say template!=news-item 
            foreach($page->children() as $child) {
                    $class = "level-" . count($child->parents);
                    $s = '';
                    if($child->numChildren && $depth > 0 ) {
                            $s = str_replace("\n", "\n\t\t", treeMenu($child, $depth));
                    }

                    $class .= " page-{$child->id}";
                    $class = " class='$class'";
                    $out .= "\n\t<li$class>\n\t\t<a$class href='{$child->url}'>{$child->title}</a>$s\n\t</li>";
            }
        $out .= "\n</ul>";
  
        return $out;
}

//parameters: current page, menu depth, ul menu id
$menu = treeMenu($page, 3, "myMenu");

echo $menu;

You shoud pass depth as a second parameter. By default it will output only one level.

Edit: optimized code a bit

#9 BDH

BDH

    Newbie

  • Members
  • Pip
  • 3 posts
  • 1

Posted 21 October 2011 - 11:59 AM

That is almost doing what I'm looking for, but it should only display the child page when the parent is active. Is that possible?  :)

#10 slkwrm

slkwrm

    Sr. Member

  • Members
  • PipPipPipPip
  • 317 posts
  • 184

  • LocationTallinn, Estonia

Posted 21 October 2011 - 12:00 PM

I was looking for a navigation for something similar, to only display the child pages when their parent is active.

Hi, BDH and welcome to forums.
What do you mean by "active"? Could you eleborate?

#11 Soma

Soma

    Hero Member

  • Moderators
  • 5,041 posts
  • 3794

  • LocationSH, Switzerland

Posted 21 October 2011 - 12:35 PM

Thanks a lot slkwrm, very nice of you, but that's not what I wanted. :) Look at my optimized code it does perfectly what I need. I just output when on a certain level, so it always needs 2 checks for parent to show children level and on next level to output its siblings. But I also was thinking of more complicated scenario with having 2-3 sublevels more with that technik and maybe also have a further "invisible" level but still highlighting the parents... I now how it can be done, it just gets little more complicated and thought if there's another way I dont know.

@somartist | modules created | support me, flattr my work flattr.com


#12 Soma

Soma

    Hero Member

  • Moderators
  • 5,041 posts
  • 3794

  • LocationSH, Switzerland

Posted 21 October 2011 - 12:48 PM

That is almost doing what I'm looking for, but it should only display the child page when the parent is active. Is that possible?  :)


sorry for ignoring you kinda :)

I slightly modified a code I'm using, it should do what you need, but you may need to modify to your needs. Key is to check for $page->parents->has($child) and $child === $page->parent and the like to add a style="display:block" to the sub ul.


<?php

$p = $pages->get("/");

$out = '';

foreach($p->children as $child) {
	
	// check if this child is in parents or direct parent of current page
	if($page->parents->has($child) || $child === $page || $child === $page->parent) $class = "on"; else $class = '';

	$out .= "\n<li><a class='$class' href='{$child->url}'>{$child->title}</a>";
	
	if($child->numChildren > 0){
		
		// check if this child is in parents or direct parent of current page
		if($page->parents->has($child) || $child === $page || $child === $page->parent) $status = " style='display:block'"; else $status = '';	

		$out .= "\n\t<ul$status>";
		
		foreach($child->children as $childchild) {
			
			if($childchild === $page || $childchild === $page->parent) $class = "on"; else $class = '';
			
			$out .= "\n\t\t<li><a class='$class' href='{$childchild->url}'>{$childchild->title}</a></li>";
		
		}
		$out .= "\n\t</ul>";
	}
	$out .= "\n</li>";
}

echo $out;

@somartist | modules created | support me, flattr my work flattr.com


#13 BDH

BDH

    Newbie

  • Members
  • Pip
  • 3 posts
  • 1

Posted 22 October 2011 - 04:05 AM

sorry for ignoring you kinda :)


Not at all, appreciate your help.  :)

I think I've found what I'm looking for:

<?php 

function treeMenu(Page $page = null, Page $rootPage = null) {

        if(is_null($page)) $page = wire('page');
        if(is_null($rootPage)) $rootPage = wire('pages')->get('/');

        $out = "\n<ul>";

        $parents = $page->parents;

        foreach($rootPage->children as $child) {
                $class = "level-" . count($child->parents);
                $s = '';

                if($child->numChildren && $parents->has($child)) {
                        $class .= " on_parent";
                        $s = str_replace("\n", "\n\t\t", treeMenu($page, $child));

                } else if($child === $page) {
                        $class .= " on_page";
                        if($page->numChildren) $s = str_replace("\n", "\n\t\t", treeMenu($page, $page));
                }

                $class = " class='$class'";
                $out .= "\n\t<li>\n\t\t<a$class href='{$child->url}'>{$child->title}</a>$s\n\t</li>";
        }

        $out .= "\n</ul>";
        return $out;
}
				
echo treeMenu($page, $page->rootParent);


#14 Soma

Soma

    Hero Member

  • Moderators
  • 5,041 posts
  • 3794

  • LocationSH, Switzerland

Posted 22 October 2011 - 08:00 AM

Yeah that's pretty much it BDH. Nice one, that will do it even recursively. My example is only for a 2level approach and what I'm doing is to hide the sublevel using css and put a display:block only if active.  But it was more to show the conditions....

@somartist | modules created | support me, flattr my work flattr.com


#15 martinluff

martinluff

    Full Member

  • Members
  • PipPipPip
  • 79 posts
  • 2

  • LocationChristchurch NZ

Posted 14 November 2011 - 06:47 AM

<?php

function treeMenu(Page $page = null, $depth = 1, $id = null) {

            $depth -= 1;
            
            if(is_null($page)) $page = wire('page');
            if(!is_null($id)) $id = " id='$id'";

            $out = "\n<ul$id>";

            $parents = $page->parents;

            // This is where we get pages we want. You could just say template!=news-item 
            foreach($page->children() as $child) {
                    $class = "level-" . count($child->parents);
                    $s = '';
                    if($child->numChildren && $depth > 0 ) {
                            $s = str_replace("\n", "\n\t\t", treeMenu($child, $depth));
                    }

                    $class .= " page-{$child->id}";
                    $class = " class='$class'";
                    $out .= "\n\t<li$class>\n\t\t<a$class href='{$child->url}'>{$child->title}</a>$s\n\t</li>";
            }
        $out .= "\n</ul>";
  
        return $out;
}

//parameters: current page, menu depth, ul menu id
$menu = treeMenu($page, 3, "myMenu");

echo $menu;


Hey slkwrm, this code does just what I'm looking for; to recursively create a sub-nav for any section of my site listing all the items in that section as a nested list (works like a dream)  EXCEPT I'd really like to add 'page-active' and ideally 'parent-active' classes to the current page in the navigation and, if appropriate to it's parents so I can style them differently to show where the user is in the tree. Hope that makes sense, if someone could point me in the right direction then that would make my day (have just spend last couple of hours trying to append these classes without success)  :)

#16 Pete

Pete

    Forum Admin

  • Administrators
  • 2,375 posts
  • 1423

  • LocationChester, England

Posted 14 November 2011 - 07:42 AM

Marking the whole path back up the tree as active is something I looked at before but it hurt my head too much so I didn't do it in the end. I think what you need to do is start with the current page and grab all the page id's going back up the tree into an array, then use that to highlight the relevant pages in the menu as it's outputted.

It sounds easy when you say it ;)

#17 ryan

ryan

    Reiska

  • Administrators
  • 7,783 posts
  • 6527

  • LocationAtlanta, GA

Posted 14 November 2011 - 09:10 AM

Martin, I haven't yet had my morning coffee so forgive me if I'm missing something. :) Didn't we have that on the earlier treeMenu variations? http://processwire.c...90.html#msg790  (and the one in BDH's most recent post)

Also, back before treeMenu, I sometimes used a little jQuery for this. Once you know the current page in the list you can do something kind of like this:

$("#subnav li.on_page").parents("li").addClass("on_parent");

Though the jQuery is more of a quick hack, whereas It's probably better to do it in the actual output via something like treeMenu().

#18 Pete

Pete

    Forum Admin

  • Administrators
  • 2,375 posts
  • 1423

  • LocationChester, England

Posted 14 November 2011 - 09:14 AM

Yet another awesome code snippet in that link ryan. I'm noting all these links down ;)

#19 martinluff

martinluff

    Full Member

  • Members
  • PipPipPip
  • 79 posts
  • 2

  • LocationChristchurch NZ

Posted 14 November 2011 - 01:54 PM

Ryan, yes, I'd seen that and that code does what I want, but my PHP isn't very good and I couldn't seem to merge that code with silkwrm's code. Well I tried and at least it didn't break but it also didn't seem to be adding the required class...  :(

Pete, good point, when I was trying to bottom this out yesterday I came across nine or ten snippets related to outputting navigation; I wonder if Ryan or another kind coder could put all these snippets together as a navigation FAQ.

#20 apeisa

apeisa

    Hero Member

  • Moderators
  • 3,151 posts
  • 1697

  • LocationVihti, Finland

Posted 14 November 2011 - 02:16 PM

I am actually building simple "navigator" module which should cover most of basic navigation needs. I hope to get it released sometime :)




0 user(s) are reading this topic

0 members, 0 guests, 0 anonymous users