master
Raw Download raw file
  1/*
  2 * Treeview 1.4.1 - jQuery plugin to hide and show branches of a tree
  3 * 
  4 * http://bassistance.de/jquery-plugins/jquery-plugin-treeview/
  5 * http://docs.jquery.com/Plugins/Treeview
  6 *
  7 * Copyright (c) 2007 Jörn Zaefferer
  8 *
  9 * Dual licensed under the MIT and GPL licenses:
 10 *   http://www.opensource.org/licenses/mit-license.php
 11 *   http://www.gnu.org/licenses/gpl.html
 12 *
 13 * Revision: $Id: jquery.treeview.js 5759 2008-07-01 07:50:28Z joern.zaefferer $
 14 *
 15 */
 16
 17;(function($) {
 18
 19	// TODO rewrite as a widget, removing all the extra plugins
 20	$.extend($.fn, {
 21		swapClass: function(c1, c2) {
 22			var c1Elements = this.filter('.' + c1);
 23			this.filter('.' + c2).removeClass(c2).addClass(c1);
 24			c1Elements.removeClass(c1).addClass(c2);
 25			return this;
 26		},
 27		replaceClass: function(c1, c2) {
 28			return this.filter('.' + c1).removeClass(c1).addClass(c2).end();
 29		},
 30		hoverClass: function(className) {
 31			className = className || "hover";
 32			return this.hover(function() {
 33				$(this).addClass(className);
 34			}, function() {
 35				$(this).removeClass(className);
 36			});
 37		},
 38		heightToggle: function(animated, callback) {
 39			animated ?
 40				this.animate({ height: "toggle" }, animated, callback) :
 41				this.each(function(){
 42					jQuery(this)[ jQuery(this).is(":hidden") ? "show" : "hide" ]();
 43					if(callback)
 44						callback.apply(this, arguments);
 45				});
 46		},
 47		heightHide: function(animated, callback) {
 48			if (animated) {
 49				this.animate({ height: "hide" }, animated, callback);
 50			} else {
 51				this.hide();
 52				if (callback)
 53					this.each(callback);				
 54			}
 55		},
 56		prepareBranches: function(settings) {
 57			if (!settings.prerendered) {
 58				// mark last tree items
 59				this.filter(":last-child:not(ul)").addClass(CLASSES.last);
 60				// collapse whole tree, or only those marked as closed, anyway except those marked as open
 61				this.filter((settings.collapsed ? "" : "." + CLASSES.closed) + ":not(." + CLASSES.open + ")").find(">ul").hide();
 62			}
 63			// return all items with sublists
 64			return this.filter(":has(>ul)");
 65		},
 66		applyClasses: function(settings, toggler) {
 67			// TODO use event delegation
 68			this.filter(":has(>ul):not(:has(>a))").find(">span").unbind("click.treeview").bind("click.treeview", function(event) {
 69				// don't handle click events on children, eg. checkboxes
 70				if ( this == event.target )
 71					toggler.apply($(this).next());
 72			}).add( $("a", this) ).hoverClass();
 73			
 74			if (!settings.prerendered) {
 75				// handle closed ones first
 76				this.filter(":has(>ul:hidden)")
 77						.addClass(CLASSES.expandable)
 78						.replaceClass(CLASSES.last, CLASSES.lastExpandable);
 79						
 80				// handle open ones
 81				this.not(":has(>ul:hidden)")
 82						.addClass(CLASSES.collapsable)
 83						.replaceClass(CLASSES.last, CLASSES.lastCollapsable);
 84						
 85	            // create hitarea if not present
 86				var hitarea = this.find("div." + CLASSES.hitarea);
 87				if (!hitarea.length)
 88					hitarea = this.prepend("<div class=\"" + CLASSES.hitarea + "\"/>").find("div." + CLASSES.hitarea);
 89				hitarea.removeClass().addClass(CLASSES.hitarea).each(function() {
 90					var classes = "";
 91					$.each($(this).parent().attr("class").split(" "), function() {
 92						classes += this + "-hitarea ";
 93					});
 94					$(this).addClass( classes );
 95				})
 96			}
 97			
 98			// apply event to hitarea
 99			this.find("div." + CLASSES.hitarea).click( toggler );
100		},
101		treeview: function(settings) {
102			
103			settings = $.extend({
104				cookieId: "treeview"
105			}, settings);
106			
107			if ( settings.toggle ) {
108				var callback = settings.toggle;
109				settings.toggle = function() {
110					return callback.apply($(this).parent()[0], arguments);
111				};
112			}
113		
114			// factory for treecontroller
115			function treeController(tree, control) {
116				// factory for click handlers
117				function handler(filter) {
118					return function() {
119						// reuse toggle event handler, applying the elements to toggle
120						// start searching for all hitareas
121						toggler.apply( $("div." + CLASSES.hitarea, tree).filter(function() {
122							// for plain toggle, no filter is provided, otherwise we need to check the parent element
123							return filter ? $(this).parent("." + filter).length : true;
124						}) );
125						return false;
126					};
127				}
128				// click on first element to collapse tree
129				$("a:eq(0)", control).click( handler(CLASSES.collapsable) );
130				// click on second to expand tree
131				$("a:eq(1)", control).click( handler(CLASSES.expandable) );
132				// click on third to toggle tree
133				$("a:eq(2)", control).click( handler() ); 
134			}
135		
136			// handle toggle event
137			function toggler() {
138				$(this)
139					.parent()
140					// swap classes for hitarea
141					.find(">.hitarea")
142						.swapClass( CLASSES.collapsableHitarea, CLASSES.expandableHitarea )
143						.swapClass( CLASSES.lastCollapsableHitarea, CLASSES.lastExpandableHitarea )
144					.end()
145					// swap classes for parent li
146					.swapClass( CLASSES.collapsable, CLASSES.expandable )
147					.swapClass( CLASSES.lastCollapsable, CLASSES.lastExpandable )
148					// find child lists
149					.find( ">ul" )
150					// toggle them
151					.heightToggle( settings.animated, settings.toggle );
152				if ( settings.unique ) {
153					$(this).parent()
154						.siblings()
155						// swap classes for hitarea
156						.find(">.hitarea")
157							.replaceClass( CLASSES.collapsableHitarea, CLASSES.expandableHitarea )
158							.replaceClass( CLASSES.lastCollapsableHitarea, CLASSES.lastExpandableHitarea )
159						.end()
160						.replaceClass( CLASSES.collapsable, CLASSES.expandable )
161						.replaceClass( CLASSES.lastCollapsable, CLASSES.lastExpandable )
162						.find( ">ul" )
163						.heightHide( settings.animated, settings.toggle );
164				}
165			}
166			this.data("toggler", toggler);
167			
168			function serialize() {
169				function binary(arg) {
170					return arg ? 1 : 0;
171				}
172				var data = [];
173				branches.each(function(i, e) {
174					data[i] = $(e).is(":has(>ul:visible)") ? 1 : 0;
175				});
176				$.cookie(settings.cookieId, data.join(""), settings.cookieOptions );
177			}
178			
179			function deserialize() {
180				var stored = $.cookie(settings.cookieId);
181				if ( stored ) {
182					var data = stored.split("");
183					branches.each(function(i, e) {
184						$(e).find(">ul")[ parseInt(data[i]) ? "show" : "hide" ]();
185					});
186				}
187			}
188			
189			// add treeview class to activate styles
190			this.addClass("treeview");
191			
192			// prepare branches and find all tree items with child lists
193			var branches = this.find("li").prepareBranches(settings);
194			
195			switch(settings.persist) {
196			case "cookie":
197				var toggleCallback = settings.toggle;
198				settings.toggle = function() {
199					serialize();
200					if (toggleCallback) {
201						toggleCallback.apply(this, arguments);
202					}
203				};
204				deserialize();
205				break;
206			case "location":
207				var current = this.find("a").filter(function() {
208					return this.href.toLowerCase() == location.href.toLowerCase();
209				});
210				if ( current.length ) {
211					// TODO update the open/closed classes
212					var items = current.addClass("selected").parents("ul, li").add( current.next() ).show();
213					if (settings.prerendered) {
214						// if prerendered is on, replicate the basic class swapping
215						items.filter("li")
216							.swapClass( CLASSES.collapsable, CLASSES.expandable )
217							.swapClass( CLASSES.lastCollapsable, CLASSES.lastExpandable )
218							.find(">.hitarea")
219								.swapClass( CLASSES.collapsableHitarea, CLASSES.expandableHitarea )
220								.swapClass( CLASSES.lastCollapsableHitarea, CLASSES.lastExpandableHitarea );
221					}
222				}
223				break;
224			}
225			
226			branches.applyClasses(settings, toggler);
227				
228			// if control option is set, create the treecontroller and show it
229			if ( settings.control ) {
230				treeController(this, settings.control);
231				$(settings.control).show();
232			}
233			
234			return this;
235		}
236	});
237	
238	// classes used by the plugin
239	// need to be styled via external stylesheet, see first example
240	$.treeview = {};
241	var CLASSES = ($.treeview.classes = {
242		open: "open",
243		closed: "closed",
244		expandable: "expandable",
245		expandableHitarea: "expandable-hitarea",
246		lastExpandableHitarea: "lastExpandable-hitarea",
247		collapsable: "collapsable",
248		collapsableHitarea: "collapsable-hitarea",
249		lastCollapsableHitarea: "lastCollapsable-hitarea",
250		lastCollapsable: "lastCollapsable",
251		lastExpandable: "lastExpandable",
252		last: "last",
253		hitarea: "hitarea"
254	});
255	
256})(jQuery);