Creating an accordion-style SharePoint Quick Launch menu with jQuery

About two months ago, a colleague of mine told me of a requirement he had to build an accordion-style menu for an intranet MOSS portal he was building. Not knowing where to start, he asked me what he could do. I advised him that he could either try to work with the <SharePoint:AspMenu> control used by the Quick Launch out of the box or he could use the Accordion control found in the AJAX.NET Control Toolkit and (combined with the SharePoint navigation providers), he could roll his own menu control. They never got around to actually doing this as more critical requirements came about and they eventually postponed this accordion-style menu to a later phase.

Last weekend, I finally decided to look into the examples and documentation around jQuery. I had heard of it before but never really had a chance to play around with it. But I decided that it would be a worthwhile investment of a few hours to learn it, especially since I had read a few weeks ago that Microsoft had put it full jQuery support into Visual Studio 2010. What I found after a few hours of digging in impressed me. I can see why everyone seems to be buying in and using jQuery. It simply makes client-side, rich internet application development much simpler. I have never enjoyed writing Javascript (frankly, I've tried to avoid it as much as possible) but with jQuery, it makes it so easy.

So, being as excited as I was, I started to dream up some ideas I had on how I could use jQuery. I have kicked around a few ideas with some of my colleagues and acquaintances and the ones they thought were good ideas, I will start to build during the upcoming days and months. I will release the code either here (if it is just small snippet code) or in CodePlex if I build out libraries or toolkits. But yesterday, as I was laying in my bed freezing (it was 45 degrees in my house because my furnace went out and had to be repaired this morning), I thought about the accordion control my colleague mentioned. I thought to myself, now this is something I think could do quickly with jQuery. So today, as I waited for the service guy to fix my furnace and for my new treadmill to be delivered, I wrote some jQuery code to make the SharePoint Quick Launch menu work like an accordion. 

Getting Started

By default, the following image is the default appearance of the Quick Launch menu in my dummy test portal: 

I annotated the image in order to better explain what I did. First, in order to understand the javascript I wrote, it is important to understand the html output that is emitted by the Quick Launch menu. Every site in the menu is outputted as a table (item B). That table has the CSS class 'ms-navheader' applied by default (I say by default because you can always override the CSS class assigned in the <SharePoint:AspMenu> control; all references to CSS classes here are in reference to the default CSS classes assigned out of the box). Within this table, a link (item A) is added to the actual site. Each of these anchor tags also have the 'ms-navheader' CSS class applied to it. Lastly, each sub-menu section (item C) is another table with the CSS class 'ms-navSubMenu2' applied.

My accordion would function as follows (the UI requirements, if you will):

  • Only one sub menu at a time should be visible.
  • If you click on a site's navigation bar (item B), the sub menu (item c) should appear and all other sub-menus would disappear.
  • If you click on a site's navigation bar to attempt to expand the sub-menu but the site has no sub-items, nothing should happen (the currently visible sub-menu should still remain).
  • Clicking on the site link (item A) should still continue to take you to the site.
  • By default, when the page is first rendered, the current site the user is on should have its sub-menu expanded out.

The following zip file contains the javascript I wrote to satisfy these UI requirements (the zip also contains both the minified and Visual Studio annotated 1.2.6 version of jQuery): quicklaunch_accordion.zip (58.59 kb)

Some highlights and explanations of the code: 

Finding each sub menu 

//For each Quick Launch navigation sub menu:

    $("table.ms-navSubMenu2").each(function(){

        //Find any navigation items under the sub menu that have been selected.

        var selectedNavItems = $(this).find("a.ms-selectednav");

 

        //Find the corresponding navigation header of the current sub menu being processed

        var menuHeader = $(this).parents("tr:eq(0)").prev("tr").find("table.ms-navheader:eq(0)");           

 

        if ($(menuHeader).hasClass("ms-selectednavheader") || selectedNavItems.length > 0)

        {

            //if the navigation header for this sub menu is selected or if there are any

            //selected navigational items in this submenu, show the submenu.

            $(this).show();

        }

        else

        {

            //otherwise, hide the submenu

            $(this).hide();

        }

    });

When the page is first rendered, the above snippet finds each sub-menu and automatically hides or displays them based on the UI requirements above. A sub-menu item link that is selected will have the 'ms-selectednav' CSS class applied. Similarly, if the user is currently on the welcome page of a site that appears on the Quick Launch, the 'ms-selectednavheader' CSS class will be applied to the corresponding table element in the Quick Launch html.

Handling a site's link (item A) click event

//When a user clicks a navigation header, the user should be taken directly

//to the site link. The javascript event handler to hide/display the submenus

//should not be triggered.

    $("a.ms-navheader").click(function(e){

        e.stopPropagation();

    });

 

Normally, the javascript that is triggered when clicking on the table navigation header (item B) would execute because this anchor tag is within that table. Calling stopPropogation() prevents that click event on the table from firing.

 

Event handler for site menu items

 

//Finally, this adds a click event handler for the navigation header table

    $("table.ms-navheader").click(function(e)

    {           

        var subMenu = $(this).parents("tr:eq(0)").next("tr").find("table.ms-navSubMenu2:eq(0)");

        if (subMenu.length > 0)

        {

            //only if we have a submenu should we hide the other submenus and show the current one.

            $("table.ms-navSubMenu2").hide("slow");

            subMenu.show("slow");

        }

 

    });

The above snippet adds an event handler to handle clicks on the table. And the final output of this code is:

Portal home page (on first load) 

 

Portal home page (News expanded) 




Portal Home Page (Search expanded)

Portal Home Page (Sites expanded)

News Home Page

Sub link Page



Configuring SSO for MOSS with split-farm back-to-back topology

I had to configure SSO on a MOSS farm with split back-to-back topology with the Web Front Ends (WFEs) in the perimeter network belonging to one domain and SQL Server in the internal network belonging to another domain (there was a trust relationship defined between the two domains). All the service accounts, farm accounts, and database access accounts being used came from the internal domain.

When I tried to configure the SSO service (ssosrv.exe) to use an account from the internal domain, I would get the following error:

A Single Sign-on error has occurred.  Please contact an administrator.  Details: The network path was not found.

There would also be a corresponding entry in the event log like this:

User internaldomain\internaluser failed to configure the single sign-on server. The error returned was 0x80630005. Verify this account has sufficient permissions and try again.

The account used absolutely had sufficient permissions. It met all the criteria specified in http://technet.microsoft.com/en-us/library/cc262932.aspx for the ssosrv.exe service account. I tried and re-tried with other accounts, making the account a domain admin, giving it every SQL Server permission known to man (jk) and it still didn’t work. I even manually created the SSO database myself and ran the SSO schema script myself and manually added permissions to it but I still couldn’t get SSO configured in Central Administration. Actually, I had ruled out issues with SQL Server permissions earlier because when I checked the SQL Server logs, there weren’t even any log entries for login failures. So I sort of came to the conclusion that something else was happening on the WFE before it even got to the database.

After struggling with the configuration for a while and looking at many error log entries, I finally got it configured. Looking at the ULS logs actually helped me resolve the issue because I noticed the following entry:

Net{User|Group}GetInfo said Account {internaluser} does not exist   

What a strange error. Clearly, SSO configuration was attempting to use the command-line command NET USER internaluser to get information about the account being used by ssosrv.exe. But issuing that command goes against the current domain of the machine, which in this case was the domain of the perimeter network and not the internal network domain where this account really came from. By switching the account used by ssosrv.exe to an account that instead came from the perimeter domain and adding that account to SQL Server (and making sure it conformed to the criteria as outlined in the article above), I was able to successfully configure the SSO.

Just another example where the error displayed hardly gets you down the right path.

Issue tracking permissions issues with Project Server

A client wanted to be able to secure specific items in the Issues list of a Project Workspace site so that only a certain group could view the item. No problem, right? Just use the out of the box item-level permissions functionality in SharePoint.

Not so fast. This works well until you start linking a project task to one of these issues. If a project task is linked to an issue or issues for which a user does not have permissions for, then the user will get the typical Access Denied SharePoint error page when the user tries to view the task's linked issues page. So far, so good. Unfortunately, if the task is linked to multiple issues, some of which the user has permissions to view and some of which the user cannot view, the same Access Denied SharePoint error page will be shown. You would think that the user wouldn't get this error page and instead would get a list of just the items he/she has access to but unfortunately not. Hopefully, this will be fixed in the future by Microsoft.