Customizing the SharePoint 2010 Menu with CSS and jQuery

Author

SharePoint 2010 offers significant changes to the Web Content Management (WCM) facet of the platform.  A common goal for this version is to provide better HTML markup,  as well as easier skinning and themeing support for public Internet web sites.  While it was possible to create such sites in the previous version of SharePoint, it yielded bloated HTML with little support for web standards compliance and search engine optimization (SEO), and often required a certain degree of web trickery to make it visually appealing.

In most websites, a common UI component is a navigation menu which provides hierarchical links with dynamic animations.  In SharePoint, we have The AspMenu control, which has been around since MOSS 2007 and has its roots in the ASP.NET Menu control.  The drawback of such server control was the lack of ability for a web “devigner” (developer * designer) to control its rendered HTML markup.  Well, fear no more!  SharePoint 2010′s AspMenu has been revamped to include better support for CSS and HTML unordered lists (goodbye, nested tables).

There’s a new boolean property in the SharePoint AspMenu control named UseSimpleRendering, which is set to true by default in the Nightanday.master master page (the out-of-box master page for WCM sites using the Publishing features).  This “magical” property overrides the default rendering of the ASP.NET Menu control, and yields beautiful HTML in the form of nested unordered lists with CSS classes that can be easily styled.

Recently, I was challenged to style a SharePoint menu for a public website in order to provide custom images for each menu item, as well as rounded corners for the dynamic flyout panels – all that you might expect in a “HTML5″-like website.  As some of you readers might know, the behavior of the menu is to render text hyperlinks of each menu node based on a Site Map Provider.  So, how do we change the menu from text to a custom icon for each node?

jQuery and CSS to the Rescue!

In order to achieve the desired behavior, we establish a convention that takes the text of each static item on the menu and makes it a CSS class name that can be styled**.  In other words, if you have the following:

  • static item 1
  • static item 2
  • static item 3

Then you would have the following corresponding CSS classes in your stylesheet:
.staticitem1 { ... }
.staticitem2 { ... }
.staticitem3 { ... }

**note: some of your static menu item text strings might have non-alphanumeric characters, such as “&” and “?”.  Don’t worry, we’ll account for that in our JavaScript, later on.

Now, we’ll resort to jQuery to select the appropriate DOM elements for the static menu items, retrieve the text, and add a CSS class to the static item to have its background “painted” with the image corresponding to the menu item.  We start off with the default HTML markup similar to this:

<ul>
    <li class="static dynamic-children">
      <a class="static dynamic-children menu-item" href="http://www.yahoo.com">
        <span class="additional-background">
          <span class="menu-item-text">static item 1</span>
        </span>
      </a>
      <ul class="dynamic">
        <li class="dynamic">
          <a class="dynamic menu-item" href="/">
            <span class="additional-background">
              <span class="menu-item-text">Dynamic Item 1.1</span>
            </span>
          </a>
        </li>
      </ul>
    </li>
    <li class="static">
      <a class="static menu-item" href="http://www.google.com">
        <span class="additional-background">
          <span class="menu-item-text">static item 2</span>
        </span>
      </a>
    </li>
</ul>

Given the above DOM structure, we can write some JavaScript and use jQuery to inject our CSS at load time for each static item in my menu.  In order to make our jQuery selectors more efficient at finding the menu, I configured the AspMenu’s CssClass property with the value “my-menu”.  This simply forces the menu to render wrapped in an element decorated with this CSS class, which we can leverage as the starting point in the selector.  Here’s the script:

$(function(){
  $('.my-menu li.static').each(function(){
        var bkgdSpan = $('span.additional-background:first', this),
            textSpan = $('span.menu-item-text', bkgdSpan),
            itemName = textSpan.text()
                               .replace(/\?/g,"")
                               .replace(/\&amp\;/g,"&")
                               .replace(/\&/g,"and")
                               .replace(/ /g,"");
        bkgdSpan.addClass('mymenu-staticitem-image ' + itemName);
        textSpan.text('');
  });
});

Let’s break the above code down:

1) find every list item with a CSS class “static” who is a descendant of element with class “my-menu”
2) for each of these items, find the following:
- the first SPAN element with  class “additional-background”,
- SPAN elements with class “menu-item-text” inside the “additional-background” SPAN
3) parse the text value from the “menu-item-text” span and remove characters”?”, “&”, and empty space
4) add 2 new CSS classes to the “additional-background” SPAN: “mymenu-staticitem-image”, and another class derived from the text value in the span element itself.  THIS IS THE KEY TO GETTING THE CORRESPONDING IMAGE FOR THE MENU ITEM.
5) finally, since we are replacing the text string with an icon image for the static item, we remove the text content from the “menu-item-text” SPAN.

At this point, all you have to do is setup your CSS classes with the appropriate images for each static item and deploy your masterpage, css, and JavaScript changes to your SharePoint environment.  Here’s a sample CSS snippet to style the menu above, and include several other style rules to further modify the default menu:

/* reset some styles for cross-browser consistency */
.my-menu ul, .my-menu li, .my-menu div, .my-menu span, .my-menu a {
   margin: 0;
   padding: 0;
   border: 0;
   font-size: 100%;
   font: inherit;
   vertical-align: baseline;
}
/*** end reset ****/

.left-nav-bar .my-menu,
.left-nav-bar .ms-quicklaunchouter {
   font-size: 8pt;
   line-height: 1.25em;
}

/* item style */
.my-menu li.static > .menu-item {
   border-collapse: collapse;
   border-bottom: 1px #336666 dotted;
}

/* item style hover */
.my-menu li.static > a:hover { background: url(/images/nav_blue_bkgd.png) no-repeat center right; }

/* current selected static item */
.my-menu li.static .selected{ background: url(/images/nav_blue_bkgd.png) no-repeat center right; }

/* dynamic flyout holder */
.my-menu ul.dynamic {
   background-color:white;
   min-height: 85px;
   padding: 0 5px 0 5px;
   border-top: 1px #2e72bb outset;
   border-right: 1px #2e72bb outset;
   border-bottom: 1px #2e72bb outset;
   z-index:100;
   top: -15px !important;
}

/* dynamic flyout item */
.my-menu li.dynamic > .menu-item {
   background-color: white !important;
   padding: 10px 10px 10px 10px;
   border-bottom: 1px #336666 dotted;
   white-space:nowrap;
}

/* dynamic flyout item links */
.my-menu li.dynamic > a, .my-menu li.dynamic > a:visited{
   font-size: 8pt;
   color: #003366;
}

/* flyout item hover */
.my-menu li.dynamic > a:hover { color:#001428; }

/* dynamically added to static items via JS - to show correct CSS sprite image */
.mymenu-staticitem-image {
   background: url(/images/menuSprites.png) no-repeat right !important;
   position: relative;
   vertical-align:middle;
   width: 212px;
   height: 72px;
   border-bottom: 0px;
   right: -7px;
   top: 1px;
}

/* these classes are derived from the text string for each static menu item */
.staticitem1 { background-position: 0px 0px !important;}
.staticitem2 { background-position: 0px -73px !important;}

That’s pretty much all you need to transform you plain text SharePoint AspMenu into an eye-catching, CSS-styled menu with custom images.  Now, you must be thinking, ‘Wait a minute…at the beginning of the article, you said “and add rounded corners”‘.  Ah…yes, rounded corners for the dynamic menu panels.  This very neat effect is fully supported by CSS3′s new border-radius property.  However, older browsers (such as IE8 and older) do not implement CSS3, and thus we have to resort to a jQuery plugin to do the trick and degrade gracefully to good ol’ square corners for such browsers.

There are several different jQuery plugins that achieve this, but I have found Malsup Corners plugin to work best (http://jquery.malsup.com/corner).    Once you’ve deployed the plugin script file to SharePoint, just add the following line to your jQuery Ready function body:

$('.my-menu ul.dynamic').corner("keep");

That’s it!  We have successfully customized the new SharePoint 2010′s AspMenu using CSS and jQuery.  Here’s what mine looks like:

customized SharePoint AspMenu with images and rounded corners

customized SharePoint AspMenu with images and rounded corners

  • Shaun

    Hi Thiago,
    Really useful article. I applied this to change the global nav and it looks great. Until I go to a system page and then it ignores my awesome tab style and reverts back to normal. Any idea why this would happen on the system pages, such as site settings?
    Thanks
    Shaun

  • http://silvaware.net Thiago Silva

    Hi Shaun,

    glad you found the article useful. In order to apply these changes to system pages, you have to change the master page used by the system pages. You can do that by going to the Site Admin page and editing the master page configuration for the system pages.
    HTH

  • Jason Rothwell

    Thank you for posting this. I am trying to implement this but am having some trouble.I am not getting images to dynamically change. In the CSS, should I be replacing the graphic names there with others? I tried that but the result only shows those images. They don’t get replaced. You didn’t mention a naming convention for the graphic files either. Could you expand on that.

    Thank you for your time. I appreciate it.

  • http://silvaware.net Thiago Silva

    Hey Jason,

    if you look at the last couple of lines of the CSS file, where I list the classes “.staticitem1″ and “.staticitem2″, those class names should actually be the text of the span element that is shown in HTML from the menu (minus spaces, of course).

    The javascript code reflects this when we create the jQuery selector for those classes by concatenating the SPAN’s text (variable “itemName”) value as follows:
    bkgdSpan.addClass('mymenu-staticitem-image ' + itemName);

    HTH

    –Thiago

  • Pwilliams

    Thanks for posting this – but I’m having difficulty getting mine to work.  What I need is for each static link to have it distinct background image, and then the position of the background image should shift on hover.  I’m having trouble understanding exactly what your css is doing – from what I can tell, the .mymenu-staticitem-image class is the one that sets the initial background image for all the static links…but wouldn’t that mean that they are all the same?  That is what I’m seeing – also, all the hover images are the same as well (although a different image).  I looked at the source code and don’t see the class being added by the .addClass.  Can you tell me what I’m doing wrong?

    Thanks,
    Patty

  • Rama-selva

    Thanks Thiago Silva it’s really helpful

  • Roger

    I’m trying this with the top links (global navigation). When you say …I configured the AspMenu’s CssClass property with the value “my-menu”, are you sayijng that you changed CssClass=”s4-tn” to CssClass=mymenu in the masterpage?
    For now, I’m just trying to make it work by changing mymenu to s4-tn in your sample code

  • Manjunath Reddy

    Hi can you please provide the solution package for this solution..

  • Thiago Silva

    I used a page layout, not a master page, and then added the aspmenu to it and set the CssClass property. You can use whatever you want for that class, just be consistent throughout your scripts when referencing it.

  • Thiago Silva

    sorry, it’s been a few years, and I don’t believe i have a solution package for this. But based on the instructions above, you should be able to create your own, and you are more than welcome to put it up on Github or Codeplex and share a link here. The community would appreciate.

  • Thiago Silva

    Patty, we use a technique in CSS called sprites. Essentially, one big image that contains all images, and then using x/y coordinates in addition to the width and height, we can tell the style rule how to find the sprite in the large image.

    So, the class “.mymenu-staticitem-image” represents the large image, and then each more specific style rule below that just changes the coordinates and width height to find the respective sprite.

    HTH