Source

c5_navigation_builder /

Filename Size Date modified Message
blocks/navigation_builder_block
elements/navigation_builder
models
5.0 KB
794 B

Navigation Builder

NavigationBuilder is a class that builds a navigation tree from a Concrete 5 page structure. Once built the structure can be manipulated (nodes added, moved or removed) as required. Once complete a NavRenderer can be used to render the structure to HTML.

This code is lightly unit tested. Tests exist for the TreeNode code that provide must of the tree creation, iteration and manipulation code.

TODOs:

  1. talk about $navRoot.flatten() for creating autonav compatible structures.
  2. should update flatten to take both a Closure and Function.
  3. investigate using php templates (and partials) for the renderer bits.
  4. this kind of thing really need to use namespaces...
  5. Should probably rename files to NavigationBuilder.class.php etc.
  6. Should probably create issues for my todos...

Example of a typical site nav

Loader::model('NavigationBuilder', 'navigation_builder');
$navBuilder = new NavigationBuilder();
$navBuilder->setMaxDepth(2);

// build a two level menu structure based on the home page which includes the
// home page as the first item in the list.
$root = $navBuilder->buildWithRootAsFirstItem(Page::getByID(HOME_CID));

// now render a menu with class 'nav'
$renderer = new NavRenderer('nav');
echo $renderer->render($root);

Example of using custom formats

Loader::model('NavigationBuilder', 'navigation_builder');
$navBuilder = new NavigationBuilder();
$navBuilder->setMaxDepth(2);

// build a two level menu structure based on the home page...
$root = $navBuilder->buildWithRootAsFirstItem(Page::getByID(HOME_CID));

// now render a menu with class 'nav' and where the root node (i.e. the HomePage) is the
// first item in the menu.
$renderer = new NavRenderer('nav');

// lets do some sliding doors
$renderer->setTextFormat(function ($navItem) {
    return "<span>{$navItem->getName()}</span>";
});

// configure class names for the LI and A tags in the menu
$renderer->setNavClassFormat(function ($navItem) {
    $classNames = array();
    if ($navItem->isFirstChild()) { $classNames[]='my-special-nav-first'; };
    if ($navItem->isLastChild()) { $classNames[]='my-special-nav-last'; };
    if ($navItem->isCurrent()) { $classNames[]='my-special-nav-selected'; };
    if ($navItem->isInPath()) { $classNames[]='my-special-nav-path-selected'; };
    $classNames[]= $navItem->pageAttribute('nav_class_name');
    return array_merge($classNames, $navItem->getClassNames());
});

// configure class names for the sub menu UL element
$renderer->setSubMenuClassNameFormat(function ($navItem) {
   return array('my-special-sub-menu', 'my-special-level-'.$navItem->depth());
});

echo $renderer->render($root);

Example of creating a Faux Home menu

In this example we're taking a Concrete5 page structure of:

  • Welcome (our home page is called "Welcome")
    • More Info
    • About
    • Services
      • Service One
      • Service Two
    • Contact

And generating a two level navigation structure where landing on the home page the navigation reflects that you're at 'Home/Welcome'.

  • Home (this is our faux home)
    • Welcome (this is the actual home page (e.g. http://mysite.com/) and will be selected when you arrive)
    • More Info (a page with the faux home attribute set to true)
    • About (ditto)
  • Services
    • Service One
    • Service Two
  • Contact

And here's the code:

$navBuilder = new NavigationBuilder();
$navBuilder->setMaxDepth(2);

$homePage = Page::getByID(HOME_CID);

$root = $navBuilder->build($homePage);

// now find any "faux home" children of the root node.
$fauxHomeChildren = $root->filterImmediateChildren(function($child) {
    /** @var $child NavItem */
    return $child->pageAttribute('faux_home') == true;
});

// now create a faux home menu that we can add the children.
$fauxHome = new NavItem($homePage, "Home"); // needs to reference the home page so the renderer has a url for it.
$fauxHome->add(new NavItem($homePage)); // add the actual home page with the name "Welcome"...
$fauxHome->addAll($fauxHomeChildren); // and all the faux children

// now we rejig the structure by adding the faux home and all the remaining root node children
// to the newRoot item.  This is bit wrong since the root doesn't get rendered by default..
$newRoot = new NavRoot();
$newRoot->add($fauxHome);
$newRoot->addAll($root->children());

// Now rejig the current path (breadcrumbs)..
$activeItem = $newRoot->findNodeOrClosestAncestorOf(Page::getCurrentPage());

if (is_null($activeItem)) {
    // the current page isn't in the nav so we just select the top level
    // "Home" tab.
    $fauxHome->_setCurrent(true);
} else {
    // ask the root node to reconfigure the active path to be the current item.
    $newRoot->configureCurrentPathUsing($activeItem);
}