This is post 1 of 5 in a series titled Patterns for navigation controls
Most websites need some sort of navigation UI. If you have a site with more than a handful of pages then you're likely to need to create some components for rendering sets of links that show the structure of your site. So continuing my theme of simple patterns for Sitecore code, here is the first of a few posts on some approaches to navigation.
The primary navigation on many sites is a fixed set of links showing the top level pages of the site. A very simple control for displaying this can start from the following markup in a user control:
<asp:Repeater runat="server" ID="navRepeater"> <HeaderTemplate><ul></HeaderTemplate> <ItemTemplate> <li><asp:HyperLink runat="server" ID="navLink" /></li> </ItemTemplate> <FooterTemplate></ul></FooterTemplate> </asp:Repeater>
With some simple code to fill in the data:
protected void Page_Load(object sender, EventArgs e) { var rootItem = Sitecore.Context.Database.GetItem(Sitecore.Context.Site.ContentStartPath); navRepeater.DataSource = rootItem.Children; navRepeater.ItemDataBound += navRepeater_ItemDataBound; navRepeater.DataBind(); } void navRepeater_ItemDataBound(object sender, RepeaterItemEventArgs e) { if(e.Item.ItemType == ListItemType.AlternatingItem || e.Item.ItemType == ListItemType.Item) { var item = e.Item.DataItem as Item; var navLink = e.Item.FindControl("navLink") as HyperLink; navLink.Text = item.DisplayName; navLink.NavigateUrl = Sitecore.Links.LinkManager.GetItemUrl(item); } }
The root item of the current site is specified in the
<sites>
config of your site, which can be accessed via the
Sitecore.Context.Site.ContentStartPath
property. (Generally this is the right starting place for primary navigation - but if your site's IA doesn't follow that pattern you can employ alternative rules, or just use the component's Data Source here if you want to give editors the power to change this) Loading that item and binding its children to a repeater lets us display a list of links which can be formatted with some CSS.
Commonly, however, you want your primary nav to be able to show a bit more of the site's structure. Commonly that means being able to show the immediate children of each of your top level items. This can be achieved by nesting the same behaviour above, to find the children of each of the top level items as it goes through them. The mark-up can be adjusted to add the second level:
<asp:Repeater runat="server" ID="navRepeater"> <HeaderTemplate><ul></HeaderTemplate> <ItemTemplate> <li> <asp:HyperLink runat="server" ID="navLink" /> <asp:Repeater runat="server" ID="childRepeater"> <HeaderTemplate><ul></HeaderTemplate> <ItemTemplate> <li><asp:HyperLink runat="server" ID="childLink" /></li> </ItemTemplate> <FooterTemplate></ul></FooterTemplate> </asp:Repeater> </asp:Repeater> </li> </ItemTemplate> <FooterTemplate></ul></FooterTemplate> </asp:Repeater>
And the code similarly:
protected void Page_Load(object sender, EventArgs e) { var rootItem = Sitecore.Context.Database.GetItem(Sitecore.Context.Site.ContentStartPath); navRepeater.DataSource = rootItem.Children; navRepeater.ItemDataBound += navRepeater_ItemDataBound; navRepeater.DataBind(); } void navRepeater_ItemDataBound(object sender, RepeaterItemEventArgs e) { if(e.Item.ItemType == ListItemType.AlternatingItem || e.Item.ItemType == ListItemType.Item) { var item = e.Item.DataItem as Item; var navLink = e.Item.FindControl("navLink") as HyperLink; var childRepeater = e.Item.FindControl("childRepeater") as Repeater; navLink.Text = item.DisplayName; navLink.NavigateUrl = Sitecore.Links.LinkManager.GetItemUrl(item); childRepeater.DataSource = item.Children; childRepeater.ItemDataBound += childRepeater_ItemDataBound; childRepeater.DataBind(); } } void childRepeater_ItemDataBound(object sender, RepeaterItemEventArgs e) { if (e.Item.ItemType == ListItemType.AlternatingItem || e.Item.ItemType == ListItemType.Item) { var item = e.Item.DataItem as Item; var childLink = e.Item.FindControl("childLink") as HyperLink; childLink.Text = item.DisplayName; childLink.NavigateUrl = Sitecore.Links.LinkManager.GetItemUrl(item); } }
It's fairly common to add a bit of CSS and/or jQuery behaviour to make the second level of this data appear and disappear as a hover or click effect. And that behaviour can be applied on top of this mark-up using whatever patterns and styles are appropriate to your site.
One issue this code doesn't address is that there may be some pages that you need on your site which should not appear in the navigation. Things like login forms, or error pages need to exist in your content tree, but should probably not be shown in the navigation listings. This can be solved fairly simply with a field added to your pages to flag which pages should be shown in navigation. You can create a template to define it like so:
You can then add this template to the inheritance tree for your own page templates.
The code can then be adapted to filter out any items which don't have the "show in nav" field checked with a simple query instead of processing the
Children
property of the items:
private string ShowInNavigationQuery = "*[@ShowInNavigation='1']"; protected void Page_Load(object sender, EventArgs e) { var rootItem = Sitecore.Context.Database.GetItem(Sitecore.Context.Site.ContentStartPath); navRepeater.DataSource = rootItem.Axes.SelectItems(ShowInNavigationQuery); navRepeater.ItemDataBound += navRepeater_ItemDataBound; navRepeater.DataBind(); } void navRepeater_ItemDataBound(object sender, RepeaterItemEventArgs e) { if(e.Item.ItemType == ListItemType.AlternatingItem || e.Item.ItemType == ListItemType.Item) { var item = e.Item.DataItem as Item; var navLink = e.Item.FindControl("navLink") as HyperLink; var childRepeater = e.Item.FindControl("childRepeater") as Repeater; navLink.Text = item.DisplayName; navLink.NavigateUrl = Sitecore.Links.LinkManager.GetItemUrl(item); childRepeater.DataSource = item.Axes.SelectItems(ShowInNavigationQuery); childRepeater.ItemDataBound += childRepeater_ItemDataBound; childRepeater.DataBind(); } }
The
ShowInNavigationQuery
finds all the children of an item which have the "show" flag set to true. This is applied to the two repeater bindings in place of the previous reference to the child items collection.
More on navigation next week.
↑ Back to top