Jordan Lev Logo Jordan Lev's Concrete5 Tips

How to make things work when building a Concrete5 website.

Customizing the Autonav Template

| Comments

The built-in Autonav block is one of the most essential blocks in a Concrete5 site (right up there with Content and Page List). But if you’ve already designed a menu with a certain HTML and CSS structure, it can be very difficult to modify the autonav template to suit your needs because its default template is rather messy.

I don
I don’t think we’re in HTML Kansas anymore

For example, what if you want to change the name of the class that indicates the current page from the default “nav-selected” to the more succinct “active”, or “current” (something that is used frequently in other systems’ dropdown menus)? Or what if you want to add a new class denoting the first or last item in the list? Or what if you want to do something more drastic like change the unordered list structure to a series of divs and spans? I consider myself a decent programmer and even I would be afraid of messing with that code!

But after a year of struggling with the autonav template on dozens of sites, and a healthy dose of assistance from other members of the C5 community, I’ve finally gotten it all figured out and have re-constructed the template so it is cleaner and easier to customize. Behold!

Ahh.... much better
Ahh…. much better

Not only is the markup nice and clean, but this template includes a ton of additional features beyond what the built-in autonav template provides (although it doesn’t break backwards compatibility in any way – you can use this new template unmodified and your site’s markup will not be any different than it was before). Some of the additional features available to you are:

  • Ability to easily set and change class names for a variety of different circumstances (e.g. first and last items, item contains dropdown, unique class for every single item, etc.)
  • Fixes a Concrete5 bug that resulted in all child pages of an excluded page to be included in the menu (the same functionality included in the Autonav Exclude Subpages addon)
  • Recognizes a new custom attribute (exclude_subpages_from_nav) you can add to your site that lets you exclude all of a page’s children without excluding that page itself
  • Lets you set up a custom attribute (nav_item_class) for a custom class name – whatever text is entered into that attribute for a certain page will get outputted into the menu item’s list of classes

Installation

To use this custom template in your own site, grab the latest file here: https://raw.github.com/jordanlev/c5_clean_block_templates/master/autonav/view.php. Save that page to siteroot/blocks/autonav/view.php (you’ll need to create the autonav directory inside blocks if it doesn’t already exist). As always, make sure you’re not in concrete/blocks/autonav.

Out of the box, nothing will change on the front-end of your site. But you now have a nice and neat default template for the autonav block that you can easily customize. If you look at the code, you’ll see some comments at the top telling you about custom attributes that will be recognized by the template, which should be fairly self-explanatory. After that is a bunch of code that handles functionality (you don’t need to worry about that). Down towards the bottom, we get to the important part of the template. There are actually two distinct areas: one for setting up the css classes, and another for the actual html markup.

Customizing The CSS Classes

The portion of the code where you customize CSS classes looks something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<?php
foreach ($navItems as $ni) {
  $classes = array();
  
  if ($ni->is_current) {
      //class for the page currently being viewed
      $classes[] = 'nav-selected';
  }
  
  if ($ni->in_path) {
      //class for parent items of the page currently being viewed
      $classes[] = 'nav-path-selected';
  }
  
  /*
 if ($ni->is_first) {
     //class for the first item in each menu section (first top-level item, and first item of each dropdown sub-menu)
     $classes[] = 'nav-first';
 }
 */
  
  /*
 if ($ni->is_last) {
     //class for the last item in each menu section (last top-level item, and last item of each dropdown sub-menu)
     $classes[] = 'nav-last';
 }
 */
  
  /*
 if ($ni->has_submenu) {
     //class for items that have dropdown sub-menus
     $classes[] = 'nav-dropdown';
 }
 */
  
  /*
 if (!empty($ni->attribute_class)) {
     //class that can be set by end-user via the 'nav_item_class' custom page attribute
     $classes[] = $ni->attribute_class;
 }
 */
  
  /*
 if ($ni->is_home) {
     //home page
     $classes[] = 'nav-home';
 }
 */
  
  /*
 //unique class for every single menu item
 //$classes[] = 'nav-item-' . $ni->cid;
 */
  
  //Put all classes together into one space-separated string
  $ni->classes = implode(" ", $classes);
}
?>

Most of the functionality is commented out, with the exception of the two “current page” classes (because those are part of the default functionality). If you want to change the name of the “current page” or “current section” class, you can do so here. For example, let’s say you want the menu item for the currently-viewed page to just be the word “current” – change this:

1
2
3
4
if ($ni->is_current) {
  //class for the page currently being viewed
  $classes[] = 'nav-selected';
}

…to this:

1
2
3
4
if ($ni->is_current) {
  //class for the page currently being viewed
  $classes[] = 'current';
}

Similarly, if you want to change the name of the class for menu items that are “parent pages” of the page currently being viewed, change this:

1
2
3
4
if ($ni->in_path) {
  //class for parent items of the page currently being viewed
  $classes[] = 'nav-path-selected';
}

…to this:

1
2
3
4
if ($ni->in_path) {
  //class for parent items of the page currently being viewed
  $classes[] = 'in-path';
}

Easy enough, right? Now let’s say your main navigation menu is a drop-down and calls for a small “down arrow” icon to appear next to items that contain a drop-down – you can change this:

1
2
3
4
5
6
/*
if ($ni->has_submenu) {
  //class for items that have dropdown sub-menus
  $classes[] = 'nav-dropdown';
}
*/

…to this:

1
2
3
4
if ($ni->has_submenu) {
  //class for items that have dropdown sub-menus
  $classes[] = 'nav-dropdown';
}

(note that all we’ve done is remove the surrounding /* and */ comment markers so that this portion of code will get run).

Now your drop-down menu CSS could contain something like this:

1
2
3
4
.nav > li.nav-dropdown > a {
  background: url(../images/downArrow.png) no-repeat 100% 21px;
  margin-right: 10px;
}

Which might give us something like this:

Customizing The Markup

With the flexibility to change and add new class names to each item, you very likely don’t need to modify the HTML markup at all. But every now and then you have some particularly nasty HTML that you’d rather not change. No problem! If you scroll down to the bottom of the template file, you’ll see code that looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
echo '<ul class="nav">'; //opens the top-level menu

foreach ($navItems as $ni) {
  
  echo '<li class="' . $ni->classes . '">'; //opens a nav item
  
  echo '<a href="' . $ni->url . '" target="' . $ni->target . '" class="' . $ni->classes . '">' . $ni->name . '</a>';
  
  if ($ni->has_submenu) {
      echo '<ul>'; //opens a dropdown sub-menu
  } else {
      echo '</li>'; //closes a nav item
      echo str_repeat('</ul></li>', $ni->sub_depth); //closes dropdown sub-menu(s) and their top-level nav item(s)
  }
}

echo '</ul>'; //closes the top-level menu
?>

(note that you won’t see the opening and closing “php” tags in the real code – I unfortunately need to include those in order for the syntax highlighting on this blog to work – but if you actually add them to your code it will break!)

Okay, let’s start with an easy one. What if you want the nav menu to use an “id” instead of a “class”? Just change this line:

1
echo '<ul class="nav">'; //opens the top-level menu

…to this:

1
echo '<ul id="nav">'; //opens the top-level menu

What if the html for your menu has spans surrounding each link? Just add the necessary tags around the “a” links, by changing this:

1
echo '<a href="' . $ni->url . '" target="' . $ni->target . '" class="' . $ni->classes . '">' . $ni->name . '</a>';

…to this:

1
echo '<span class="menu-item"><a href="' . $ni->url . '" target="' . $ni->target . '" class="' . $ni->classes . '">' . $ni->name . '</a></span>';

(be careful to make sure you keep everything inside the single-quotes, otherwise the php code will get messed up)

Here’s a really tough one I’ve had to deal with before – a particularly gnarly dropdown menu that had rounded corners at the bottom, but needed to maintain IE6 compatibility(!). I wish I had this template back then, and if I did, I would have changed the line that closes out every dropdown sub-menu from this:

1
echo str_repeat('</ul></li>', $ni->sub_depth); //closes dropdown sub-menu(s) and their top-level nav item(s)

…to this:

1
echo str_repeat('<div style="bottom-rounded-corner"></div></ul></li>', $ni->sub_depth); //closes dropdown sub-menu(s) and their top-level nav item(s)

(the reason that code is inside the “str_repeat” function is so all levels of a multi-level dropdown menu are closed in case you’re at the last item in one level whose parent item is also the last of its level)

Good luck!

Comments