1,711 articles and 13,033 comments as of Thursday, September 30th, 2010

Wednesday, May 13, 2009

JQuery for Everyone: Accordion Left-nav with Cookies Speed Test

We had a great run of Superstar! workshops this year. I enjoyed them all and learned a lot from our users. One of the more popular solutions, based on Accordion Left Nav and featured in Tony’s Deployment article, has only one improvement since the original article.

A few months ago, I added the ability to save a cookie that determined which navigation sections should be open when the page loads. As Tony pointed out, that works great going from page to page when deployed across an entire site.

However, I can’t leave well enough alone. I want to see if I can make it just a little better before I publish again.

The first thing I need to do is get a baseline for the script’s speed. Firebug has a timer method, console.time(’name of timer’); and console.timeEnd(’name of timer’);. It turns in a consistent 53ms.

With this script, as with many jQuery solutions, everything is packed into an anonymous function that runs on the document.ready event.

$(function(){
	console.time('timer');
	//document ready code goes here
	console.timeEnd('timer');
});
//53ms

The order of the instructions as well as which selectors and DOM-walking strategy you employ makes a huge difference. Experience will help but good old fashioned tenacity and testing can’t be beat. Here’s what I started with:

$(function(){
	console.time('timer'); 

	//find menu rows in the left nav
	//filter the array of rows for headers
	//set variables for open and closed states
	//close all rows
	//set the click event for headers
	//iterate over the headers and check for a cookie
		//fire the click event for any headers with cookies

	console.timeEnd('timer');
});

I added .timer() calls around each of the sections to get a picture of which parts take the longest. The first thing I noticed is that my goal (to hide) comes too late. I should hide the rows as soon as I identify them.

Next, I started trying to optimize my selectors to make the process faster. One really interesting experiment lead me to this:

$(function(){
	console.time('menuRows');
	var menuRows = $("table#zz2_QuickLaunchMenu tr[id]");
	console.timeEnd('menuRows');
});
//23ms
$(function(){
	console.time('menuRows');
	var menuRows = $("table#zz2_QuickLaunchMenu > tbody > tr[id]");
	console.timeEnd('menuRows');
});
//12ms (faster than baseline)
$(function(){
	console.time('menuHd');
	var menu = $("#LeftNavigationAreaCell");
	var menuHd = $("table.ms-navSubMenu1 > tbody > tr[id]", menu);
	console.timeEnd('menuHd');
});
//3ms (WOW!)

That gave me the confidence to improve this script top to bottom.

Click “Read more…” to see the new version, enjoy!

<script type="text/javascript">
if(typeof jQuery=='undefined'){
	var jQPath="http://ajax.googleapis.com/ajax/libs/jquery/1.3.1/";
	document.write('<script src="',jQPath,'jquery.min.js" type="text/javascript"><\/script>');
}
</script>
<script type="text/javascript">
/*
 * Accordion Quicklaunch with Cookies
 * Copyright (c) 2009 Paul Grenier (endusersharepoint.com)
 * Licensed under the MIT (MIT-LICENSE.txt)
 */

$(function(){
	var menu = $("#LeftNavigationAreaCell"),
		menuHd = $("table.ms-navSubMenu1 > tbody > tr[id]", menu),
		submenus = menuHd.next("TR:not([id])");
	submenus.hide();
	var menuHd = submenus.prev(),
		closedImg = "/_layouts/images/PLUS.gif",
		openedImg = "/_layouts/images/MINUS.gif",
		cssInit = {
			"background-image":"url('"+closedImg+"')",
			"background-repeat":"no-repeat",
			"background-position":"100% 50%",
			"cursor": "pointer"
		},
		cssClosed = {"background-image":"url('"+closedImg+"')"},
		cssOpen = {"background-image":"url('"+openedImg+"')"};
    menuHd.find("td:last").css(cssInit);
    menuHd.bind("click", function(event) {
		if (event.target.tagName=="TD"){
			var styleElm = $(this).find("td:last");
			var nextTR = $(this).next("tr:not([id])");
			if (nextTR.is(':visible')) {
				nextTR.hide();
				styleElm.css(cssClosed);
				SetCookie(this.id, "", "/");
			} else {
				nextTR.show();
				styleElm.css(cssOpen);
				SetCookie(this.id, "open", "/");
			}
		}
    });
 	$.each(menuHd,function(i,e){
		var g = GetCookie(e.id);
		if (g && g == "open") {
			$(e).find("td:first").click();
		}
	});
});
</script>

Paul Grenier

View all entries in this series: PaulGrenier-JQuery for Everyone»
Entries in this series:
  1. JQuery for Everyone: Accordion Left Nav
  2. JQuery for Everyone: Print (Any) Web Part
  3. JQuery for Everyone: HTML Calculated Column
  4. JQuery for Everyone: Dressing-up Links Pt1
  5. JQuery for Everyone: Dressing-up Links Pt2
  6. JQuery for Everyone: Dressing-up Links Pt3
  7. JQuery for Everyone: Cleaning Windows Pt1
  8. JQuery for Everyone: Cleaning Windows Pt2
  9. JQuery for Everyone: Fixing the Gantt View
  10. JQuery for Everyone: Dynamically Sizing Excel Web Parts
  11. JQuery for Everyone: Manually Resizing Web Parts
  12. JQuery for Everyone: Total Calculated Columns
  13. JQuery for Everyone: Total of Time Differences
  14. JQuery for Everyone: Fixing Configured Web Part Height
  15. JQuery for Everyone: Expand/Collapse All Groups
  16. JQuery for Everyone: Preview Pane for Multiple Lists
  17. JQuery for Everyone: Preview Pane for Calendar View
  18. JQuery for Everyone: Degrading Dynamic Script Loader
  19. JQuery for Everyone: Force Checkout
  20. JQuery for Everyone: Replacing [Today]
  21. JQuery for Everyone: Whether They Want It Or Not
  22. JQuery for Everyone: Linking the Attachment Icon
  23. JQuery for Everyone: Aspect-Oriented Programming with jQuery
  24. JQuery for Everyone: AOP in Action - loadTip Gone Wild
  25. JQuery for Everyone: Wiki Outbound Links
  26. JQuery for Everyone: Collapse Text in List View
  27. JQuery for Everyone: AOP in Action - Clone List Header
  28. JQuery for Everyone: $.grep and calcHTML Revisited
  29. JQuery for Everyone: Evolution of the Preview
  30. JQuery for Everyone: Create a Client-Side Object Model
  31. JQuery for Everyone: Print (Any) Web Part(s) Plugin
  32. JQuery for Everyone: Minimal AOP and Elegant Modularity
  33. JQuery for Everyone: Cookies and Plugins
  34. JQuery for Everyone: Live Events vs. AOP
  35. JQuery for Everyone: Live Preview Pane
  36. JQuery for Everyone: Pre-populate Form Fields
  37. JQuery for Everyone: Get XML List Data with OWSSVR.DLL (RPC)
  38. Use Firebug in IE
  39. JQuery for Everyone: Extending OWS API for Calculated Columns
  40. JQuery for Everyone: Accordion Left-nav with Cookies Speed Test
  41. JQuery for Everyone: Email a List of People with OWS
  42. JQuery for Everyone: Faster than Document.Ready
  43. jQuery for Everyone: Collapse or Prepopulate Form Fields
  44. jQuery for Everyone: Hourly Summary Web Part
  45. jQuery for Everyone: "Read More..." On a Blog Site
  46. jQuery for Everyone: Slick Speed Test
  47. jQuery for Everyone: The SharePoint Game Changer
  48. JQuery For Everyone: Live LoadTip
 

Please Join the Discussion

15 Responses to “JQuery for Everyone: Accordion Left-nav with Cookies Speed Test”
  1. David says:

    Everything works except the last line after reading the cookie doesn’t fire:

    $(e).find(”td:first”).click();

    Is that correct?

  2. AutoSponge says:

    What exactly does not work? Because it will only fire the click event for a valid cookie with the value open. All other sub-menu sections will default to closed.

  3. David says:

    I do get a hit on the value (”open”) in the conditional. So it is setting and reading properly from the cookie. It just doesn’t fire the next line…find a tag and force the click on open. I’m new to jquery so I don’t know how to force a known tag to click to troubleshoot. Other than that, everything works. Is there an alternative to the fin method, or maybe some other reason it doesn’t get a hit?

  4. AutoSponge says:

    @David,

    The $.each method passes the element and iterator (i,e). If you customized the nav or .master, you may need to adjust what gets the click.

    If clicking manually works, add a console.log(event); line in the .bind() area. When it fires, click the event in Firebug and go to the DOM panel.

    Drill down the DOM panel to find the target. Look at that element (if it’s working it should be a TD).

    Now compare that element to the position of the elements returned by console.log(menuHd); (anywhere after it’s set). The event.target should be the first TD inside a menuHd element for the last section to work.

    If not, say if you used a custom theme or something, you may need to change TD:first to something else.

  5. Vinny says:

    Jan is there a reason not to put the img paths directly in the CSS statements? Seems like it would be faster and the only reason to have the variable is to facilitate changing the image, but with it only appearing once or twice in the code, not sure the benefit is worth it.

    cssInit = {
    "background-image":"url('/_layouts/images/PLUS.gif')",
    "background-repeat":"no-repeat",
    "background-position":"100% 50%",
    "cursor": "pointer"
    },
    cssClosed = {"background-image":"url('/_layouts/images/PLUS.gif')"},
    cssOpen = {"background-image":"url('/_layouts/images/MINUS.gif')"};
  6. AutoSponge says:

    @Vinny,

    Speed is a funny thing. Sometimes, using a separate SCRIPT tag slows things down because it blocks other elements during loading. If you have a custom CSS already being loaded by the .master, by all means, add the images to a pair of classes and toggle the classes.

  7. Vinny says:

    With jQuery 1.3.2 you need to change the following:

    if (nextTR.is(’:hidden’)) {
    nextTR.show();
    styleElm.css(cssOpen);
    SetCookie(this.id, “open”, “/”);
    } else {
    nextTR.hide();
    styleElm.css(cssClosed);
    SetCookie(this.id, “”, “/”);
    }

    • Jim H says:

      I tried the change for jQuery 1.3.2 (version I am using) and it does not work – maybe there need to be more specific instructions?. Prior to making the change, the menus show colapsed, then when I click on a header they open up for a second and then go back to closed. Does anyone have a solution to make this work with version 1.3.2?

  8. CRISTY says:

    Does anybody tried to add ‘help balloons / tool tips / dialog balloons’ or whatever that can be called…. when someone move the pointer over the nav menu and get the a box with comments?

  9. Mark says:

    I can the accordeon to work (it expands and collapses when I click on the heading next to the heading text), but the pictures for opening (+) and closing (-) are not displayed. Any thoughts how to solve this? My knowledge of jQuery does not go much beyond copy/paste, so please, be gentle.

  10. AutoSponge says:

    @mark,

    The urls in this script point to default images installed with WSS. If you don’t have them, moved them, or changed their location, simply update the url or fix the permission issue.

  11. Mark says:

    The images are still there and in the browser I can open them directly with the link from the code. If I try other images they also won’t show (the functionality keeps working). Other ideas?

  12. CRISTY says:

    I replaced the jquery version to 4-1 and now is not working …..

  13. husso says:

    hi paul,
    because i am new to sp2010 i do not know where exactly to put this javascript code
    Can you tell us what steps to follow to get this Accordion Left-nav working?

    Thanks
    husso

Trackbacks

Check out what others are saying about this post...
  1. [...] JQuery for Everyone: Accordion Left-nav with Cookies Speed Test [...]




Notify me of comments to this article:


Speak and you will be heard.

We check comments hourly.
If you want a pic to show with your comment, go get a gravatar!