One of the hardest things in any content management system is the construction of reliable, scalable navigation systems. While Textpattern facilitates rapid site development, building dynamic navigation elements can be a challenge because of the necessary code wrangling and tag trickery. There are a lot of plugins out there that help, but best practices dictate that a site should be built with as much native functionality as possible to avoid reliance on third-party solutions that might fall into development stagnation.
This tutorial explores building a fully dynamic navigation system using sections and articles. Our goal is to create a left-hand navigation that uses sections as the primary menu elements with articles as the submenu elements, and to make the submenu items appear only when that section is active. The tag examples take advantage of functionality available only in version 4.0.7 and beyond, and will not work with previous versions of the CMS.
Let’s pretend we’re building the navigation for a small corporate website. Our high-level navigation will look like this:
- Case Studies
Each of these represents a section. The above is what the visitor would see on the homepage, before any items are clicked. However, if they click “Services”, for example, we want to display the articles within that section as a submenu, like this:
- Garage Organization
- Gutter Cleaning
- Interior Painting
- Case Studies
As a bonus, we also want to add a class of “active” to whatever page is currently being viewed, so we can style the navigation accordingly.
A Quick Peek at Our Page Template
First, let’s look at what part of our page template might look like:
<div id="nav"> <txp:output_form form="nav" /> </div> <div id="content"> <txp:if_individual_article> <txp:article /> <txp:else /> <txp:article status="sticky" /> </txp:if_individual_article> </div>
Essentially, we have the nav relegated to a separate form, which we’ll tackle in a little bit. Below that is our main content area. This basic code is telling Textpattern that if we’re looking at an individual page (like “Gutter Cleaning”), show it, but if not, show the article with a sticky status for the landing page content. That way we can go ahead and create an article called “Services”, make it sticky, and use it for the landing page content when they click “Services” in the nav. Easy. On to the good stuff.
Using section_list as Our Foundation
We’re going to use the tag
<txp:section_list /> as our foundation for the navigation. This tag is designed, at its heart, to display a list of defined sections, usually in the context of navigation. Prior to 4.0.7, this would have been insufficient for this tutorial. However, it now boasts one key piece of new functionality — the ability to define a form that renders the output. This opens up an entire world of possibilities because we can now use all kinds of Textpattern tags to define precisely how that list of sections gets generated.
In the previous section, we referenced the form nav. This is a misc-type form. It contains this single line of code:
<txp:section_list form="nav.build" class="" break="" wraptag="dl" sections="about,services,case-studies,contact" />
Let’s examine this tag briefly. First of all, we’re using the new form attribute to reference nav.build, which we’ll cover in the next section. We’re then nulling the default values for the attributes class and break so they do not interfere with our HTML. We’re then defining a wraptag of
<dl>, which will encase our entire navigation. Finally, we’re telling Textpattern precisely which sections we want to appear in this list; this mirrors what we discussed in the beginning of the tutorial. Simple, right? On to the good stuff.
Getting Funky With if_different
Textpattern is very unique as a CMS, but one tag that almost singlehandedly defines the system’s off-the-beaten-path approach is
<txp:if_different>. Textbook describes it thus: “The tag will execute the contained statement when the value of the contained statement differs from the preceding value for that contained statement.” Not exactly intuitive. In order to understand its power (and limitations), you just have to experiment.
In the previous section, we referenced the form nav.build, which is an article-type form. This is where the heavy lifting is done. Here is the core content of nav.build:
<txp:if_different> <dt><txp:section link="1" title="1" /></dt> </txp:if_different> <txp:article_custom section='<txp:section />' form="nav.build.link" />
Let’s dig in. The
<txp:if_different> tag is wrapping a normal
<txp:section /> tag. That section tag will always be rendered, and in this case we want both the formal title to be displayed in addition to making it a link. After that, however, we are instructing Textpattern to go find all of the articles within that section and display each one using the form nav.build.link. Note that we are using the
<txp:section /> tag (in single quotes) as the value for the section attribute in the
<txp:article_custom /> tag, which is possible because of the new tag parsing engine in 4.0.7. This variable — which simply renders the current section name — enables us to avoid hard-coding section names, and helps make the navigation truly dynamic and scalable.
Making the Submenu Contextual
We’re not done yet. Using that above code, every article across all the sections would be rendered as submenu items and shown all at once, resulting in something like this:
- News and Press
- Garage Organization
- Gutter Cleaning
- Interior Painting
- Case Studies
This is not what we set out to do. We only want the submenu for the currently active section to appear, as we discussed in the beginning of the tutorial. In order for this to happen, we need to add a little more code to our nav.build form:
<txp:if_different> <dt><txp:section link="1" title="1" /></dt> </txp:if_different> *<txp:if_section name='<txp:section />'>* <txp:article_custom section='<txp:section />' form="nav.build.link" /> *</txp:if_section>*
The new code is in bold. This restricts the output of the submenu items to only ones that are attributed to the current section. In other words, when the visitor selects “Services”, those three submenu articles will appear because they are all attributed to the services section; articles in any other section will not render because the
<txp:if_section> tag is restricting the output. This is an important addition if you want to show only the submenu items that are applicable to that section.
Building the Submenu Links
OK, let’s move on to the article-type form nav.build.link, which we referenced above. This is small bit of code that outputs the content for each submenu article:
<dd><a href="<txp:permlink />"><txp:title /></a></dd>
This is an easy one. A simple
<dd> tag wraps the link, with the article title as the anchor text.
Adding an “Active” Class to Menu Items
Let’s review what we have to this point. Using our introductory example of “Services” as being the section the visitor clicks, our HTML would like this:
<dl> <dt><a href="/about">About</a></dt> <dt><a href="/services">Services</a></dt> <dd><a href="/services/garage-organization">Garage Organization</a></dd> <dd><a href="/services/gutter-cleaning">Gutter Cleaning</a></dd> <dd><a href="/services/interior-painting">Interior Painting</a></dd> <dt><a href="/case-studies">Case Studies</a></dt> <dt><a href="/contact">Contact</a></dt> </dl>
This is all good and fine, but to make it really ideal, we want to ensure that whatever page is currently being viewed has a class of “active” added to the corresponding menu item so that we can style it appropriately. We need to apply this to both the first level menu items (the sections) as well as the submenu items (the articles). We’ll need to edit both the nav.build and nav.build.link forms.
The Section Landing Pages
First, the first-level section-based navigation items in the nav.build form. The new code is bolded.
<txp:if_different> <dt*<txp:if_section name='<txp:section />'> <txp:if_individual_article><txp:else /> class="active"</txp:if_individual_article></txp:if_section>*> <txp:section link="1" title="1" /></dt> </txp:if_different> <txp:if_section name='<txp:section />'> <txp:article_custom section='<txp:section />' form="navbuild.link" /> </txp:if_section>
This is kind of convoluted, but essentially the two conditional tags
<txp:if_individual_article> are working in tandem to figure out if you’re really looking at a section landing page like /services. The former tag is triggered if you’re looking at the current section (which we are), and the second tag is triggered if the current page is not an individual article (which it is not — it’s a section). Once both conditions are met, a class of “active” is applied to the parent
<dt>. Removing either conditional tag would cause the exclusivity to fail. Here is an example of the final output:
<dl> ... <dt class="active"><a href="/services">Services</a></dt> ... </dl>
The Individual Article Pages
Now that we have tackled the first-level section menu items, we need to adjust the form nav.build.link so a class of “active” is applied to the appropriate submenu item when that individual page is being viewed. Here is the form again with the new code bolded:
<dd*<txp:if_article_id> class="active"</txp:if_article_id>*> <a href="<txp:permlink />"><txp:title /></a></dd>
This one is a little lighter than the first-level menu items because we only need one conditional to figure out whether we are looking at the current page. This takes advantage of new 4.0.7 functionality in the
<txp:if_article_id> tag, wherein the attribute id, when left blank, defaults to the current article. This funky logic is basically saying “if you’re looking at the current article, you must be looking at the current article.” This provides the perfect conditional for slapping a class of “active” on whatever page we’re currently viewing. Here is an example of the final output:
<dl> ... <dt><a href="/services">Services</a></dt> <dd class="active"><a href="/services/garage-organization">Garage Organization</a></dd> ... </dl>
This tutorial supports a relatively simple navigational structure, but one that is ideal for smaller websites. Adjusting a few conditional tags here and there will alter the resulting output greatly, so I encourage you to experiment to see what can be accomplished — especially with the
<txp:if_different> tag, expertise with which only comes after significant trial and error.