Creating a main menu and sub-menu for your website

Posted 25th Nov 2017

#api #navigation

Creating a main menu is a simple task thanks to the processwire API. We'll look into how to create a typical main site menu.

Let's say you've got a structure with a few top level pages under 'Home' that you want to be in your main menu.

The page tree showing the site structure
The page tree showing the site structure Zoom

And your final output needs to look something like this (pulled straight from the Bootstrap 4 'nav' component).

<ul class="nav">
  <li class="nav-item">
    <a class="nav-link" href="#">Active</a>
  </li>
  <li class="nav-item">
    <a class="nav-link" href="#">Link</a>
  </li>
  <li class="nav-item">
    <a class="nav-link" href="#">Link</a>
  </li>
  <li class="nav-item">
    <a class="nav-link" href="#">Link</a>
  </li>
</ul>

You can go about this in a number of ways. First of all, you'll need to find/get a PageArray of the items that will be in the menu. An easy way to do this is to get the homepage, and the children of the homepage.

<?php
  // get the homepage object
  $homePage = $pages->get("/");
  // get PageArray of homepage object and child page objects
  $navItems = $homePage->and($homePage->children);
?>

Now you have a PageArray with the required items, you can iterate over them one at a time, and print the title of the page and the link to the page itself and wrap the whole thing in your list tag.

<ul class="nav">

<?php
  // get the homepage object
  $homePage = $pages->get("/");
  // get PageArray of homepage object and child page objects
  $navItems = $homePage->and($homePage->children);
  // or if you don't want the home link
  // $navItems = $homePage->children;
  // iterate over the $navItems PageArray
  foreach ($navItems as $navItem):
?>

  <li class="nav-item">
    <a class="nav-link" href="<?php echo $navItem->url; ?>"><?php echo $navItem->title; ?></a>
  </li>

  <?php endforeach; ?>

</ul>

And that's it! You may be asking why the other items in the tree didn't print, that's because the other pages are 'hidden'.

Hidden pages in the page tree
Hidden pages in the page tree Zoom

See how you can 'unhide' that page above? Hidden pages by default will not be returned in the PageArray.

Now, you would probably want some sort of 'active' css class on the menu item that can be styled to highlight the current page. This can be added in like so.

<ul class="nav">

<?php
  // get the homepage object
  $homePage = $pages->get("/");
  // get PageArray of homepage object and child page objects
  $navItems = $homePage->and($homePage->children);
  // or if you don't want the home link
  // $navItems = $homePage->children;
  // iterate over the $navItems PageArray
  foreach ($navItems as $navItem):

    // if the id of the page currently being
    // iterated equals the id of the current page
    if ($navItem->id == $page->id) {
      $activeClass = " active";
    }
    else {
      $activeClass = "";
    }
?>

  <li class="nav-item">
    <a class="nav-link<?php echo $activeClass; ?>" href="<?php echo $navItem->url; ?>"><?php echo $navItem->title; ?></a>
  </li>

  <?php endforeach; ?>

</ul>

A useful thing to use here is the ternary operator. Instead of an if/else, you can shorten that block into one (still readable) line.

// if the id of the page currently being
// iterated equals the id of the current page
$activeClass = ($navItem->id == $page->id) ? " active" : "";

Ok, so that's great! What about if I wanted the children of 'Services'. Maybe it's a submenu of something that will show in a sidebar when on that page. It's the same principle, what we've been doing is getting the parent page, and just printing off the children. So, let's do it.

Child pages of services page
Child pages of services page Zoom

In my case above I know a few things in order to find the correct page:

  1. Services has an id of 1027 (found by hovering over 'Edit', the link ends with '?id=1027').
  2. Services has a system path of '/services/'.

So we already have more than enough ways of getting this page.

// 1) By id - returns a single Page or NullPage
$servicesPage = $pages->get(1027);

// 2) By path - returns a single Page or NullPage
$servicesPage = $pages->get("/services/");

Now it's just a case of getting the chldren of this page like above.

NOTE: An id and a path are unique in processwire so they are guaranteed to correspond to a single page.

<ul class="sub-menu">

<?php
  // get the Services page object
  $servicesPage = $pages->get("/services/");

  // get PageArray of Services page child objects
  $navItems = $servicesPage->children;

  // iterate over the $navItems PageArray
  foreach ($navItems as $navItem):

  // if the id of the page currently being
  // iterated equals the id of the current page
  $activeClass = ($navItem->id == $page->id) ? " active" : "";
?>

  <li class="nav-item">
    <a class="nav-link<?php echo $activeClass; ?>" href="<?php echo $navItem->url; ?>"><?php echo $navItem->title; ?></a>
  </li>

  <?php endforeach; ?>

</ul>

So now, when you're on the 'Services' page, you could stick this menu in a sidebar and the child pages wll be printed out. The link to '/services/' will not be in this menu as the PageArray used was only the children of 'Services'.

But this is processwire, this isn't the only way, now we're getting deeper into relationships! Once you understand how to traverse the page tree, you'll be amazed how easy it is to get any pages you want. What if, when you were on the 'Services' page, instead of finding/getting the 'Services' page object directly, you could get the children of the parent of the current page.

This makes the menu more re-usable on other pages that have a parent where you might want to print out a child menu.

<ul class="sub-menu">

<?php
  // get children of the parent of the current page
  $childPages = $page->parent->children;

  // iterate over the $navItems PageArray
  foreach ($childPages as $childPage):

  // if the id of the page currently being
  // iterated equals the id of the current page
  $activeClass = ($navItem->id == $page->id) ? " active" : "";
?>

  <li class="nav-item">
    <a class="nav-link<?php echo $activeClass; ?>" href="<?php echo $childPage->url; ?>"><?php echo $childPage->title; ?></a>
  </li>

  <?php endforeach; ?>

</ul>

So maybe I'm going a little off the track here but it just shows that there are many ways to achieve what you want. It's hard to describe every scenario but try some of this stuff out and you'll be doing awesome things in no time!

Feedback & support

I hope you enjoyed this tutorial. You can support pwtuts by following on @pwtuts.

Related tutorials / See all