1. Plugin code review
    
    (function($) {
    	$.fn.menuTree = function(options) {
    ...
    
  2. Overview
    • Developed using jQuery version 1.4 ... Plugin page

    • Demo of the plugin behavior showing both lists and definition list... Demo

    • Event delegation for click event binding to tree/list element, instead of binding to each anchor

    • Tracer plugin added and featured with demo.

  3. Markup, before script executes
    
    <ul id="list1">
    	<li><a href="#">Menu - toggle animation</a>
    		<ul>
    			<li><a href="#">Top Parent - Tier 1</a>
    				<ul>
    					<li><a href="#">Child - Tier 2</a>
    						<ul>
    							<li><a href="http://pixelhandler.com">Pixelhandler</a> - Tier 3</li>
    							<li><a href="http://jquery.com">jQuery</a> - Tier 3</li>
    						</ul>
    					</li>
    				</ul>
    			</li>
    		</ul>
    	</li>
    </ul>
    
  4. Markup, after script executes
    
    <ul id="list1">
    	<li><a href="#" class="menuTree">Menu - toggle animation</a>
    		<ul class="collapsed">
    			<li><a href="#" class="menuTree">Top Parent - Tier 1</a>
    				<ul class="collapsed">
    					<li><a href="#" class="menuTree">Child - Tier 2</a>
    						<ul class="collapsed">
    							<li><a href="http://pixelhandler.com">Pixelhandler</a> - Tier 3</li>
    							<li><a href="http://jquery.com">jQuery</a> - Tier 3</li>
    						</ul>
    					</li>
    				</ul>
    			</li>
    		</ul>
    	</li>
    </ul>
    
  5. Besides <ul> and <ol> plugin also works with <dl> element
    
    <dl>
    	<dt><a href="#">Definition list - Term 1</a></dt>
    	<dd>Description not targeted,
    	    <em>uses CSS classes to collapse/expanded view</em>
    	</dd>
    	<dt><a href="#">Term 2</a></dt>
    	<dd>Description 2</dd>
    </dl>
    

    Choose list type to act on when applying $.fn.menuTree

  6. Style using classes added to DOM with plugin
    
    #myTree .menuTree:before {
    	content: "[+] ";
    }
    
    #myTree .expanded:before {
    	content: "[-] ";
    }
    
    #myTree .collapsed {
    	display: none;
    }
    

    (Leaving the visual to the designers)

  7. Setup your list with options, $('#list1').menuTree( ... );
    
    	$(document).ready(function() {
    
    		$('#list1').menuTree({
    			animation: true, 	// false
    			handler: 'toggle', 	// 'slideToggle'
    			hrefBegins: '#',	// root, use '/' or 'http'
    			listElement: 'ul',	// select ul, ol, or dd for hidden sub-menu(s)
    			speed: 'slow'		// 'fast'
    		});
    
    	});
    

    Source code at github.com

  8. init method that executes
    
    // setup tree behavior and bind on controller
    $.fn.menuTree.init = (function() {
    	$.fn.menuTree.mtTargets.each(function() {
    		var $localTarget = $(this);
    		// set state
    		$localTarget.data({ state: 'ready', responsive: true });
    		// set behavior up on all .menuTree anchors create with plugin
    		$localTarget.addClass('menuTree');
    		// hide the child elements to reveal later // $.fn.menuTree.
    		reveal($localTarget).toggleClass('collapsed');
    		// set Click event handler for targets 
    		// $localTarget.click(clickHandler); // =>  no event delegation
    		$.fn.menuTree.mtParent.click(clickHandler);
    		// bind the Controller to listen for state change on 
    		$.fn.menuTree.mtParent.bind('statechange',$.fn.menuTree.controller);
    	});
    });
    
    return $.fn.menuTree.init();
    
  9. ClickHandler for targeted anchors (part A)
    
    function clickHandler(event) {
    	var $target = $(event.target);
    	// if data value is not ready bail out
    	if (!$target.data('responsive')){
    		return;
    	}
    	$target.stop();
    	// choose your animation behavior based on options passed to plugin instance
    	if (!opts.animation) { // false uses CSS to handle effects
    		reveal($target).toggleClass('collapsed');
    		$target.toggleClass('expanded').data('state','ready').trigger('statechange');
    	}
    
  10. ClickHandler for targeted anchors (part B)
    
    		else { // true uses opts.handler to choose effects
    			$target.data('state','transition').trigger('statechange');
    			switch(opts.handler) {
    				case "slideToggle":
    					reveal($target).slideToggle( opts.speed, function() {
    						$(this).prev('.menuTree').toggleClass('expanded').data('state','ready').trigger('statechange');
    					}).toggleClass('collapsed');
    					break;
    				case "toggle":
    					reveal($target).toggle(opts.speed, function() {
    						$(this).prev('.menuTree').toggleClass('expanded').data('state','ready').trigger('statechange');
    					}).toggleClass('collapsed');
    					break;
    				default: // css only, but if called with true we should do something
    			}
    		}
    		event.preventDefault();
    	}
    
  11. Controller handles responsiveness
    
    	$.fn.menuTree.controller = function(event) { 
    		var $target = $(event.target); // manage link state on local target
    		if ($target.data('state') != 'ready'){
    			$target.data('responsive',false);
    		} else {
    			$target.data('responsive',true); 
    			// may need to collapse children
    			if ($target.next(opts.listElement).find('.expanded').length > 0) {
    				$target.next(opts.listElement).find('.expanded').each(function() {
    					$(this).removeClass('expanded').next(opts.listElement).hide().addClass('collapsed');
    				});
    			}
    		}
    	};
    

    Depending on duration of animation the target anchor may not be responsive

  12. $('#list1').menuTree( ... )
    
    // tree behavior only operates on anchor elements in the 
    // list that begin with a hash '#' unless options called for
    $.fn.menuTree.mtParent = $(this);
    
    // targeted links
    $.fn.menuTree.mtTargets = $.fn.menuTree.mtParent.find("a[href^='"+opts.hrefBegins+"']");
    
    • During init() list was bound with clickHander and controller methods
    • Target anchors are contained in children of the lists, <li> or <dd>
  13. Method to set list type also helps to find local target during event delegation
    
    function reveal(element) {
    	var $reveal = $(element);
    	// select targets to reveal based on options we choose what list element to target default is 'ul'
    	switch(opts.listElement) {
    		case "dd": 
    			$reveal.mtReveal = $reveal.parent().next(opts.listElement);
    			break;
    		case "ol":
    			$reveal.mtReveal = $reveal.next(opts.listElement);
    			break;
    		default: 
    			$reveal.mtReveal = $reveal.next(opts.listElement);
    	}
    	return $reveal.mtReveal; 
    }
    

    When parent list is clicked the event.target is referred to to find collapsed element to reveal