Archive for the ‘Navigation’ category

Creating Mega Drop Down Navigation in SharePoint with jQuery – Part 2

January 24th, 2010

This is part two in a three part series for implementing Mega Drop Down navigation in SharePoint 2007.  The topics covered are:

Part 1 – Tables, Unordered Lists, and SharePoint
Part 2 – Custom Control Rendering
Part 3 – Hooking it all up with jQuery

Since the out-of-the-box SharePoint menu control renders the HTML in <TABLE> elements,skinning and customizations are limited.  The rendering for my MegaDropDown is based on the <DIV> and <UL> elements and provides many more customization options.  Note:  In SharePoint 2010, the AspMenu control has an option to render with <UL> based elements.  I haven’t ported the MegaDropDown to SP2010 yet, but will post my results when I get a chance to convert it.

Once you bind to the navigation’s data source, you can easily walk through the collection of items and render HTML on the fly. I was doing this in my first iteration of the MDD, but things got a little complicated as I needed to parse all the available menu options, and then apply some custom jQuery logic once I had everything calculated.  That said, I opted for a two pass approach:

Pass 1 – Iterate through the navigation data source and build my own customized collection that stored some extended properties that were specific to the MDD.

Pass 2 – Iterate through the custom collection and render the HTML

While there is obviously an extra step involved (and another copy of the navigation), it really simplified the code and the overall process.

The custom collection essentially consists of several container classes that keep track of the navigation items (Title, Url, Type).  The first two properties are pretty obvious but Type is tailored for the MDD.  Type determines if the item is a standard menu item or is an image.  A benefit of the MDD is that is has the ability to display a site “image” within the navigation drop down itself (more on this later).

Code Snippet
  1. [Serializable]
  2. public class MenuItem
  3. {
  4.     public string Name;
  5.     public string Html;
  6.     public string Url;
  7.  
  8.     public MenuItem() { }
  9.  
  10.     public MenuItem(string name, string html, string url)
  11.     {
  12.         Name = name;
  13.         Html = html;
  14.         Url = url;
  15.     }
  16.  
  17.     public MenuItemType Type;
  18. }

Displaying and grouping items

There are two formats for displaying menu items: standard and grouped.  Standard is just what you would expect and menu items are rendered in the order defined in SharePoint.  Grouped items however, allow you to organize links into categories which can significantly simplify the interface when needing to display dozens of links.  A major requirement of the MDD project was to keep the customization as transparent and compatible with SharePoint as possible.  The grouping logic is one place where some customizations are visible.  In order to group items there needs to be a way to define the group name (i.e. category) as well as the links are associated with each category. 

Nav UnGrouped

MegaDropDown with uncategorized items

Nav Grouped

MegaDropDown with categorized items

The client I was working with was very adamant about using the standard SharePoint "Modify Navigation” UI so I am not entirely happy with the solution for grouping, but it does solve the problem without building any new web forms or pages.  A special syntax was created so an admin simply has to enter it within the standard UI.  When you want to specify that a link should fall within a specific group, you append the link title with $GroupName.  For example, if you wanted to create a group named Offices and add the Chicago office, you would enter:

Chicago$Offices

That’s it.  The parsing code will detect this and separate out the names automatically.  Here’s how it would look in the Navigation Link dialog:

image

As I said, I don’t exactly like the approach, but it is a pretty low-impact solution and took about 20 minutes to write the parsing code—and no UI changes were needed!

I keep track of each MenuItem in a generic List.  Since there are two types of dropdowns (standard and grouped), I keep track of each link type in a separate List. 

Code Snippet
  1. [Serializable]
  2. public class MenuNode : MenuItem
  3. {
  4.     public List<MenuGroup> Groups = new List<MenuGroup>();
  5.     public List<MenuItem> Items = new List<MenuItem>();
  6.     public List<MenuNode> Nodes = new List<MenuNode>();
  7.  
  8.     public MenuNode() { }
  9.  
  10.     public MenuNode(string name, string html, string url)
  11.         : base(name, html, url)
  12.     {
  13.     }
  14.  
  15.     public MenuState State;
  16. }

 

Keeping track of all of the navigation types is a little tricky, so hopefully the following diagram helps illustrate how the collections relate:

Once all the parsing is done and I am ready to render the items, I simply iterate through each list and generate the HTML:   For brevity, I’ve removed some of the housekeeping logic, but the RenderMenus() method walks the main List<> and calls RenderGroupItems or RenderMenuItems() accordingly:

Code Snippet
  1. private bool RenderMenus()
  2. {
  3.     int menuCount = 0;
  4.     string _menuItem = "<li class=’menuitem{0}’><a href=’{1}’><span class=’menuitem{2}’>{3}</span></a></li>";
  5.     string _parentMenuItem = "<li class=’nav_sites{0} menuitem{1}’><a href=’{2}’><span class=’groupmenuitem{3}’>{4}</span></a>";
  6.  
  7.     writer.Write("<ul id=’navigation’>");
  8.  
  9.     // renders the top level menu (horizontal navigation bar)
  10.     foreach (MenuNode node in _menuNodes)
  11.     {
  12.         string current = string.Empty;
  13.  
  14.         // if this site (or any sub-site within) is the current site, add a CSS style so we can hightlight it
  15.         if (node.State == MenuState.MenuSelected)
  16.             current = " current";
  17.  
  18.         // just a single-level menu item (no kids)
  19.         if ((node.Items.Count == 0) && (node.Groups.Count == 0))
  20.         {
  21.             output = string.Format(_menuItem, current, node.Url, current, node.Name);
  22.             writer.Write(output);
  23.         }
  24.         else
  25.         {
  26.             if (node.Groups.Count > 0)  // traverse all groups
  27.             {
  28.                 output = string.Format(_parentMenuItem, menuCount, current, node.Url, current, node.Name);
  29.                 writer.Write(output);
  30.  
  31.                 RenderGroupItems(writer, node, menuCount);
  32.  
  33.                 writer.Write("</li>");
  34.             }
  35.             else if (node.Items.Count > 0)
  36.             {
  37.                 output = string.Format(_parentMenuItem, menuCount, current, node.Url, current, node.Name);
  38.                 writer.Write(output);
  39.  
  40.                 scriptBlock = RenderMenuItems(writer, node.Items, menuCount);
  41.                 scripts.Add(scriptBlock);
  42.  
  43.                 writer.Write("</li>");
  44.             }
  45.  
  46.             menuCount++;
  47.         }
  48.     }
  49. }

Rendering Menu Items

For the most part, displaying each of the menu items involves the same logic except you iterate through each of the top level items menus.  As you read each item, you just emit individual <li> elements that represent each of the menus.

nav_gr

The end result of the HTML might look something like this:

Code Snippet
  1. <div id="navcontainer">
  2.     <ul id=’navigation’>
  3.         <li class=’menuitem current’>
  4.             <a href=’/Pages/Default.aspx’>
  5.                 <span class=’menuitem current’>Home</span>
  6.             </a>
  7.         </li>
  8.         <li class=’menuitem’>
  9.             <a href=’/news’>
  10.                 <span class=’menuitem’>News</span>
  11.             </a>
  12.         </li>
  13.        
  14.         <li class=’nav_sites0 menuitem’>
  15.             <a href=’/locations’>
  16.                 <span class=’groupmenuitem’>Locations</span>
  17.             </a>
  18.  
  19.             <div id=’menu0′ class=’menu’>
  20.                 <div class=’menunavitems’ id=’menunavitems_1′>
  21.                     <table cellpadding=’0′ cellspacing=’0′ class=’navlinklayout’ width=’100%’ border=’0′>
  22.                         <tr>
  23.                             <td class=’grouplayout’ valign=’top’>
  24.                                 <h3 class=’cat_header’>Offices</h3>
  25.                                 <ul class=’sub_nav’ id=’1′>
  26.                                     <li>
  27.                                         <a href=’/offices/chicago’><span>Chcago</span></a>
  28.                                     </li>
  29.                                     <li><a href=’/offices/newyork’><span>New York</span></a></li>
  30.                                 </ul>
  31.                             <td>
  32.                         </tr>
  33.                     </table>
  34.                 </div>
  35.             </div>
  36.         </li>
  37.        
  38.         <li class=’menuitem’>
  39.             <a href=’/reports’>
  40.                 <span class=’menuitem’>Reports</span>
  41.             </a>
  42.         </li>
  43.  
  44.     </ul>
  45. </div>

 

The code is fairly straightforward and I tried to make the HTML as simple as possible.  The entire navigation, including the mega drop down sits inside the main <div> container ‘navcontainer’.  The ‘navigation’ <UL> handles the horizontal top ‘tabbed’ navigation menu.  Since it is <UL> based, I style the layout and appearance with CSS.  Each <LI> represents a tab defined in SharePoint’s navigation page. 

When sub-navigation items exists (e.g. sub-sites), a new <DIV> (menu) is defines the drop down itself.   Although I tried to follow a flexible <DIV> based layout, the sub-items list is tabular and fits nicely in a <TABLE>.   If grouping exists, each group (or category) is displayed with a styled <H3>.  All the sub-items are then displayed with an embedded <UL> (sub-nav).

In my next and final article, I will explain how I hook it all up with jQuery.  (I promise that it won’t be nearly as long between postings).

As always, comment or email me if you have any questions.

Share

Creating Mega Drop Down Navigation in SharePoint with jQuery – Part 1

August 10th, 2009

This is part one in a three part series for implementing Mega Drop Down navigation in SharePoint 2007.  The topics covered are:

Part 1 – Tables, Unordered Lists, and SharePoint
Part 2 – Custom Control Rendering
Part 3 – Hooking it all up with jQuery

Although the styling and navigation had to be scrubbed, a screen cast of the final solution can be found here:

 

Providing users with rich navigation is the latest trend in web design and the hottest technique today is the Mega Drop Down.  Rather than providing single column navigation, Mega Drop Downs present menu information in a rich, two-dimensional format:

image

As you can see in the above example, the drop down menu provides navigation links in multiple columns (and even has a slick transparency effect).  Sites with Mega Drop Downs are popping up all over the web and usability guru Jakob Nielsen has written a great article describing the benefits.

For most sites, implementing a Mega Drop Down is fairly straightforward and most of the effort focuses around CSS.  If you have ever customized navigation in SharePoint, you are well aware that things are easy enough—but only to a point.

Out-of-the-box, SharePoint uses the SharePoint:AspMenu class that inherits from the standard ASP.NET asp:Menu control.  This control provides the ability to be skinned and can be customized through the use of CSS and ItemTemplate support, but at its core, it still is a <TABLE> based control and isn’t flexible enough to radically change its appearance.

Rather than relying on <TABLE> based navigation, CSS unordered lists <UL> can easily be used to implement navigation.  I want to keep this post focused on SharePoint and not CSS, so if you want to read up on how to create the shell for the Mega Drop Down, I recommend starting with this article,   To summarize the concepts, we essentially create a nested set of unordered lists:

&lt;ul id=’topNav’>
  <li>Employee Services</li>
    <div id='mdd'>
      <h3>Benefits</h3>
       <ul id='subNav'>
        <li>401(k) Information</li>
      </ul>
     </div>
   </li>
</ul>

 

The ‘topNav’ <ul> represents the top navigation that is displayed horizontally.  The <div> is the actual Mega Drop Down itself and contains one or more ‘subNav’ <ul> elements that represent the sub-menu items.  The <h3> tag is used as a category heading so we can group menu items together (more on that in part 2).

CSS is used to style the outer ‘topNav’ so elements are displayed inline.  We also hide the <div> as it should only appear when you hover over an <li> within ‘topNav’.  I applied some extra styling, but so far, navigation should look something like this:

 

image

 

We’ve defined the structure of the menu and navigation, but up until now, it has all been static.  To make this work in SharePoint, we have to provide a method of displaying navigation dynamically.  There are several different methods of replacing the default navigation, and for this solution, I opted to create a custom navigation control.   A great overview of the various options can be found here.  Earlier I mentioned that out of the box, SharePoint uses the asp:Menu control to render menu items.  This is a standard data-bound control that references a DataSource for rendering navigation.  If you look inside default.master with SharePoint Designer, you’ll see how all this works:

 

<SharePoint:AspMenu
  ID="TopNavigationMenu"
   DataSourceID="topSiteMap"
   runat="server"
   ...
/>
<asp:SiteMapDataSource
  ShowStartingNode="False"
   SiteMapProvider="SPNavigationProvider"
   id="topSiteMap"
   runat="server"
  StartingNodeUrl="sid:1002" />

 

The SiteMapDataSource is a standard ASP.NET data source control and is widely used in ASP.NET web development.  If you are not familiar with how navigation works in ASP.NET, MSDN has a great overview.  The important concept to note here is the SiteMapProvider property and the SPNavigationProvider value.  SPNavigationProvider is a class that follows the standard provider model and essentially is the code that retrieves the navigation hierarchy from SharePoint.  SPNavigationProvider handles all the mundane tasks of retrieving the data and security trimming results so users don’t see links to sites that they may not have access to.

As mentioned, we are scrapping the SharePoint:AspMenu control because it renders <TABLE> based navigation and are replacing it with a shiny, new <UL> based navigation control.  Other than being difficult to pronounce (and type), HierarchicalDataBoundControl is the underlying class that asp:Menu uses to retrieve its data.  We are simply bypassing asp:Menu and its bias for <TABLE> layouts and going directly at the data.   Our custom control’s declaration looks something like this

:

public class MegaDropDown : HierarchicalDataBoundControl
{
   protected override void PerformDataBinding()
   protected override void Render(HtmlTextWriter writer)
}

 

The two methods that we need to override are PerformDataBinding() and Render().

The PerformDataBinding method is where we will retrieve our data from the data source (indirectly through SPNavigationProvider) and then (ultimately) parse the hierarchical data that is returned.  Initially, the GetData() method is called to…wait for it…get the data.  In general, these controls can represent many different flavors of data.  Since we are working with hierarchical data, GetData() happily returns a reference to a HierarchicalDataSourceView object that represents a view into the navigation.  A data source view is similar to a database view and has the ability to provide a subset of data based on a query.  Coincidentally, HierarchicalDataSourceView points to a nested list of the navigation data.  For our purpose, there isn’t a need a need to filter results and a simple call to the Select() method will suffice.

Having a reference to the view is great, but what we really want is an enumerable reference to the actual data (remember, the view is just an abstraction of the data).  Having the IHierarchyData interface gives us so many options (well, not really) but since everyone likes enumerable objects, getting to the data is only a two-step process.  Once we call GetHierarchyData(), we only need to invoke IHierarchyData.GetChildren() to access the enumerable data

Surprisingly it is very straightforward and only involves a dozen or so lines of code::

protected override void PerformDataBinding()
{
   base.PerformDataBinding();

   // Do not attempt to bind data if there is no data source set.
   if (!IsBoundUsingDataSourceID && (DataSource == null))
      return;

   HierarchicalDataSourceView view = GetData("");

   if (view == null)
      throw new InvalidOperationException("Cannot get any data.");

   IHierarchicalEnumerable enumerable = view.Select();

   if (enumerable != null)
   {
      IHierarchyData data = enumerable.GetHierarchyData(rootItem);
      IHierarchicalEnumerable newEnum = data.GetChildren();
   }
}


Next up is the HTML rendering and actual control implementation…Part 2 – Custom Control Rendering

Share