This article is a follow up to the article posted on July 18th,  Exploring the API: Building a Custom Menu in which Mike Andrews presented a customization that leverages the new API in order to build a light weight, unordered list based menu while still maintaining the built-in page security model of NetCommunity.  In this article I am going to highlight a few more of the new API methods while I take the menu a step further and allow for 'skinning' and the ability for the menu to drop down and fly out!  I also add accessibility functionality to the menu.  The complete source for this project (C#) can be downloaded at the end of this article.  
  
Mike did a great job in the first article of explaining the API as it relates to menu content, I'm not going to rehash that article.  I'm going to mostly touch base on the additional functionality and the API functions that were required.  I'll also briefly touching on the .js required to support cross browser drop downs and keyboard accessibility.
 
At this point I would suggest you download the attached source code and compile the project. Don't forget to update the reference path in the project to point to your NetCommunity\bin folder so that it can find BBNCExtensions.dll.  You may also need to edit the DefaultDeploy.cmd file to point to your NetCommunity installation. If you've never built and deployed a custom part, I strongly recommend reading this first.
 

Skinning the menu 

 
One of my goals for this project was to allow for the menu to be 'skinable'.  For an HTML based project skinning is basically the application of different images and CSS to the HTML.   I have done this in a manner that allows the end user to simply select the name of the provided 'skin' (css file) from a drop down list on the edit part.  In order to accomplish this I needed to be able to dynamically add stylesheet links to the page that the part resides on.  Fortunately the API now allows for that, the code snippet below will demonstrate this. 
 
 

//The API now allows you to add custom stylesheets to a page.  This CSS path resolves to a css file that we create

//and using our deploy script deploy to the our custom folder structure that we create under the NetCommunity/Custom folder

//The edit part for this sample allows you to select a CSSClass name for the menu's root.  Here we use the same name

//to associate to a CSS file.  This allows different menus, perhaps on the same page, to be styled differently

this.API.Pages.CurrentPage.AddStylesheet(this.Page.ResolveUrl("~/Custom/NCC.Menu/css/" + mContent.RootMenuClass + ".css"));

 
 
As you may infer from the API call above, the API determines the current page and dynamically inserts the resolved stylesheet path into the page header.  In the example above, mContent.RootMenuClass is the name of the skin as stored in my part's content object,  and refers to the user selected skin name.  I use this to dynamically change the class names in the menu HTML.  I have provided 14 'skins' to get you started, the css and images for the skins are deployed by the included .cmd file and are located in the img and css directories of the project.
 
 

Javascript for the menu

 
I don't want to get too much into the SuckerFish methodology in this article since that is aptly described here however I do need to touch briefly on the core idea behind suckerfish in order to illustrate a point.  In order to provide cross browser compatibility for a UL based menu the suckerfish method relies on tricking Internet Explorer by adding a  pseudo hover class to all of the LI elements in the menu.  The :hover pseudo class should work with any element, but in Internet Explorer it only works with links thus the need to add it using DOM based scripting. 
 
The .JS file included with this example is a standard suckerfish .js function adapted so that we can support multiple menus, each with it's own style if necessary (a horizontal and vertical menu on the same page perhaps).  As you can see from the code snippets below, the RegisterClientScriptInclude call adds the link to the custom .js file that we provide with the customization and the RegisterStartupScript passes the RootMenuClass name of the current custom menu to the function in order to limit the LI nodes that we attach the psuedo hover class to as the partial .js snippet below shows:
 
 
ScriptManager.RegisterClientScriptInclude(this.Page, mULRoot.GetType(), "nccUlMenu", this.Page.ResolveUrl("~/Custom/NCC.Menu/js/UlMenu.js"));
 

Page.ClientScript.RegisterStartupScript(mULRoot.GetType(), "OnMenuLoaded" + mULRoot.ClientID, String.Concat("OnMenuLoaded('", mContent.RootMenuClass, "');"), true);

 
One of our design philosophies here in the product development group is that we do not want to wrap calls into the API that can be achieved in a more standard way.  An illustration of this is that we do not have any script registration calls exposed via the API.  This example uses the standard ScriptManager or Page.ClientScript calls to register the necessary .js file and startup scripts:
 
 
 
 This is a partial snippet of the standard suckerfish .js function, this function searches for all <li> elements within the menu's HTML structure and adds a class "sfhover" which can then be addressed in the CSS to provide for pseudo hover capabilities.  On line 1 below, className is the 'skin name' (RootMenuClass) that allows the js function to only iterate the nodes that belong to this particular menu instance (we don't want to add "sfhover" to every <li> on the page!
 
 

function OnMenuLoaded(className) {

 

        var mcAllUls = document.getElementsByTagName("UL");

        for (var iA=0; iA<mcAllUls.length; iA++) {

            if(mcAllUls[iA].className == className ){

 

                //attach sfhover class to elements

                var sfEls = mcAllUls[iA].getElementsByTagName("LI");

 

                for (var i=0; i<sfEls.length; i++)

                {

                    sfEls[i].onmouseover=function()

                    {

                        this.className+= (this.className.length>0? " ": "") + "sfhover";

                    }

                    sfEls[i].onmouseout=function()

                    {

                        this.className=this.className.replace(new RegExp(" sfhover\\b"), "");

                    }

                }

 
 
The complete Javascript file is included with the project in the /js folder.  The file is small (3kb) and not shown above is a section that adds functionality for cross browser keyboard navigation, a requirement for an accessible menu.
 

 Changing presentation based on a User's role

 
Another use of the API in my enhanced menu is to allow an edit button to surface on the menu if the currently logged in use is an administrator.  This is admittedly not all that important as you could simply click on the NetCommunity part edit button to essentially end up at the same place however it does add a certain 'skinnability' to the part, allows you to change the skin without clicking on 'edit this page', and also illustrates a potential use for another API function.  Using the code below:
 
 

if (this.API.Users.CurrentUser.IsAdmin)

 
 
I check to see if the current user is an admin.  If the user is an admin I then use the new PartTypeInfo class to query NetCommunity for the part type ID which is needed in order to spawn a PartEditButton.
 
 

///<summary>

/// This uses the API's Parts class to get the PartTypeID by name, this is needed by the PartEditButton server control

/// this in effect wraps up something like "SELECT ID FROM dbo.ContentTypes WHERE (Name = '?')

///</summary>

publicstaticint GetPartTypeId(string partName)

{

    BBNCExtensions.API.Parts.PartTypeInfo oPartTypeInfo = BBNCExtensions.API.NetCommunity.Current().Parts.GetPartTypeInfoByName(partName);

    if (oPartTypeInfo != null)

        return oPartTypeInfo.Id;

    else

        return 0;

}

 
 
Once I have the part type ID the following code creates one of the newly exposed PartEditButton server controls and adds it to my menu.
 
 

//dynamically create a PartEditButton API servercontrol

BBNCExtensions.ServerControls.PartEditButton peb = new BBNCExtensions.ServerControls.PartEditButton();

peb.CallbackFunctionName = "myToolBarEditCallback";

peb.CallbackContext = "editskin";

peb.Text = "Edit";

peb.Visible = true;

peb.PartId = this.Content.ContentID;

peb.PartTypeId = iPartTypeID;

peb.Attributes.Add("style", "font-size:9px; font-family:verdana;color:#333;border:dotted 1px #000;background:#eee;vertical-align:baseline;text-align:center;");

 

//add inside an <li> to make it sit on toolbar nicely at the end

HtmlGenericControl li = newHtmlGenericControl("li");

li.Controls.Add(peb);

li.Attributes.Add("style", "vertical-align:baseline;text-align:center;");

 

mULRoot.Controls.Add(li);

 
on the last line I add the <li> that contains the PartEditButton server control to the menu's controls collection.  This results in the button being displayed as the last item in the menu (at the root level).
 
 

Accessibility

I touched on keyboard accessibility in the section about the Javascript.   In order to be fully compliant we need to do more than simply add the ability to navigate the menu using the keyboard.  We also need to allow for the ability to SKIP the menu if desired.   The keyboard navigation tabs through every item, sub item, sub sub item, etc. in the menu.  As you can imagine it would be quite annoying to have to tab through a large number of menu items to get to, for instance, a simple login form.  In order to avoid this we add a skip link before the menu and a skip anchor after the menu.  This allows someone to select the skip link which will in effect hop them over the menu to the skip anchor where they can continue tabbing to the screen element they desire.
 
Look for this region in the source code to see where I add the skip link and anchor:
 
#region accessibility

 
 

ScreenShots

 

5 menus with different skins on the same page:

 
 
 

Expanded Menu:


 
 
 

Vertical Menu:

 
 
 
 
Please feel free to get back to me with any feedback or suggestions for improvements or other articles / topics you would like to see covered in the future.  tom.demille@blackbaud.com