New clean(er) templates for Autonav, Page List, and Form blocks

Permalink 10 users found helpful
The built-in view.php templates for these 3 very important blocks (Autonav, PageList, and Form) are very messy and especially difficult for non-programmers (that is, designers who are familiar with HTML/CSS, but not PHP) to work with.

Over time I've created my own set of templates for these blocks that I use when building websites. These templates are much more consistent and separate the logic from the display as much as possible. The Form template is already available in the "Form Tableless Layout" addon, but instead of creating new addons for the other templates I've decided to just put them up on github -- it's unlikely that anyone who isn't a designer/developer would need these anyway and it doesn't really make sense to package them up as addons because they're more of a boilerplate or starting point than a complete working solution.

Feel free to use these on your site, and of course if you have any improvements or bug fixes, submit a pull request.

https://github.com/jordanlev/c5_clean_block_templates...

In the long run, I hope that these can serve as a good demonstration of how the built-in templates could be more "designer-friendly" and hopefully rolled into the core system. A huge benefit of them being rolled into the core is that a lot of the logic can be moved out of the templates themselves and into the controllers, thus making the view.php files even cleaner and easier to understand. If you have opinions on this matter, please post here as I'm interested to see what others think about this concept in general.

-Jordan

jordanlev
 
JohntheFish replied on at Permalink Reply
JohntheFish
I have been doing a similar exercise on Autonav, though it is still a work in progress and hasn't been used in anger yet, so no doubt capable of further refinement and probably a bit buggy.

As you have hinted, I have split the data structure from the display. The key parts are below.

Build the data structure:
$link_map=array();
$i=0;
foreach($aBlocks as $ni) {
   $_c = $ni->getCollectionObject();
   $target = $ni->getTarget();
   if ($target != '') {
      $target = 'target="' . $target . '"';
   }
   $pageLink = false;
   if ($_c->getCollectionAttributeValue('replace_link_with_first_in_nav')) {
      $subPage = $_c->getFirstChild();
      if ($subPage instanceof Page) {
         $pageLink = $nh->getLinkToCollection($subPage);
      }
   }


Display the navigation nested list

$size = count($link_map);
if ($size>0){
   echo "\n".'<ul class="menu-structure-class">'."\n";
   for ($i=0; $i<$size; $i++){
      $li_class = '';   
      // first in a level
      if (!isset($link_map[$i-1]) || ($link_map[$i-1]['level'] < $link_map[$i]['level']) ){
         $li_class .='first ';
      }
      // last in a level
      if (!isset($link_map[$i+1]) || ($link_map[$i+1]['level'] < $link_map[$i]['level']) ){
         $li_class .='last ';
      }
      // has sub levels
      $a_class = '';
jordanlev replied on at Permalink Reply
jordanlev
Wow, this is brilliant -- thank you for sharing this code. I never thought to re-map everything because it was such a daunting task to understand what's going on in the controller, but this sheds a lot of light on things for me.

It actually seems to be taking an opposite approach from what I've done -- I tried to completely hide the html output from the designer and instead put in options for every conceivable situation I have come across in my own work and in the forums, whereas you have made it so the output of the html is actually understandable. Cool!

Do you mind if I take this code and add it to my github repo? (I would probably modify it a bit first -- especially would need to add the "exclude subpages" functionality to your method because I find that to be a critical feature in every site I build.)

-Jordan
JohntheFish replied on at Permalink Reply
JohntheFish
Go ahead and put it to good use. Once you have it integrated, I may well pick it up again with your work and use in the menu I was developing this for.

On the 'exclude' functionality, I was under the impression it is still checked at the end of the first loop, then taken care of in the second loop - or maybe I left a lurking bug - this is code I was playing with a few weeks back, then put down to get on with something else, so it hasn't been tested apart from a quick play.
jordanlev replied on at Permalink Reply
jordanlev
Thanks, I'll try to remember to PM you if/when I update it.

For the 'exclude' functionality, I'm actually referring to 2 additional aspects above and beyond how C5 usually handles it (and how you've handled it in your code):

1) Excluding a page should also exclude all of its sub-pages. In the current implementation of the autonav block (and your code above), this is not the case and basically it means you need to go to every single sub-page of an excluded section and mark them as "exclude from nav", and also any new pages added to that excluded section need to be excluded as well -- otherwise they wind up in the nav menu (and at the wrong level too because their parent isn't there so they wind up as additional top-level items!) Addressing this problem is the main purpose of the "Autonav Exclude Subpages" addon I have in the marketplace.

2) Users should be able to exclude all sub-pages of a top-level page, without excluding the top-level page itself. For example, if you have a Blog section of your site, and you want the top-level Blog page to be in your nav menu but you don't want every blog post to be in the dropdown. This is the secondary functionality included in the Autonav Exclude Subpages addon (which adds a new page attribute for this purpose).
JohntheFish replied on at Permalink Reply
JohntheFish
Aah, now I see what you mean. I would guess you should find it fairly straightforward to add to the structure I was working on.
jordanlev replied on at Permalink Reply
jordanlev
BEHOLD!!! After a year of failed attempts, I have *finally* untangled the mess that is the autonav template! I think this is my proudest programming accomplishment of my life :)

JohntheFish, thanks for sharing your code -- seeing your approach is what got me headed in the right direction to actually make this thing work.

Oh man I am so excited about this -- I feel like I can actually show this template to a designer now and they'll have half a chance of being able to understand and customize it!

-Jordan
cali replied on at Permalink Reply
cali
Hi, i'm very interested by your new autonav template :)
jordanlev replied on at Permalink Reply
jordanlev
Awesome! Please post back here with any feedback (especially if you find bugs) -- thanks.
cali replied on at Permalink Reply
cali
i will for shure, very nicely designed.
for the moment just playing with autonav and trying it's template with nothing to say moore than "good job" on c5.4.2 & 5.4.2.1
JohntheFish replied on at Permalink Reply
JohntheFish
I like it when ideas build on each other. You have now taken it further than I was originally aiming and it opens up so many more possibilities.

Looking towards a realignment of the controller/view split. I think the array (and then string) of classes (lines 119-150) could be replaced with an array of symbols/tokens that are attached to the item object.

These tokens would then be translated into the actual classes in the html output loop so $ni->classes would be replaced with a token-array-to-class-string translation inserted at line 178 (does that make sense?), so putting all the presentation in one place. The view would then be just the output loop and everything else would be in the controller.

On slight tangents:

Where would code for the breadcrumb view split off from this? Can it then simply be a different output loop?

Are there any other attribute based filters that could usefully be added to the first part of the code?
jordanlev replied on at Permalink Reply
jordanlev
Ugh, I wrote a long response and then Chrome crashed on me (seems to happen from time to time with C5 forums -- this whole edit-in-a-lightbox thing drives me nuts, I wish they'd just keep it simple... okay enough complaining).

Basically I tried to do what you suggest but the code was too ugly because there was all of this class preparation code in the middle of the html output loop. I plan to submit this as a core update which means I'll be able to move most of the logic into the controller and then the template can just be one loop for classes and another loop for HTML, which will drastically simplify things even further.

re: breadcrumbs: well that's already a different template, but could be brought inline with this one if I add more data to the data structure (like one for "is_first" and one for "is_current") -- which I guess is another reason to add those "tokens" you talked about above.

re: other attributes: If there are more, let me know. But I've already included every one I've ever used myself or seen on the popular forum discussion threads about customizing autonav.

-Jordan
jordanlev replied on at Permalink Reply
jordanlev
I just updated the template on github to provide more data in the markup output loop. So now it is really easy to make this act as the breadcrumb template. Just delete everything below the "DESIGNERS: CUSTOMIZE THE NAV MENU HTML STARTING HERE..." line, and replace it with this:
foreach ($navItems as $ni) {
   if ($ni->current) {
      echo $ni->name;
   } else {
      echo '<a href="' . $ni->url . '" target="' . $ni->target . '">' . $ni->name . '</a>';
   }
   if (!$ni->last) {
      echo ' <span class="ccm-autonav-breadcrumb-sep">&gt;</span> ';
   }
}
aghouseh replied on at Permalink Reply
aghouseh
I was just discussing with some colleagues about a personal goal I was going to make it to really hone down a fully-featured autonav template, but it looks like you've already done the heavy lifting! Awesome job as usual, Jordan.
adamjohnson replied on at Permalink Reply
adamjohnson
Epically clear and easy to understand. Thanks for putting these together. If I could give you more karma, I would.
boomgraphics replied on at Permalink Reply
boomgraphics
My own autonav block has easy enough customization:

///////////////////////////////////////////////////////////////////////////////////////
//////// ALL ATTRIBUTES THAT THE VIEW CHECKS FOR ARE IN HERE FOR EASY CUSTOMIZATION. //
///////////////////////////////////////////////////////////////////////////////////////
$attrReplaceLinkWithFirstChild = 'bms_replace_link_with_first_child';
$attrExcludeNav = 'exclude_nav';
$attrAccessKey = 'bms_accesskey';
$attrForceSSL = 'bms_force_ssl';
$attrForceNoSSL = 'bms_force_no_ssl';
$attrUnlink = 'bms_unlink';
$attrLinkDescription = 'bms_link_description';
///////////////////////////////////////////////////////////////////////////////////////
//////// ALL CSS CLASSES USED ARE IN THIS SECTION FOR EASY CUSTOMIZATION. :-) /////////
///////////////////////////////////////////////////////////////////////////////////////
    $cssFirstLink = 'first ';
    $cssLevelOne = 'levelOne ';
jordanlev replied on at Permalink Reply
jordanlev
Thanks for sharing that. My initial attempt at this took a similar approach (if you browse through the github revision history for that file you can see it).

I know a lot of this is just a matter of preference, so I'm not saying that is the wrong approach or a bad one, but after a lot of thought I started to think that a more html-centric (as opposed to settings-centric) approach would allow for more flexibility to designers. For example, it is now trivial in my new template to change the <ul> and <li> tag structure to <div>'s or <span>'s. Or if you want to insert some non-semantic markup into the menu structure (for example, if you have extra <span> elements around each <a> tag to support IE-compatible rounded corners), it's very easy to see where those need to be inserted.

I can see on the other hand, though, that a template like yours that has everything in settings would be a better solution for someone who is not a designer and is not familiar with how HTML or CSS works at all. And if you wanted to make a new addon it would be really easy to plug in your settings to an edit interface.

EDIT: I just saw the "view entire code block" link on your code snippet and now realize that your template does also make it possible to change the HTML as well as the classes, so that's pretty cool!
boomgraphics replied on at Permalink Reply
boomgraphics
Ah yes, but I like your way much better, after learning how arrays work.
Also having the ability to look forward in the list is invaluable. :-P

I have somewhat modified your file (it's up to 473 lines :-)), but I think I have found a bug...It is probably something to do with concrete5, and I just updated, so maybe its the new software smell messing with my site, but I had a top level directory I copied into a second level directory, from the sitemap.

I checked the html output for the first class and the last class classes, and under that copied directory there one was one li and anchor tag that had both classes. I am thinking there is a bug somewhere in the database, but I haven't got a clue. :-)
jordanlev replied on at Permalink Reply
jordanlev
Interesting. I wonder if this is specifically because the pages were "copied" via the sitemap, or if it always does that under these circumstances.

Could you possibly post a screenshot of your expanded sitemap so I can recreate that structure and see if I can reproduce the bug myself? (Or email me at concrete@jordanlev.com if you don't want to post the sitemap pic publicly).

Oh, and just to be sure... if that new page is the only one in its level, then actually it *should* have both "first" and "last" classes (the "first" and "last" is for each menu level, not the first and last in the entire list).
boomgraphics replied on at Permalink Reply
boomgraphics
After you said that both first and last classes should be on a tag if it is the only tag for that level, I reexamined the html and realized that when I copied the whole directory into another top level folder, it created a single tag for the second level, and then everything was underneath it. So no bug. :-) My HTML output is getting quite complex and dynamic as far as class assignment logic so I missed that bit. Thanks though!
JohntheFish replied on at Permalink Reply
JohntheFish
The many different approached to translating the autonav data into HTML serve to highlight a constraint on a redesign of the controller. It must continue to support all the custom views that are in various templates and add-ons.

Looking at the other menu-list add-ons, such as Jordan's Manual-Nav, a view that works for one should conceptually work for any of them.
At the moment, the autonav controller has many views. What I am speculating about is many controllers being able to share each other's many views. A site designer looking for navigation could then mix and match between controllers (that edit/create an abstract navigation structure) and views (that take the abstract navigation structure and decide how to display it). It seems like a straight-forward ideal, but is it a realistic objective to bring all of this together?
jordanlev replied on at Permalink Reply
jordanlev
Supporting backwards compatibility in controllers is not very difficult -- just keep all of the same functions, and then add new ones as well that can be called by new templates, but just get unused by old templates.

Of course that assumes the general functionality of the controller is staying the same. If, on the other hand, you want to significantly change the way the thing functions (for example, adding the "manual nav" functionality to the autonav block), well then yes that's quite a challenge. I think in that case it just makes more sense to have a different block than to try to shoehorn everything into one. Look at the "Date Nav" block that's included with core, for example -- in the end it's outputting a list of pages just like autonav, but the settings in the add/edit dialog are totally different, so it makes more sense to just make a different block altogether for it.

I'm not really sure having the ability to mix and match views between different blocks/controllers really provides much benefit anyway. I really just think that the default out-of-the-box views should be a lot cleaner so they're easier for designers to customize (especially the all-important Autonav, PageList, and Form blocks).
andrewpietsch replied on at Permalink Reply
First of well done on the templates, they are a vast improvement over the current ones.

I still think however that much of the issue is that the autonav controller needs to be refactored into separate reusable components, a proper page/search API, a controller that uses it to generate a NavItemTree (the vast majority of Jordens code would go here), the NavItemTree would have a flatten method that would produce the list that would then be rendered. Essentially the template would only contain the designer bits.

I.e. you'd end up with something like this in the view (I'm ignoring backward compatibity here since this should probably be a new block, although it could probably be retrofitted) :
// By returning the tree developer types can use it as god intended (c;
// those who don't like trees can flatten it to a list..
$navItems = $controller->generateNavTree()->flatten()
// now proceed as normal in jordans tempate...
/******************************************************************************
* DESIGNERS: CUSTOMIZE THE CSS CLASSES STARTING HERE...
*/
etc...



Having a NavItemTree also lets you create nicely structured *unit testable* code. For example things like checking parent paths etc become:
$navItemTreeNode->isParentOf($currentPage)

instead of having to do array logic every time (in fact Page should probably have a $parents = Page::getParentPages() style method).


I'm keen to see C5 improve (better design patterns internally, unit testing, views that are objects and so on but I'm not sure if there's any motivation in the core team to do so (anyone?) and I really don't want to be working on a fork so I've held off on this kind of thing (plus I've been pretty busy).

And finally I think there's an issue with replace_link_with_first_in_nav when it's more than one level deep. I.e. it should use a while loop to find the first child without the attribute. Once I've merged your template into my code I can submit a patch if you like.

Well done! It's a great improvement.

Cheers
jordanlev replied on at Permalink Reply
jordanlev
I completely agree that most of this logic needs to be moved into the controller. I've already submitted a pull request to do something similar for the page list block. Like yourself though, I have limited time and haven't gotten around to similar autonav cleanup yet. I strongly encourage you to fork C5 on github and submit a pull request for any such changes -- there isn't a huge number of people actively contributing but that's all the more reason you should get involved.
andrewpietsch replied on at Permalink Reply
Yep, understood. For me it's been a bit of a chicken & egg problem, I'd like to see changes but haven't been too keen to invest to much time if the direction of the core team isn't heading in the same/similar direction (i.e. at least unit testing for a start and perhaps a path to cleaner db separation (sooo many queries strings everywhere!). I'd love to see C5 head in architectural direction outlined at http://googletesting.blogspot.com/2008/08/by-miko-hevery-so-you-dec... but obviously not all aspects since they require certain frameworks that are out of the question at this stage.

I've already got it checked out, when I get some time I'll have another look. The last time I looked into it I was still a complete newbie, I'm only a regular newbie now (c:

Cheers
TheRealSean replied on at Permalink Reply
TheRealSean
Great Thread, great information thank you all for sharing.
kaost replied on at Permalink Reply
Amazing, you just keep making my job easier. The idea of building a large scale website on concrete5 is becoming less like an idea, but more like a reality.

Oh.. and it's free. Thank you.
cmscss replied on at Permalink Reply
Awesome work for Designers once again Jordan!

For designers that stumble across this thread while looking for cleaner and clearer c5 templates, Jordan also has a Designer Gallery block that lets Designer create a gallery/slider block from any slider out there:https://github.com/jordanlev/c5_designer_gallery...

And heck, if you're new to c5, there's Jordan's life saving Designer Content block:https://github.com/jordanlev/c5_designer_content...

Without Jordan's work and help, I could never have deciphered how c5 fits together.

Thanks again Jordan.

Cheers

Ben
foiseworth replied on at Permalink Reply
foiseworth
Best thread ever - the autonav template rocks!

Thank you to everyone and Jordan in particular!
JohntheFish replied on at Permalink Reply
JohntheFish
(deleted, obsolete post)
jordanlev replied on at Permalink Reply
jordanlev
John,
I was very excited to hear this news, but I looked at the the latest version of the autonav block's view.php file in github and do not see what you're talking about. The latest update to this code was a year ago (for an unrelated issue). Can you please clarify?
JohntheFish replied on at Permalink Reply
JohntheFish
Oops.

To All: My apologies for my incorrect posts of 13 March.

Having done nothing with this since last summer, I just looked quickly at the view in 5.5.1 and thought 'I can understand that', so jumped to a conclusion that some of the ideas from here had been merged in. Now looking further this turns out to be very wrong. After Jordan's above post I have just compared the 5.5.1 and 5.4.2.2 files and they are identical.

So - anyone reading PLEASE IGNORE MY POSTS ON THIS THREAD FROM 13 MARCH. THEY ARE COMPLETELY WRONG AND MISLEADING.

I think that having got involved in unwrapping the rats nest my subconscious still recognised and understood the old code.

(EDIT August 2013) Jordan's GitHub repository is athttps://github.com/jordanlev/c5_clean_block_templates/blob/master/au... and put this file in your C5 as an override for autonav as /siteroot/blocks/autonav/templates/yourtemplatename. You can then set any autonav block to use this as a 'Custom Template'.

(EDIT August 2013) I have just double checked to make sure and the work from this thread is now part of the core autonav template. So the core autonav view should now be your starting point for any custom autonav template.
jordanlev replied on at Permalink Reply
jordanlev
Heh... no worries. I actually have this "problem" as well, where I get to know something so well that I forget how difficult it was when I first learned it. I always try to keep copious notes about what is confusing to me as I learn a new technology for this reason.

I do hope to take that template and submit it to the core code -- I think I'm finally going to have some time again to spend on c5-related activities (just been through a long relocation process, but finally seeing the light at the end of the tunnel).

Cheers,
Jordan
nosfan1019 replied on at Permalink Reply
Sorry if I might have missed it ... I'm new to C5 and I can't quite figure how to install this workaround. Can anyone tell me where to place the files?
JohntheFish replied on at Permalink Reply
JohntheFish
(deleted post - obsolete)
nosfan1019 replied on at Permalink Reply
Oh I see, thanks for the reply
jpcharrier replied on at Permalink Reply
jpcharrier
Firstly...

Thanks heaps for your awesome templates, such a life saver and great comment markup as well!

Sorry for a potentially n00b question here, but trying to get your autonav template to work with Twitter Bootstrap dropdown menus.

Ive narrowed it down to the if statement outputting each 'li'. When a menu item is a parent page, its outputting the normal link & the submenu link (both returning true in the php). Is there anyway to validate whether a menu item is a parent and thus only output once rather than twice? (hope that makes sense!)

Also, is there a way to make the outer dropdown li also have a class of 'active' when its child has a class of 'active'?

(screenshots and code below)

/******************************************************************************
* DESIGNERS: CUSTOMIZE THE HTML STARTING HERE...
*/
echo      '<div class="row">';
echo        '<div class="span10">';
echo          '<div class="navbar">';
echo            '<form class="navbar-search pull-right">';
echo              '<input type="text" class="search-query" placeholder="Search">';
echo            '</form>';
echo            '<div class="navbar-inner">';
echo              '<ul class="nav">';
foreach ($navItems as $ni) {
echo '<li class="' . $ni->classes . '">'; //opens a nav item
if ($ni->isEnabled) {
echo '<a href="' . $ni->url . '" target="' . $ni->target . '" class="' . $ni->classes . '">' . $ni->name . '</a>';


which outputs this HTML when a menu item is a dropdown... notice the a tag for "About" appears twice, the first one being constructed by the first if statement, and the second by the second statement (being it has a submenu):
<li class="dropdown">
<a class="dropdown" target="_self" href="/about/introduction/">About</a>
<a role="button" data-toggle="dropdown" class="dropdown-toggle" data-target="#" href="#">About <b class="caret"></b></a>
<ul role="menu" class="dropdown-menu">
<li class=""><a class="" target="_self" href="/about/introduction/">Introduction</a></li>
<li class=""><a class="" target="_self" href="/about/joint-venture/">Joint Venture</a></li>
<li class=""><a class="" target="_self" href="/about/airport-facts/">Airport Facts</a></li>
<li class=""><a class="" target="_self" href="/about/location/">Location</a></li>
<li class=""><a class="" target="_self" href="/about/infrastructure/">Infrastructure</a></li>
<li class=""><a class="" target="_self" href="/about/customise/">Customise</a></li>
<li class=""><a class="" target="_self" href="/about/consent/">Consent</a></li>
</ul>
</li>
JohntheFish replied on at Permalink Reply
JohntheFish
I have just double checked to make sure and the work from this thread is now part of the core autonav template (My false start on March 2012 has been overtaken by the core). So the core autonav view should now be your starting point for any custom autonav template.

There are many Bootstrap based themes, including some free ones, so I would look to them for how to achieve bootstrap styled nav menus.

However, please bear in mind the theme authors copyright if you release your finished theme to the marketplace.
TheRealSean replied on at Permalink Reply
TheRealSean
It may need some tweaking but this is the template I am currently using on a site with a bootstrap style navigation

A few default classes needed to be changed, not sure if they do in the core? this template is based off a lot of the code from this thread.

foreach ($navItems as $ni) {
    $classes = array();
    if ($ni->isCurrent) {
        //class for the page currently being viewed
        $classes[] = 'active';
    }
    if ($ni->hasSubmenu) {
        //class for items that have dropdown sub-menus
        $classes[] = 'dropdown';
    }
}
foreach ($navItems as $ni) {
    $submenu = $ni->hasSubmenu;
    if($ni->hasSubmenu && $ni->level==2){
        echo '<li class="dropdown-submenu ' . $ni->classes . '" role="presentation"> '; //opens a nav item
jpcharrier replied on at Permalink Reply
jpcharrier
Thanks Sean, that really helped!

Here is what I ended up with. Which is basically a simplified version of Seans tweaks.

/*** STEP 1 of 2: Determine all CSS classes (only 2 are enabled by default, but you can un-comment other ones or add your own) ***/
foreach ($navItems as $ni) {
   $classes = array();
   if ($ni->isCurrent) {
      //class for the page currently being viewed
      $classes[] = 'active';
   }
   if ($ni->inPath) {
      //class for parent items of the page currently being viewed
      $classes[] = 'active';
   }
   /*
   if ($ni->isFirst) {
      //class for the first item in each menu section (first top-level item, and first item of each dropdown sub-menu)
      $classes[] = 'nav-first';