the 'ultimate navigation'
Rating: 3.3 / 5 stars - 3 vote(s).
Bob baty-barr (http://www.bootnumlock.com) and myself have been working on what is called the 'ultimate navigation' package. Bob posted his idea on the forum, I just jumped right in to help him out with some xslt scripting.
In this article, I'll explain a bit more about how to install the package, what it does and what it takes to get a navigation up-and-running in literally a few minutes.
First, download the package here. After that, install the package. At the moment, the package is only v4 compatible due to some architectural changes in the packager from v3 to v4. After a successful install, you should now have a new stylesheet, 5 new javascript files and a macro that references an xslt script file. All files except for the javascript files have a 'mtt_' prefix, so they should be easily identifiable.
If you still wishes to use this navigation on a umbraco v3 installation, you'll have to do some copy 'n' paste action to get all files in place.
What did Bob have in mind? Well, he wanted to create a customizable navigation based on some parameters:
- ** A node should not be shown if 'umbracoNaviHide' is set to true;
- ** Child nodes of a node should not be shown if 'umbracoHideChildren' is set to true;
- ** If a 'umbracoRedirect' property has been defined a any document type (of type 'textstring'), use the url as value for the href attribute
- ** If a 'externalURL' property has been defined and a value has been supplied, use this value as the value for the href attribute
- ** If a 'navText' property has been defined and has a value, it should be used as the text for the link.
- ** If a 'navTooltip' has been defined and a value has been supplied, use this as the text when hovering a link. If no value has been found, use the standard @nodeName attribute on the node as the value for the href attribute.
I've added two extra parameters to allow people to select a start node in the hierarchy and the maximum number of levels for the navigation.
For the remainder of the article, I'll go through the script to show how it's build.It might to useful to others to know how the navigation is built.
Reading both parameters is done like this:
1: <xsl:template match="/">
2:
3: <!-- Check whether a start node has been supplied -->
4: <xsl:choose>
5: <xsl:when test="$startNodeId != ''">
6:
7: <!-- Start building the top navigation from the node supplied by start node id -->
8: <xsl:call-template name="buildTopNavigation">
9: <xsl:with-param name="navigationNodes" select="umbraco.library:GetXmlNodeById($startNodeId)"/>
10: </xsl:call-template>
11:
12: </xsl:when>
13: <xsl:otherwise>
14:
15: <!-- Start building navigation from top level node -->
16: <xsl:call-template name="buildTopNavigation">
17: <xsl:with-param name="navigationNodes" select="umbraco.library:GetXmlAll()"/>
18: </xsl:call-template>
19:
20: </xsl:otherwise>
21: </xsl:choose>
22:
23: </xsl:template>
At this point, we're ready to build the navigation tree. As the first level needs different styling/css, we're first iterating all child nodes of our start node. Our template 'buildTopNavigation' will take care of that.
1: <!-- Start building the top navigation (first level navigation) -->
2: <xsl:template name="buildTopNavigation">
3: <xsl:param name="navigationNodes"/>
4:
5:
6: <ul class="mainNav">
7:
8: <!-- Iterate child nodes -->
9: <xsl:for-each select="$navigationNodes/child::node">
10:
11: <!-- Create var for easier reading/processing -->
12: <xsl:variable name="currentProcessedNode" select="."/>
13: <xsl:variable name="currentLevel" select="0"/>
14:
15: <!-- Check whether node should be visible in first level navigation -->
16: <xsl:if test="string($currentProcessedNode/data [@alias = 'umbracoNaviHide']) != '1'">
17:
18: <li>
19:
20: <!-- Build the navigation link using the node currently being processed in the for-each loop -->
21: <xsl:call-template name="buildLink">
22: <xsl:with-param name="node" select="$currentProcessedNode"/>
23: </xsl:call-template>
24:
25: <!-- Build next level navigation only if applicable -->
26: <!-- Still need to check whether all child nodes have been set to umbracoHideChildren = 1 whereas umbracoNaviHide = 0
27: this case would yield an empty ul element -->
28: <xsl:if test="(count($currentProcessedNode/node) > 0)
29: and (string($currentProcessedNode/data [@alias = 'umbracoHideChildren']) != '1')
30: and ($currentLevel < $maxDrilldownLevel)">
31: <xsl:call-template name="buildNavigation">
32: <xsl:with-param name="parentNode" select="$currentProcessedNode"/>
33: <xsl:with-param name="level" select="$currentLevel + 1"/>
34: </xsl:call-template>
35: </xsl:if>
36:
37: </li>
38:
39: </xsl:if>
40:
41: </xsl:for-each>
42:
43: </ul>
44:
45: </xsl:template>
As said, we iterate child nodes of the start node and call the template 'buildLink' which will take care building a single link for a single node. After building the link, we check whether we should process its child nodes and if so, call another template 'buildNavigation', which will recursively build the navigation tree from the second level until we reach the level set as a parameter.
1: <!-- A template used for building the non top navigation tree -->
2: <xsl:template name="buildNavigation">
3: <xsl:param name="parentNode"/>
4: <xsl:param name="level"/>
5:
6: <ul>
7: <!-- Iterate over the child nodes-->
8: <xsl:for-each select="$parentNode/node">
9:
10: <!-- Create var for easier reading/processing -->
11: <xsl:variable name="currentProcessedNode" select="."/>
12:
13: <!-- Check whether node should be processed -->
14: <xsl:if test="string($currentProcessedNode/data [@alias = 'umbracoNaviHide']) != '1'">
15:
16: <li class="child">
17:
18: <!-- Build the navigation link -->
19: <xsl:call-template name="buildLink">
20: <xsl:with-param name="node" select="$currentProcessedNode"/>
21: </xsl:call-template>
22:
23: <!-- Build next level navigation only if applicable; recursive call -->
24: <!-- Still need to check whether all child nodes have been set to umbracoHideChildren = 1 whereas umbracoNaviHide = 0
25: this case would yield an empty ul element -->
26: <xsl:if test="
27: (count($currentProcessedNode/node) > 0)
28: and (string($currentProcessedNode/data [@alias = 'umbracoHideChildren']) != '1')
29: and ($level < $maxDrilldownLevel)">
30: <xsl:call-template name="buildNavigation">
31: <xsl:with-param name="parentNode" select="$currentProcessedNode"/>
32: <xsl:with-param name="level" select="$level + 1"/>
33: </xsl:call-template>
34: </xsl:if>
35:
36: </li>
37:
38: </xsl:if>
39:
40: </xsl:for-each>
41:
42: </ul>
43:
44: </xsl:template>
Our template 'buildLink' is where all the magic happens. Here's is where we check the different properties and build a link based on the values of those properties.Based on the values of the properties, a different template is called. We use this technique to clarify the code a bit. We could have put everything into one big template, but that would make it more difficult to read.
1: <!-- A template that builds our navigation link based on node properties -->
2: <xsl:template name="buildLink">
3: <xsl:param name="node"/>
4:
5: <xsl:choose>
6:
7: <!-- Build link to external page -->
8: <xsl:when test="string($node/data [@alias = 'externalURL']) != ''">
9:
10: <xsl:call-template name="buildExternalLink">
11: <xsl:with-param name="node" select="$node"/>
12: </xsl:call-template>
13:
14: </xsl:when>
15:
16: <!-- Build link for redirecting to a custom supplied url -->
17: <xsl:when test="string($node/data [@alias = 'umbracoRedirect']) != ''">
18:
19: <xsl:call-template name="buildRedirectLink">
20: <xsl:with-param name="node" select="$node"/>
21: </xsl:call-template>
22:
23: </xsl:when>
24:
25: <!-- Default link builder -->
26: <xsl:otherwise>
27:
28: <xsl:call-template name="buildNormalLink">
29: <xsl:with-param name="node" select="$node"/>
30: </xsl:call-template>
31:
32: </xsl:otherwise>
33: </xsl:choose>
34:
35: </xsl:template>
Code for the different templates 'buildExternalLink', 'buildRedirectLink' and 'buildNormalLink' are quite self-explaining and can be found below. Each of those templates takes the current node being processed as a parameter.
1: <!-- A template that builds a link to an external page -->
2: <xsl:template name="buildExternalLink">
3: <xsl:param name="node"/>
4:
5: <!--
6: <xsl:call-template name ="outputNode">
7: <xsl:with-param name="currentNode" select="$node"/>
8: </xsl:call-template>
9: -->
10:
11: <a>
12: <!-- Set the href attribute -->
13: <xsl:attribute name="href">
14: <xsl:value-of select="$node/data [@alias = 'externalURL']"/>
15: </xsl:attribute>
16: <!-- Set the target attribute if available from the properties -->
17: <xsl:if test="string($node/data [@alias = 'externalTarget']) != ''">
18: <xsl:attribute name="target">
19: <xsl:value-of select="$node/data [@alias = 'externalTarget']"/>
20: </xsl:attribute>
21: </xsl:if>
22: <!-- Set the title attribute if available from the properties -->
23: <xsl:if test="string($node/data [@alias = 'navTooltip']) != ''">
24: <xsl:attribute name="title">
25: <xsl:value-of select="string($node/data [@alias = 'navTooltip'])"/>
26: </xsl:attribute>
27: </xsl:if>
28: <!-- Set actual text for the link, either available from the properties or just plain umbraco link-->
29: <xsl:choose>
30: <xsl:when test="string($node/data [@alias = 'navText']) != ''">
31: <xsl:value-of select="string($node/data [@alias = 'navText'])"/>
32: </xsl:when>
33: <xsl:otherwise>
34: <xsl:value-of select="$node/@nodeName"/>
35: </xsl:otherwise>
36: </xsl:choose>
37: </a>
38:
39: </xsl:template>
40:
41: <xsl:template name="buildRedirectLink">
42: <xsl:param name="node"/>
43:
44: <!--
45: <xsl:call-template name ="outputNode">
46: <xsl:with-param name="currentNode" select="$node"/>
47: </xsl:call-template>
48: -->
49:
50: <a>
51: <!-- Set the href attribute based on the redirect supplied -->
52: <xsl:attribute name="href">
53: <xsl:value-of select="netaddicts-be:FixLink(string($node/data [@alias = 'umbracoRedirect']))"/>
54: </xsl:attribute>
55: <!-- Set the title attribute if available from the properties -->
56: <xsl:if test="string($node/data [@alias = 'navTooltip']) != ''">
57: <xsl:attribute name="title">
58: <xsl:value-of select="string($node/data [@alias = 'navTooltip'])"/>
59: </xsl:attribute>
60: </xsl:if>
61: <!-- Set actual text for the link, either available from the properties or just plain umbraco link-->
62: <xsl:choose>
63: <xsl:when test="string($node/data [@alias = 'navText']) != ''">
64: <xsl:value-of select="string($node/data [@alias = 'navText'])"/>
65: </xsl:when>
66: <xsl:otherwise>
67: <xsl:value-of select="$node/@nodeName"/>
68: </xsl:otherwise>
69: </xsl:choose>
70: </a>
71:
72: </xsl:template>
73:
74: <xsl:template name="buildNormalLink">
75: <xsl:param name="node"/>
76:
77: <!--
78: <xsl:call-template name ="outputNode">
79: <xsl:with-param name="currentNode" select="$node"/>
80: </xsl:call-template>
81: -->
82:
83: <a>
84: <!-- Set the href attribute, either the alias if available, else use NiceUrl() -->
85: <xsl:attribute name="href">
86: <xsl:choose>
87: <xsl:when test="string($node/data [@alias = 'umbracoUrlAlias']) != ''">
88: <xsl:value-of select="netaddicts-be:FixLink(string($node/data [@alias = 'umbracoUrlAlias']))"/>
89: </xsl:when>
90: <xsl:otherwise>
91: <xsl:value-of select="umbraco.library:NiceUrl($node/@id)"/>
92: </xsl:otherwise>
93: </xsl:choose>
94: </xsl:attribute>
95: <!-- Set the title attribute if available from the properties -->
96: <xsl:if test="string($node/data [@alias = 'navTooltip']) != ''">
97: <xsl:attribute name="title">
98: <xsl:value-of select="string($node/data [@alias = 'navTooltip'])"/>
99: </xsl:attribute>
100: </xsl:if>
101: <!-- Set actual text for the link, either available from the properties or just plain umbraco link-->
102: <xsl:choose>
103: <xsl:when test="string($node/data [@alias = 'navText']) != ''">
104: <xsl:value-of select="string($node/data [@alias = 'navText'])"/>
105: </xsl:when>
106: <xsl:otherwise>
107: <xsl:value-of select="$node/@nodeName"/>
108: </xsl:otherwise>
109: </xsl:choose>
110: </a>
111:
112: </xsl:template>
Here's an overview of all properties/aliases used in the script. Remember, you really don't need them to make the script work. If you do not use any of the properties, the navigation will still build ok.
- ** Hide node from the navigation tree (umbracoNaviHide)
- ** Hide child nodes for a specific node (umbracoHideChildren)
- ** Redirect to another page (umbracoRedirect). Be aware as this property is defined as a textstring rather than as a contentPicker!!
- ** Use an alias for a node (umbracoUrlALias). If you really care about SEO
- ** Text to be shown (navText). If no value supplied, the node's attribute @nodeName will be used instead.
- ** Tooltip for the anchor tag (navTooltip)
- ** Link to an external (urlexternalURL)
- ** Target for an external url (externalTarget)
If you wish to use the navigation on your site, just create a new macro that references the 'mtt_ultimateNav' xslt script and add this macro to a template. Most probably, this will be the master template so the navigation is put on all pages of the site. Once you've installed the script, all there's left is some css/styling work to match the housing style.
Do you think this script is useful to you? Go and download the package and use it. We really appreciate your feedback or comments here.