Jump to content

Submenu from sidebar to main menu


kazu
 Share

Recommended Posts

I'm just working on my first ProcessWire project. Unfortunately I am not so talented in the PHP programming, so I have a question about the menu code.

In my template it’s just included so:
 

<nav class="row main-navigation">
<ul class='topnav'>
    <?php
        // top navigation consists of homepage and its visible children
        foreach($homepage->and($homepage->children) as $item) {
            if($item->id == $page->rootParent->id) {
                echo "<li class='current'>";
            } else {
                echo "<li>";
            }
            echo "<a href='$item->url'>$item->title</a></li>";
        }

        // output an "Edit" link if this page happens to be editable by the current user
        if($page->editable()) echo "<li class='edit'><a href='$page->editUrl'>" . __('Edit') . "</a></li>";
    ?>
</ul>
</nav>

The main menu is displayed, the submenu does not work:
 

<ul class="topnav">
<li class="current">
<a href="/about/">About</a>
</li>

There are two child sites, and I would like that it is appears like that:
 

<li class="parent">submenu
    <ul>
        <li>
        <a href="/about/child/">submenu item 1</a>
        </li>
    <li>
    </ul>
</li>

Currently, the submenu will be displayed in the Sidebar. How do I get it in the main menu?

Link to comment
Share on other sites

Hello kazu and welcome to the forum!

 
If you'd like to output submenu for the current page, change the code like so:
// iterate children of a current page object
foreach($page->children as $item) as $item) {
   if($item->id == $page->id) {
       echo "<li class='current'>";
   } else {
       echo "<li>";
   }
   echo "<a href='$item->url'>$item->title</a></li>";
}

And here's how to put it to the main menu:

foreach($homepage->and($homepage->children) as $item) {

    // class name for item wrapper
    $class = $item->id == $page->rootParent->id ? 'current ' : '';
    if($item->hasChildren()) $class .= 'parent ';

    // open item wrapper
    if(!empty($class)) {
       echo "<li class='". rtrim($class) ."'>";
    } else {
       echo "<li>";
    }

    // item link element
    echo "<a href='$item->url'>$item->title</a>";

    // markup for possible childs
    if($item->hasChildren()) {
        echo "<ul>";
        foreach($item->children as $child) {
            echo "<li><a href='$child->url'>$child->title</a></li>";
        }
        echo "</ul>";
    }

    // close item wrapper
    echo "</li>";
}

If you like to have 'current' class for submenu items also, change the child markup of foreach loop to match with the first example.

  • Like 1
Link to comment
Share on other sites

That does work fine, thanks Roope!

I'm trying now to integrate the current status into the childs menu. I have not figured out why the menu knows which page is currently active? I think that probably one if or else is necessary, so that not all pages get the current status? For me a good deal to learn. Once again, thanks for your help!

Link to comment
Share on other sites

Yes, while we loop the childs, we can check if the current $child id matches with the viewed $page id.

// markup for possible childs
if($item->hasChildren()) {
  echo "<ul>";
  foreach($item->children as $child) {
     if($child->id == $page->id) {
        echo "<li class='current'>";
     } else {
        echo "<li>";
     }
      echo "<a href='$child->url'>$child->title</a></li>";
  }
  echo "</ul>";
}

$page is one of the predefined variables in processwire template files and it represents the current viewed page object.

https://processwire.com/api/variables/

 
Logic is same when we set the class name for first the level item, except that instead of if else we use PHP Ternary Operator and check the $item id against $page->rootParent which represents the parent page closest to the homepage.
// class name for item wrapper
$class = $item->id == $page->rootParent->id ? 'current ' : '';

This way 'current' class name is added also when child of the item page is being viewed.

<ul>
  <li class='current'>
    <a href='/item/'>Item</a>
    <ul>
      <li class='current'>
        <a href='/child/'>Child</a>
      </li>
    </ul>
  </li>
</ul>

If you don't like this behaviur and just want to actual current viewed page wrapper have this class name you can simply change the condition from $page->rootParent to $page:

// class name for item wrapper
$class = $item->id == $page->id ? 'current ' : '';
Link to comment
Share on other sites

While Roope's solution works, you will only get the direct children in your nav. You could unify this with a recursive function. Modifying Roope's code like this should work:

function buildNav($page) {
	echo "<ul>";
	foreach($page->and($page->children) as $item) {
	
	    // class name for item wrapper
	    $class = $item->id == $page->rootParent->id ? 'current ' : '';
	    if($item->hasChildren()) $class .= 'parent ';
	
	    // open item wrapper
	    if(!empty($class)) {
	       echo "<li class='". rtrim($class) ."'>";
	    } else {
	       echo "<li>";
	    }
	
	    // item link element
	    echo "<a href='$item->url'>$item->title</a>";
	
	    // markup for possible childs
	    if($item->hasChildren()) {	        
	        buildNav($item);
	    }
	
	    // close item wrapper
	    echo "</li>";
	}
	echo "</ul>";
}

I have not tested the code, but it should work. It could be, that you have to tinker with the markup a little bit.

Edit: Whoops, wrong variable name.

Edited by EvanFreyer
Link to comment
Share on other sites

@Roope

I have inserted your code, it works fine. Thanks to the links I was able to add also the title :-)

<ul class='topnav'>
    <?php
        // top navigation consists of homepage and its visible children
        foreach($homepage->and($homepage->children) as $item) {

    // class name for item wrapper
    $class = $item->id == $page->rootParent->id ? 'current ' : '';
    if($item->hasChildren()) $class .= 'parent ';

    // open item wrapper
    if(!empty($class)) {
       echo "<li class='". rtrim($class) ."'>";
    } else {
       echo "<li>";
    }

    // item link element
    echo "<a title='{$page->parent->title}' href='$item->url'>$item->title</a>";

    // markup for possible childs
    if($item->hasChildren()) {
      echo "<ul>";
      foreach($item->children as $child) {
         if($child->id == $page->id) {
            echo "<li class='current'>";
         } else {
            echo "<li>";
         }
          echo "<a title='{$child->title}' href='$child->url'>$child->title</a></li>";
      }
      echo "</ul>";
    }

    // close item wrapper
    echo "</li>";
}
    ?>
</ul>

@EvanFreyer

In my site it does not work, thanks anyway.

Link to comment
Share on other sites

If you need recursive version of this, I think you need to modify code from @EvanFreyer a bit:

<?php 

function buildNav(PageArray $pages) {
    $str = '';
    foreach($pages as $item) {

        // class name for item wrapper
        // NOTE: since inside function we need to use wire() to get the current viewed page
        $class = $item->id == wire('page')->rootParent->id ? 'current ' : '';
        if($item->hasChildren() && $item->id != 1) $class .= 'parent ';

        // open item wrapper
        if(!empty($class)) {
           $str .= "<li class='". rtrim($class) ."'>";
        } else {
           $str .= "<li>";
        }

        // item link element
        $str .= "<a href='$item->url' title='$item->title'>$item->title</a>";

        // iterate possible childs
        // NOTE: except for homepage, since those are already included in the array 
        // we pass to the function as first level pages (same logic added to class 'parent' name)
        if($item->hasChildren() && $item->id != 1) {
            $str .= buildNav($item->children);
        }

        // close item wrapper
        $str .= "</li>";
    }

    // return ul list
    return "<ul>$str</ul>";
}

?>

<nav class='topnav'>
    <?php echo buildNav($homepage->and($homepage->children)); ?>
</nav>
Link to comment
Share on other sites

@Roope: Yeah, that one looks better, thanks for the update! :)

@kazu: Recursive means, that a piece of code calls itself, so that it can manage the same task again and again. Regarding the navigation, this pattern ensures, that you can print the navigation for the third, fourth, n-th level as well without modifying your existing code to do that explicitly. See, if we take your code, you will only get the navigation to the second level. If somehow you want to build the third level as well, you have to add that to your code and if you want to drop the list elements and go for example for just the text and a break, you have to change your code in three places. This won't be necessary with the recursive version.

If you look closely at Roope's code, you see, that it calls itself here:

        // iterate possible childs
        // NOTE: except for homepage, since those are already included in the array 
        // we pass to the function as first level pages (same logic added to class 'parent' name)
        if($item->hasChildren() && $item->id != 1) {
            $str .= buildNav($item->children);
        }

Here, we see, that our navigation has child elements and so we call the same function again, but with the children as parameter. If the function then finds children again, the whole thing happens again and so forth. Of course it is important to ensure, that this function terminates. Otherwise, this will be a infinite loop and not a recursive function :).

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