Jump to content

Page level and sub-navigation


Soma
 Share

Recommended Posts

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>";
}

Link to comment
Share on other sites

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); 
Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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);

Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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!

Link to comment
Share on other sites

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

  • Like 1
Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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;
Link to comment
Share on other sites

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);
  • Like 2
  • Thanks 1
Link to comment
Share on other sites

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....

Link to comment
Share on other sites

  • 4 weeks later...

<?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)  :)

Link to comment
Share on other sites

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 ;)

Link to comment
Share on other sites

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.com/talk/index.php/topic,128.msg790.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().

Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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

Cool; I suspect it'll be a very well used module  ;)

I'm trying to improve my PHP coding but unfortunately not a fast learner... The Zend PHP 101 series, although from a while back, seems the best online introduction I've found so far http://devzone.zend.com/6/php-101-php-for-the-absolute-beginner/

Link to comment
Share on other sites

This goes little off topic:

IMO good way to learn is to be brave and publish stuff that you have done for feedback. This forum is one fine place to do it (and I remember you have done also) - it's great to get feedback and is many times beneficial for others too! I don't know how to thank Ryan and others enough for all the learning I have got from here.

I have also found that there are some concepts that are very common in web development (php & js for me) and understanding those will speed up your learning (not saying that I have grasped these all, still learning basic stuff also..):

  • arrays, multidimensional
  • looping those arrays
  • functions (especially those that return something)
  • classes and objects
  • Web dev basics: server side vs. client side? sessions, cookies, databases. http-basics: post, get, headers...

Also there are super simple things that we waste a lot of time. Things like string manipulation or simple maths. If you have problems like that and google doesn't help you right away, then just ask for help - it will help you learn faster. Also - learn to love php.net - ugly but working docs there!

There will be boring and depressing phases when you learn slower and even feel going backwards (you realize how much you don't know and it feels bad :)). And then there are those awesome moments when you feel like everything is possible and you just want to build and learn more. Most important thing is to keep building stuff. There are those wizard brain lies that keep telling people that they don't know and they will never know something - that they are just incompetent for doing stuff like coding/painting/music etc. "You are just a designer, don't bother trying to code something yourself, it will never work" or just the opposite: "You are just a developer, don't even try to build something beautiful or even usable"... how much better software we would have without lies like that? :)

  • Like 2
Link to comment
Share on other sites

OK Antti, I think I'm in the depressing stage at this point  ;)

In response to your suggestion here's my mutilated version of slkwrm's beautiful code; in which I'm trying to append a simple 'item-active' class on the current item (adding 'parent-active' on any parent's of the current item would be a bonus):

<?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>";

            // This is where we get pages we want. You could just say template!=news-item or list the templates you do want
            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));
                        if ($child === $page) { 
                                $class .= " current"; 
                        }
                    }

                    $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->rootParent, 4, "myMenu");

So from my reading of it and looking at the linked article in Ryan's post (http://processwire.com/talk/index.php/topic,128.msg790.html#msg790) I'm guessing the argument to test if it should append the active class needs to be in the loop plus needs to work out when current page is same as $child?

Here's another attempt:

<?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>";

            // This is where we get pages we want. You could just say template!=news-item or list the templates you do want
            foreach($page->children() as $child) {
                    $class = "level-" . count($child->parents);
                    $s = '';
                    if($child->numChildren && $depth > 0 && $child->id === $page->$id) {
                        $class .= " current";
                        $s = str_replace("\n", "\n\t\t", treeMenu($child, $depth));
                    } else if ($child->numChildren && $depth > 0) {
                        $class .= "";
                        $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->rootParent, 4, "myMenu");

echo $menu;

If someone could sort out where I'm going wrong (I'm not even sure if it's my argument for detecting when page and child are the same or my placement of the arguments or if I'm just way off track) that would be much appreciated  :)

Link to comment
Share on other sites

it's as simple as checking for if the current looped $child is in parents of the current page:

 <?php

if($page->parents->has($child) ....)

// may look like this with a fully check
if($page->parents->has($child) || $child === $page || $child === $page->parent) $status = " $class=' on'"; else $class = '';

Since $page->parents returns an array has "has" does check if it's in there.. you got what you look for.

Just think about it for a second, it will be clear as water after you use it sometime.

// not tested but should work I think
<?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>";

            // This is where we get pages we want. You could just say template!=news-item or list the templates you do want
            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));
                    } 

                    if(wire("page")->parents->has($child) || wire("page") === $child || $child === wire("page")->parent){
                        $class .= " current";
                    } else {
                        $class = '';
                    }

                    $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->rootParent, 4, "myMenu");

echo $menu; 

EDIT: modified code a little... there was something double. And to check for $page, I didn't first recognize it, but in this case to check for current page in the function scope it has to be wire("page")->parents ... I testet and it works now. Current page and its parent get the class "current".

Link to comment
Share on other sites

Thanks a bundle Soma  :) I get the logic - thanks for explaining and taking the time to give a working example, need to read the code a few times to understand it in practice but examples like this help a lot. That will be a really useful bit of re-usable code for me (and probably some others). Cool...

Edit: OK, have had a bit more time with the code now... that's great. See I never would have thought it would be a simple solution like that  :) Thanks again.

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...