Jump to content

Custom Menu (not related to page tree)


Macrura
 Share

Recommended Posts

Recently I've been working on a site where it seemed like it was going to be impossible to keep the page tree and the needs of the site's main menu in harmony;

So to solve this I setup a separate page tree under a hidden page called 'Main Menu', with each menu item having three fields: title, menu-link-page, and menu-link-url. All of the menu items are also hidden, so the code below uses the include=all parameter when getting the pages for the tree.

To generate the main menu markup, i adapted the great code that was developed by Soma for Joss's bootstrap menu, and modified it to output links to the menu-link-page or menu-link-url; (Entering a value in the menu-link-url field overrides any selection in the menu-link-page.)

This solution has enabled me to setup the menu exactly how the site needs it, even if the menu heirarchy is not the same as the page heirarchy, and has solved a lot of problems and made things easier for this scenario. For example, menu items can easily contain external URLs, to subdomain pages, or other related site's pages. (In this case the client has a separate web store for selling parts). Also, all of the parent menu items had to use a javascript:void() as the href, in order for the accordion version to work right on mobile; So this was easy to do by putting that into the menu-item-link field.

In the code below, page #1271 contains the menu tree, so it is specified as a parameter to the $root variable.

I think this sort of setup, using a custom menu tree, could solve a lot of the questions I've seen recently on the forum; I probably wouldn't use this technique on small sites since it is more work, but for larger ones where you might need a lot of menus, this could be helpful.  Also, if you had to setup a Mega Menu, with images and icons, this might make it easier to manage.

<?php

/**
 *  render custom markup menu for bootstrap nested navigation
 *
 * @param  PageArray  $pa     pages of the top level items
 * @param  Page  $root   root page optional, if you use other root than home page (id:1)
 * @param  string  $output for returned string collection
 * @param  integer $level  internally used to count levels
 * @return string          menu html string
 */
function renderChildrenOf($pa, $root = null, $output = '', $level = 0) {
    if(!$root) $root = wire("pages")->get(1);
    $output = '';
    $level++;
    foreach($pa as $child) {
        $class = '';
        $has_children = count($child->children('include=all')) ? true : false;

        if($has_children && $child !== $root) {
            if($level == 1){
                $class .= 'parent'; // first level boostrap dropdown li class
                //$atoggle .= ' class="dropdown-toggle" data-toggle="dropdown"'; // first level anchor attributes
            }
        }

        // make the current page and only its first level parent have an active class
        if($child->menu_item_page === wire("page")){
            $class .= ' active';
        } else if($level == 1 && $child !== $root){
            if($child->menu_item_page === wire("page")->rootParent || wire("page")->parents->has($child)){
                $class .= ' active';
            }
        }

        $class = strlen($class) ? " class='".trim($class)."'" : '';
        if($child->menu_item_url) {$childlink = $child->menu_item_url; } else { $childlink = $child->menu_item_page->url; }
        $output .= "<li$class><a href='$childlink'>$child->title</a>";

        // If this child is itself a parent and not the root page, then render its children in their own menu too...
        if($has_children && $child !== $root) {
            $output .= renderChildrenOf($child->children('include=all'), $root, $output, $level);
        }
        $output .= '</li>';
    }
    $outerclass = ($level == 1) ? "accordmobile" : '';
    return "<ul class='$outerclass'>$output</ul>";
}

// bundle up the first level pages and prepend the root home page
// $root = $pages->get(1);
// $pa = $root->children;
// $pa = $pa->prepend($root);

// Set the ball rolling...
// echo renderChildrenOf($pa);

// example with pages further down in the tree

$root = $pages->get("1271");
$pa = $root->children('include=all');
// $pa = $pa->prepend($root);

// add the root as the second parameter
echo renderChildrenOf($pa,$root);
 
  • Like 3
Link to comment
Share on other sites

I think it's a fine way to go when there is no way to make the site structure consistent with the navigation, for certain parts (like top navigation or footer nav, etc.) Still I think it's always preferable to keep a consistent site and navigation structure whenever possible. A layer of pages separate from the structure, just for menus, is [in most cases] and unnecessary bit of complexity. But for those times when you will benefit from it, it's a nice pattern to have. 

Link to comment
Share on other sites

Hey Ryan - thanks for replying to this.

RIght... i think in most cases using the PW tree to generate the menu is best; But it does speak to the flexibility of PW that you can do something like this with relative ease when required...and in setting this up I learned from Soma's bootstrap menu markup how to generate complex nav markup without using a plugin..

I have heard it said somewhere that the navigation menu should be optimized for the user and not be constrained by the hidden structure of the site, especially for larger sites. In this case, it is a company where many of the products overlap in functionality, so there needs to be several routes for different users to arrive at certain pages.  And they are not extremely organized yet about the content, so i'm sort of dumping all of the content into the tree in what seems like a logical structure, and then linking to things as necessary, with the custom menu.  It's also helping to prevent confusion in some cases for links to external sites, where those would have been showing in the page tree if we did it that way.

This site will also have a lot of advanced search and filter types of things, so the nav is kind of secondary to that route for finding pages...

-marc
 

  • Like 2
Link to comment
Share on other sites

I have heard it said somewhere that the navigation menu should be optimized for the user and not be constrained by the hidden structure of the site, especially for larger sites.

True, if you don't have control over the structure. In my experience, what's good for the user, and good for the structure, are closely tied together. And it tends to hold true regardless of scale. So I try to base my structure around what's good for the user, and that usually ends up being ideal for everything else. I'm sure there are exceptions though.

In this case, it is a company where many of the products overlap in functionality, so there needs to be several routes for different users to arrive at certain pages.  And they are not extremely organized yet about the content, so i'm sort of dumping all of the content into the tree in what seems like a logical structure, and then linking to things as necessary, with the custom menu. 

I think this is okay so long as you don't run into a situation where the same rendered page is being delivered at multiple URLs. 

If you are dealing with lots of products with multiple classifications, a good approach is to throw them all in a bucket (i.e. /products/) and then relate with categories via page relations. In this manner, you aren't maintaining a separate menu, but building upon existing page relations. 

It's also helping to prevent confusion in some cases for links to external sites, where those would have been showing in the page tree if we did it that way.

I usually have a template called 'redirect' that has nothing but a title and URL field. It's good for handling external links, or even occasional internal links as navigation placeholders. Like you mentioned, this puts it in your page tree. Usually that's what I want, but I tend to only have the need for the 'redirect' template occasionally.

This site will also have a lot of advanced search and filter types of things, so the nav is kind of secondary to that route for finding pages...

With the unknowns in terms of structure that you are dealing with, I think the approach you are taking with separate menu makes sense. It probably also makes sense in this case to go with a more bucket-oriented structure for the content (like the /products/ you mentioned). 

  • Like 2
Link to comment
Share on other sites

Thanks Ryan -

I think i will use the redirect template idea for some things..Right now there are no multiple URLs, everything is well organized in the page tree, diescrreet

now you're giving me some ideas about how to improve this – right now the products are arranged heirarchially under one parent, and then 4 'categories' with all of the products under each of those; But now i'm thinking that ALL of the products should have the same parent, i think this will make it more flexible to show search results.. then i guess i'll use URL segments for the product category, application, name etc...if i do it this way, there will be multiple URLs to the same page, but i could then use canonical URLs;

  • Like 1
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

×
×
  • Create New...