19. June 2009 12:19 by Marko Simic

Super sexy jQuery hierarchical menu for nondevelopers

19
Jun/09
2

It all started when I start looking for menu library for project I am working on.
Since jQuery was my framework of choice I wanted to find either plugin or inline code written in jQuery.
Surprise one. Google returns absolutely nothing. LOL. How it can be?! I mean, that’s one of the most popular libraries written in JavaScript in last decade (and more). Next step was to check jQuery official plugin repository. There was much better situation and found 65(!) plugins in “menu” group.
Ho-ho-ho” I said. There must be something for me“, I continued (drama stops here).
Still I was surprised that none of it was indexed by Google (everything after 3rd page doesn’t exist ).
Disappointment again. As Bruce Springsteen would say:  “57 channels and nothin’ on”.
Well, it’s not complete truth. There are many nice drop down menus, but generally you need to be quite versed in jQuery to be able to use and understand it. Not that I am underestimate myself (nevah!), but, at that time, I realy didn’t want to fire up my gray cells
Then I said:”In the name of all off us who don’t want to use brains, who don’t care to learn jQuery, but still would like to have hierarchical drop-down menu, I’ll try to write one”.

Now you’ll say “Can’t believe you wrote so many words to say something totally unrelated to article subject”.
No,no,no,…introductory words contain the essence of my idea. on which I’ll base forthcoming work:

1. Must be easy to model and design
2. Should not require (any) skills in jQuery to be able to use it

All that end-user need to know is HTML and CSS. Even that knowledge can be limited on unsorted lists and few CSS attributes. If that is possible at all.
Let us understand from start. None of this would be possible to write down in 2 days, that jQuery is not so awesome and that I haven’t previously read “Sexy Drop Down Menu q/jQuery and CSS” tutorial. So, I am not (re)discovering the wheel, but improving existing.

Easy to design

As said before, all you need to know is little HTML.
Concept is based on unsolreted lists. By its nature they look like menus, and are organized hirearchically.
Let’s look at this chunk of code:

<a href="#">Sub Nav Link1</a>
	<li><a href="#">Sub Nav Link2</a></li>
	<li><a href="#">Sub Nav Link3</a></li>
	<li><a href="#">Sub Nav Link4</a></li>
	<li><a href="#">Sub Nav Link5</a></li>

What we see here is A (link) element on start and symbolize menu link. When user click on on it, submenu will open.
Next, we see Royal Highness “UL” – Unsorted List. This element is so popular these (web 2.0) days that internet would fall apart if browsers would suddenly stop supporting it :)
In our case, this will symbolize “popup menu” – group of menu items. It’s obvious that “LI” elements are “menu items” and “A” link actions.
To summarize: when you click on first A element (<a href=”#”>Sub-Menu…) new popup menu will open; like in all Windows GUI applications (and mac’s?).
Each A elements, nested inside LI element, is an action. For example “File -> New”. “New” is Action)

First level menu

What would you do if you want to nest one more submenu inside this one. For example: “File -> New -> Document Type -> Word”.
Simple. Just nest one more UL list. Like this:

<a href="#">Sub-Menu 1 »</a>
<ul>
	<li>
		<a class="ttl" href="#">Sub-Menu 2 »</a>
<ul>
	<li><a href="#">Sub Nav2 Link1</a></li>
	<li><a href="#">Sub Nav2 Link2</a></li>
	<li><a href="#">Sub Nav2 Link3</a></li>
	<li><a href="#">Sub Nav2 Link4</a></li>
	<li><a href="#">Sub Nav2 Link5</a></li>
</ul>
</li>
	<li><a href="#">Sub Nav Link2</a></li>
	<li><a href="#">Sub Nav Link3</a></li>
	<li><a href="#">Sub Nav Link4</a></li>
	<li><a href="#">Sub Nav Link5</a></li>
</ul>

With nested submenu

Voila! Now all together it looks like this:

<!-- Another menu branch starts here - -->
<ul class="ct_top">
	<li>
		<a href="#">Menu 2</a>
<ul class="ct_root">
	<li class="ct_submenu">
				<a class="ttl" href="#">Sub-Menu 1 »</a>
<ul>
	<li class="ct_submenu">
						<a class="ttl" href="#">Sub-Sub Menu »</a>
<ul>
	<li><a href="#">Sub Nav Link1</a></li>
	<li><a href="#">Sub Nav Link2</a></li>
	<li><a href="#">Sub Nav Link3</a></li>
	<li><a href="#">Sub Nav Link4</a></li>
	<li><a href="#">Sub Nav Link5</a></li>
</ul>
</li>
	<li><a href="#">Sub Nav Link1</a></li>
	<li><a href="#">Sub Nav Link2</a></li>
	<li><a href="#">Sub Nav Link3</a></li>
	<li><a href="#">Sub Nav Link4</a></li>
	<li><a href="#">Sub Nav Link5</a></li>
</ul>
</li>
	<li class="ct_submenu">
				<a class="ttl" href="#">Sub-Menu 2</a>
<ul>
	<li><a href="#">Sub Nav Link1</a></li>
</ul>
</li>
	<li>
				<a class="ttl" href="#">Root Link</a></li>
</ul>
</li>
</ul>

And if words are not enough, image should fill the gaps:

Marked most important spots with HTML tags

You’ll notice that LI elements have one unusual attribute – ctt. It’s created to make life easer for jQuery.
Well, we are not the only who need help
Therefore, the only non-standard thing to do, while modeling, is to follow these simple rules:

1. ctt=”submenu” attribute of LI element that holds submenu must be present . Number of such elements, per menu branch, is not limited.
2  ctt=”root” attribute of UL element, that initiates new menu, must be present . Only one such element can exists per menu branch.
3. “<ul ctt=”top”> <li></li></ul>” wrap/surrounds menu branch and must be present

There is not much to explain about CSS code, just don’t delete lines which are marked/commanted as important.
Those parts won’t effect or limit your creativity :)

OK. This is it. Download code, adjust design to you needs and use it freely.
Let’s see what our friend jQuery is doing.

jQuery

Unobtrusiveness in its full shine.
To keep away designers from JS, all event handling, methods and visual adjustments are going to be performed by jQuery code.
As usual we start with jQuery(document).ready

To avoid conflict with other JS frameworks which are in use, I prefer not to use $ symbol.

jQuery.noConflict(); //forget $ symbol

Because most of code is commented, I’ll just point on parts which I found crucial.

Recursion. Hierarchical menus theoretically can have unlimited (big enough) number of nested menus and spread its tentacles like octopus.
Typical way to discover how deep single branch goes, is by recursion. Method is looping through all items on the same level, assigning them adequate actions, and checking if any contains more nested or story end there. If method discover new nested branch it will (re)call it self and repeat all actions on it.  In our example this method is “assignActions”.

Initial method call

jQuery("ul[ctt='top'] li ul[ctt='root']").each(function(){
		assignActions(jQuery(this));
	}
);

..and then actual action and recursive call

//Assign actions to elements recursively
function assignActions(rootItem){
	jQuery("li[ctt='submenu'] a",rootItem).click(function(){
			//reposition
			pos = jQuery(this).position();
			wd = jQuery(this).width();
			lst = jQuery("~ ul",this);
			lst.css({ position: "absolute", width: "100%",top:pos.top,left: pos.left+wd})
 
			//assign hover
			jQuery("~ ul",this).hover(
				function(){},
				function(){
					jQuery(this).slideUp('slow');
				}
			);				
 
			lst.closeOthers();
			lst.slideDown("fast").show();
		}
	);
	// recursion
	jQuery("li[ctt='submenu']&gt;a&gt;ul[ctid]",rootItem).each(function(){
			assignActions(jQuery(this)); //Recursive call. Checks
		}
	);
}

2 more short notes (of educational cause). It’s quite well documented in jQuery official docs.

jQuery (custom) functions
jQuery Plugins/Authoring

// jQuery function. Close all elements but callee and its parents (branch)
jQuery.fn.closeOthers = function(){
	var refID = jQuery(this).attr("ctid");
	arr = jQuery.makeArray(jQuery(this).parents("ul[ctid]").attr("ctid"));
	arr[arr.length]=refID;
 
	jQuery("ul[ctt='top'] * &gt; li &gt; a ~ ul[ctid]:visible").each(function(){
		cid = jQuery(this).attr("ctid");
		if (jQuery.inArray(cid,arr)==-1){ //close all but me and my parents
			jQuery(this).hide();
		}
	});
};

and second at not least, there is a little handy utility for logging messages in Firebug console

jQuery.fn.log = function (msg) {
	if (window.console) {
		console.log("%s: %o", msg, this);
	}
	return this;
};

OK. This is definitely it.
It would be nice to pack this as plugin. That way,it would be even more transparent to nondevelopers. But hey ppl, I have a life too…so I leave it for some other day. Tomorrow? Noooooo

Get the code! ;)

Share and Enjoy:
  • Print
  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • LinkedIn
  • MisterWong.DE
  • Netvibes
  • Reddit
  • Slashdot
  • StumbleUpon
  • Technorati
  • Twitter
  • Yahoo! Bookmarks
  • LinkArena
  • Live
  • MySpace
  • Yahoo! Buzz
  • Yigg
  • blogmarks
  • Faves
  • FriendFeed
  • MisterWong
Comments (2) Trackbacks (0)
  1. underdeveloped
    19:41 on June 19th, 2009

    I think you missed your own objective, providing value to non developers. The idea of having to add classes to submenu makes no sense for ease of use, why not do it programmatically with jquery?

    No plugin, no API, no options? How does this help when there are multiple other jquery based menus that are easy to set up, and have those attributes.

    What about hover Intent compatibilty?

    Finally, adding non valid code with a bogus attribute “ctt” is just plain wrong!

  2. Marko Simic
    23:34 on June 19th, 2009

    Hi,

    I could agree about all objections. But, as I wrote at the begging of article and its very end, this is just one of many possible solutions and it’s unfinished. By writing this code, I wanted to present how jQuery can help in spearation of presentaion layer from business logic.
    Therefore, in that context, is HTML well formed or is it packed as plug-in or not, is not that relevant. Of course, it would be better if I haven’t used “bogus” attributes or code encapsulated as jQuery plug-in. But that would only make JS developers happier, not contribute to subject of analyse.

    Talking about “bogus” attritube. Idea behind this was to a) leave ID and CLASS attribute free to designers to play with by their own will and, at the same time, B) to provide additional info to jQ selector to locate element(s) faster.

    So, if I ever decide to further develop this code, I’ll have your words in mind.
    Thank you for your comment.

Leave a comment

No trackbacks yet.