Wednesday, October 19, 2011

CSS friendly menus with ASP.NET Menu control, jQuery and Superfish


Googling for references to people using the CSS Friendly Control Adapters didn't seem to turn up much, other than questions on stackoverflow like this one jQuery Menu and ASP.Net Sitemapthat didn't have a complete answer. In the end I've found a "hack" solution which is explained below - it is not 100% correct as the arrows for submenus aren't appearing (for example).
You can see the menus 'working' on this page - I have attached a sample project (60Kb) that contains JUST the stuff you need to get them working.
  1. CSSFriendly.DLL and .browser file come from Codeplex
  2. Superfish.css and .js must be downloaded from Joel Birch.
    hoverIntent.js is optional and is also linked from Joel
  3. jQuery 1.2.6 is linked from Joel or get the latest (1.3.2) fromjQuery.com
In addition to those external items, I created
  • Default.aspx (which is really just content)
  • Test.master
  • Test.sitemap with some links to make the menu
  • and added to web.config
The contents of these files are shown below:

Test.sitemap

This is a regular ASP.NET siteMap XML file. Normally most of these links would (presumably) be to internal pages on your website, but for this example I have made them all external links.
<?xml version="1.0" encoding="utf-8" ?>
<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
<siteMapNode url="~/Default.aspx" title="Home"  description="Home Page">
<siteMapNode url="http://jquery.com/" title="jQuery"  description="" />
<siteMapNode url="http://users.tpg.com.au/jbirch/plugins/superfish" title="Superfish"  description="">
<siteMapNode url="http://users.tpg.com.au/jbirch/plugins/superfish/" title="About"  description="" />
<siteMapNode url="http://alistapart.com/articles/dropdowns" title="What is Suckerfish"  description="" />
<siteMapNode url="http://users.tpg.com.au/jbirch/plugins/superfish/#download" title="Download"  description="">
<siteMapNode url="http://docs.jquery.com/Downloading_jQuery" title="Download jQuery"  description=""/>
<siteMapNode url="http://users.tpg.com.au/jbirch/plugins/superfish/?#download" title="Download Superfish"  description=""/>  
</siteMapNode>
</siteMapNode>
<siteMapNode url="http://cssfriendly.codeplex.com/" title="CSSFriendly.dll"  description="">
<siteMapNode url="http://cssfriendly.codeplex.com/?" title="About CSS Adapters"  description="" />
<siteMapNode url="http://msdn.microsoft.com/en-us/library/yy2ykkab.aspx" title="ASP.NET Sitemap" />
<siteMapNode url="http://cssfriendly.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=2159" title="Download"  description="" />
</siteMapNode>
</siteMapNode>
</siteMap>

Test.master

In addition to including all the relevant javascript files (lines 6 and 7) and CSS files (line 8), Superfish requires a 'setup' function which is in the script block (lines 10-14). I changed the parameter inside from 'ul.sf-menu' to ul.AspNet-Menu because by default the control uses that CSS class name.
Unfortunately that didn't make the menus work, so I also had to open superfish.css and search-and-replace sf-menu with AspNet-Menu. This is the "hack" because other superfish styles (such as the one that makes the arrows appear for submenus) don't work any more.
   1:  <%@ Master Language="C#" AutoEventWireup="true" CodeFile="Test.master.cs" Inherits="Test" %>
   2:  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
   3:  <html xmlns="http://www.w3.org/1999/xhtml">
   4:  <head runat="server">
   5:      <title></title>
   6:      <script type="text/javascript" src="/script/jquery-1.3.2.min.js"></script>
   7:      <script type="text/javascript" src="/script/superfish.js"></script>
   8:      <link   type="text/css" href="~/css/superfish.css" rel="stylesheet" media="screen" runat="server" />
   9:      <asp:ContentPlaceHolder ID="header" runat="server"></asp:ContentPlaceHolder>
  10:      <script type="text/javascript">
  11:          $(document).ready(function() {
  12:          $('ul.AspNet-Menu').superfish();
  13:          }); 
  14:      </script>
  15:      <style type="text/css">
  16:      body,h1,p,.AspNet-Menu{font-family:Tahoma}
  17:      </style>
  18:      <asp:ContentPlaceHolder id="head" runat="server">
  19:      </asp:ContentPlaceHolder>
  20:  </head>
  21:  <body>
  22:      <form id="form1" runat="server">
  23:      <h1>Css menus with asp:Menu and jQuery</h1>
  24:  <asp:SiteMapDataSource ID="SiteMapDataSource" runat="server" ShowStartingNode="false" />
  25:  <asp:Menu ID="Menu1" runat="server" 
  26:      DataSourceID="SiteMapDataSource"
  27:      Orientation="Horizontal">
  28:  </asp:Menu>
  29:      <div id="body" style="float:left;">
  30:          <asp:ContentPlaceHolder id="ContentPlaceHolder1" runat="server">
  31:              <!-- actual page content goes here -->
  32:          </asp:ContentPlaceHolder>
  33:      </div>
  34:      </form>
  35:  </body>
  36:  </html>

Web.config

The siteMapFile is configured to point at the Test.sitemap shown above. Ensure you place this in the correct location (inside system.web in your web.config file).
<system.web>
<siteMap defaultProvider="TestSiteMapProvider" enabled="true">
<providers>
<add name="TestSiteMapProvider" 
type="System.Web.XmlSiteMapProvider" 
siteMapFile="~/Test.SiteMap" 
securityTrimmingEnabled="false"/>
</providers>
</siteMap>

Output

The menu looks like this when rendered:
and the HTML source is a nice neat set of <ul><li> tags.

//TODO:

Ideally we would be able to have the asp:Menu output the correct classnames for Superfish, rather than search-and-replace in the superfish.css file. The author of this codeproject articlehas done exactly that by writing his own control to parse the sitemap XML directly.
It seems to me that the built-in functionality of the asp:Menu (shown below) should work - but perhaps the CSSFriendly Adapter is munging that behaviour?
<asp:Menu ID="Menu1" runat="server" 
DataSourceID="SiteMapDataSource"
Orientation="Horizontal">
<DynamicMenuStyle CssClass="sf-menu" />
</asp:Menu>