Recently, I was tasked to help create multi-level main navigation within the master page of a website. After a little investigation and finding that the CSS List Menu would not work for my implementation, I concluded that it would be the perfect opportunity to use either a Universal Viewer or a Universal Viewer with custom query; both of which I implemented with Hierarchical Transformations.
I. Universal Viewer
Add the Universal Viewer web part to your master page, set the appropriate content and content filtering properties (just as you would for a CMS Repeater) and then check the boxes for Load hierarchical data, Use default hierarchical order and make sure that the Hierarchical display mode is set to: Inner. Select the 'OK' button and navigate to the Site Manager to define a new Hierarchical Transformation.
The Universal Viewer and Hierarchical Transformation provide the ultimate flexibility to display nested data that exists within the content tree. The Hierarchical Transformation is basically a collection of Transformation Types that can be defined for each level (note: levels always start at zero and transformations are inherited from a parent level if not defined) and also for each Document Class Type.
For a navigation with two levels, define a item transformation, header transformation and footer transformation for each level. Select the "New transformation" button, just as you would for any transformation.
For HeaderTransformationZero:
<nav id="MainNavigation">
<ul class="Menu0">
For FooterTransformationZero:
</ul>
</nav>
For ItemTransformationZero:
<li class="MenuItem0">
<a href="<%# IfEmpty(Eval("DocumentMenuRedirectUrl"),GetDocumentUrl(),Eval("DocumentMenuRedirectUrl")) %>" <%# IfEmpty(Eval("DocumentMenuJavascript"),""," onclick=\""+Eval("DocumentMenuJavascript")+"\"") %>><%# IfEmpty(Eval("DocumentMenuCaption"),Eval("DocumentName"),Eval("DocumentMenuCaption")) %></a></li>
For HeaderTransformationOne:
<ul class="Menu1">
For FooterTransformationOne:
</ul>
For ItemTransformationOne:
<li class="MenuItem1">
<a href="<%# IfEmpty(Eval("DocumentMenuRedirectUrl"),GetDocumentUrl(),Eval("DocumentMenuRedirectUrl")) %>" <%# IfEmpty(Eval("DocumentMenuJavascript"),""," onclick=\""+Eval("DocumentMenuJavascript")+"\"") %>><%# IfEmpty(Eval("DocumentMenuCaption"),Eval("DocumentName"),Eval("DocumentMenuCaption")) %></a></li>
Select "New hierarchical transformation" on the Transformations tab. Add all of the transformations by type and assign them to the appropriate level (remember to start at level zero). Go back to your Universal Viewer web part and select your Hierarchical Transformation.
What should render is something like:
<nav id="MainNavigation">
<ul class="Menu0">
<li class="MenuItem0">
<a href="[url]">[Name]</a>
</li>
<ul class="Menu1">
<li class="MenuItem1">
<a href="[url]">[Name]</a>
</li>
<li class="MenuItem1">
<a href="[url]">[Name]</a>
</li>
</ul>
<li class="MenuItem0">
<a href="[url]">[Name]</a>
</li>
</ul>
</nav>
For my navigation, I needed to embed sub <ul> within their parent <li>. Go to each Item Transformation and remove the </li> closing tag.
Create another transformation called SeparatorTransformationZero with just:
</li>
Add this transformation to your Hierarchical Transformation at the ZERO level (it will be inherited by all other levels without a Separator transformation).
You navigation should now render like:
<nav id="MainNavigation">
<ul class="Menu0">
<li class="MenuItem0">
<a href="[url]">[Name]</a>
<ul class="Menu1">
<li class="MenuItem1">
<a href="[url]">[Name]</a>
</li>
<li class="MenuItem1">
<a href="[url]">[Name]</a>
</li>
</ul>
</li>
<li class="MenuItem0">
<a href="[url]">[Name]</a>
</li>
</ul>
</nav>
Of course, you can customize your html attributes with any of the returned data for CSS purposes.
II. Universal Viewer with Custom Query
The Universal Viewer with Custom Query is ideal for data outside of the content tree, mixture of data inside/outside the content tree, or for just additional flexibility. I will provide examples for the last two.
For a mixture of data inside/outside the tree, consider the following example:
I have a document type called Restaurant. Restaurant is associated to States with a property called, "StateId." StateId can be selected from a dropdown list that I populate using the CMS_State table. I need to list out each State with Restaurants and also have the ability to implement different transformations.
First, create a query for the Restaurant document type such as:
SELECT StateID
,1 AS ParentStateID
,0 AS NodeLevel
,StateID AS DocumentID
,StateDisplayName AS Name
,'Foo.State' AS ClassName
,'Foo.State' AS NodeAliasPath
FROM CMS_State WHERE StateID IN (SELECT DISTINCT [StateID] FROM View_Restaurant_Joined WHERE ##WHERE##)
UNION
SELECT
NodeId AS StateID
,StateId AS ParentStateID
,1 AS NodeLevel
,DocumentID
,DocumentName AS Name
,ClassName
,NodeAliasPath
FROM View_Restaurant_Joined INNER JOIN
CMS_State ON CMS_State.StateID = View_Restaurant_Joined.StateID
WHERE ##WHERE##
(Note: Class names listed in Hierarchical Transformations do not have to be concrete document types. Hence, Foo.State!)
Next, create a Hierarchical Transformation just as described above and add a Universal Viewer with Custom Query to your page template. Select the Query and Hierarchical Transformation you created. Fill out the Hierarchical settings and Extended settings as such:
Done!
My next example is more of a Sql Server t-sql tip, but it works very well with the Universal Viewer with Custom Query web part. Basically, it involves using
Recursive Queries using Common Table Expressions. You can read more about it
here, but I will provide an example query. I can create a 3-tiered navigation with this query:
WITH CTE_Nav as(
SELECT treeAnchor.*
FROM [View_CMS_Tree_Joined] treeAnchor
WHERE treeAnchor.NodeSiteID = 1 and treeAnchor.NodeLevel = 1 and treeAnchor.DocumentMenuItemHideInNavigation = 0 AND
treeAnchor.ClassName IN ('CMS.MenuItem','TEST.Section') AND treeAnchor.Published = 1
UNION ALL
SELECT treeOther.*
FROM [View_CMS_Tree_Joined] treeOther
INNER JOIN CTE_NAV ON treeOther.NodeParentID = CTE_NAV.NodeId
WHERE treeOther.NodeLevel > 1 and treeOther.NodeLevel < 4 and
treeOther.DocumentMenuItemHideInNavigation = 0 and
treeOther.ClassName IN('CMS.MenuItem','TEST.SubSection','TEST.SectionPage','TEST.Restaurant') AND treeOther.Published = 1
)
SELECT ##TOPN## ##COLUMNS##
FROM CTE_NAV
ORDER BY NodeLevel, NodeOrder
SELECT treeAnchor is the actual anchoring query that is only executed once. SELECT treeOther is the looping query. You could mess around parameterize-ing different parts to make it more reusable or turn it into a stored procedure. Obviously, using the normal Universal Viewer web part is much less work. But, if you're unable to do so, this is not a bad option.
(note: No matter which node levels that are returned, your transformation levels should always start at zero. For example, if my query returned data with node levels from 1 to 3, my transformation levels would still start at level zero.)