/*
* Treeview 1.2 - jQuery plugin to hide and show branches of a tree
*
* Copyright (c) 2006 Jörn Zaefferer, Myles Angell
*
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
*
* Revision: $Id$
*
*/
/**
* Takes an unordered list and makes all branches collapsable.
*
* The "treeview" class is added if not already present.
*
* To hide branches on first display, mark their li elements with
* the class "closed". If the "collapsed" option is used, mark intially open
* branches with class "open".
*
* @example .treeview, .treeview ul {
* padding: 0;
* margin: 0;
* list-style: none;
* }
*
* .treeview li {
* margin: 0;
* padding: 4px 0 3px 20px;
* }
*
* .treeview li { background: url(images/tv-item.gif) 0 0 no-repeat; }
* .treeview .collapsable { background-image: url(images/tv-collapsable.gif); }
* .treeview .expandable { background-image: url(images/tv-expandable.gif); }
* .treeview .last { background-image: url(images/tv-item-last.gif); }
* .treeview .lastCollapsable { background-image: url(images/tv-collapsable-last.gif); }
* .treeview .lastExpandable { background-image: url(images/tv-expandable-last.gif); }
* @desc The following styles are necessary in your stylesheet. There is are alternative sets of images available.
*
* @example $("ul").Treeview();
* @before
* - Item 1
*
*
* - Item 2 (starts closed)
*
* - Item 2.1
*
* - Item 2.1.1
* - Item 2.1.2
*
*
* - Item 2.2
*
*
* - Item 3
*
* @desc Basic usage example
*
* @example $("ul").Treeview({ speed: "fast", collapsed: true});
* @before
* - Item 1 (starts open)
*
*
* - Item 2
*
*
*
* @desc Create a treeview that starts collapsed. Toggling branches is animated.
*
* @example $("ul").Treeview({ control: #treecontrol });
* @before
* @desc Creates a treeview that can be controlled with a few links.
* Very likely to be changed/improved in future versions.
*
* @param Map options Optional settings to configure treeview
* @option String|Number speed Speed of animation, see animate() for details. Default: none, no animation
* @option Boolean collapsed Start with all branches collapsed. Default: none, all expanded
* @option control Container for a treecontrol, see last example.
* @option Boolean unique Set to allow only one branch on one level to be open
* (closing siblings which opening). Default: none
* @option Function toggle Callback when toggling a branch.
* Arguments: "this" refers to the UL that was shown or hidden.
* Works only with speed option set (set speed: 1 to enable callback without animations).
* Default: none
* @option Boolean|Object store When set, stores the tree-state in a cookie when leaving/reloading the page,
* and restoring that state when loading the page. By default, no state is stored. Only one tree-per-page can be stored.
* When specifying the option as a boolean-true, the default setting for cookie-storage is used,
* saving the state for the browser session. To set a different expiration, set the option to an
* object with a "expiration" property. Refer to the cookie plugin for details about
* possible values of that object.
* @type jQuery
* @name Treeview
* @cat Plugins/Treeview
*/
(function($) {
// classes used by the plugin
// need to be styled via external stylesheet, see first example
var CLASSES = {
open: "open",
closed: "closed",
expandable: "expandable",
collapsable: "collapsable",
lastCollapsable: "lastCollapsable",
lastExpandable: "lastExpandable",
last: "last",
hitarea: "hitarea"
};
// styles for hitareas
var hitareaCSS = {
height: 15,
width: 15,
marginLeft: "-15px",
"float": "left",
cursor: "pointer"
};
// ie specific styles for hitareas
if( $.browser.msie )
$.extend( hitareaCSS, {
background: "#fff",
filter: "alpha(opacity=0)",
display: "inline"
});
$.extend($.fn, {
swapClass: function(c1, c2) {
return this.each(function() {
var $this = $(this);
if ( $.className.has(this, c1) )
$this.removeClass(c1).addClass(c2);
else if ( $.className.has(this, c2) )
$this.removeClass(c2).addClass(c1);
});
},
replaceclass: function(c1, c2) {
return this.each(function() {
var $this = $(this);
if ( $.className.has(this, c1) )
$this.removeClass(c1).addClass(c2);
});
},
Treeview: function(settings) {
// currently no defaults necessary, all implicit
settings = $.extend({}, settings);
// factory for treecontroller
function treeController(tree, control) {
// factory for click handlers
function handler(filter) {
return function() {
// reuse toggle event handler, applying the elements to toggle
// start searching for all hitareas
toggler.apply( $("div." + CLASSES.hitarea, tree).filter(function() {
// for plain toggle, no filter is provided, otherwise we need to check the parent element
return filter ? $(this).parent("." + filter).length : true;
}) );
return false;
}
}
// click on first element to collapse tree
$(":eq(0)", control).click( handler(CLASSES.collapsable) );
// click on second to expand tree
$(":eq(1)", control).click( handler(CLASSES.expandable) );
// click on third to toggle tree
$(":eq(2)", control).click( handler() );
}
// handle toggle event
function toggler() {
// this refers to hitareas, we need to find the parent lis first
$( this ).parent()
// swap classes
.swapClass( CLASSES.collapsable, CLASSES.expandable )
.swapClass( CLASSES.lastCollapsable, CLASSES.lastExpandable )
// find child lists
.find( ">ul" )
// toggle them
.toggle( settings.speed, settings.toggle );
if ( settings.unique ) {
$( this ).parent()
.siblings()
.replaceclass( CLASSES.collapsable, CLASSES.expandable )
.replaceclass( CLASSES.lastCollapsable, CLASSES.lastExpandable )
.find( ">ul" )
.hide( settings.speed, settings.toggle );
}
}
// add treeview class to activate styles
this.addClass("treeview");
// mark last tree items
$("li:last-child", this).addClass(CLASSES.last);
// collapse whole tree, or only those marked as closed, anyway except those marked as open
$( (settings.collapsed ? "li" : "li." + CLASSES.closed) + ":not(." + CLASSES.open + ") > ul", this).hide();
// find all tree items with child lists
var branches = $("li[>ul]", this);
function serialize() {
var data = [];
branches.each(function(i, e) {
data[i] = settings.collapsed
? $(e).is("[>ul:visible]")
? !$(e).is("." + CLASSES.open)
? 1
: 0
: $(e).is("." + CLASSES.open)
? 1
: 0
: $(e).is("[>ul:hidden]")
? !$(e).is("." + CLASSES.closed)
? 1
: 0
: $(e).is("." + CLASSES.closed)
? 1
: 0;
});
$.cookie("treestorage", data.join(""), settings.store.expiration );
}
function deserialize() {
var stored = $.cookie("treestorage");
if ( stored ) {
var data = stored.split("");
branches.each(function(i, e) {
if( parseInt(data[i]) ) {
$(e).find(">ul").toggle();
}
});
}
}
if (settings.store) {
deserialize();
$(window).unload(serialize);
}
// handle closed ones first
branches.filter("[>ul:hidden]")
.addClass(CLASSES.expandable)
.swapClass(CLASSES.last, CLASSES.lastExpandable);
// handle open ones
branches.not("[>ul:hidden]")
.addClass(CLASSES.collapsable)
.swapClass(CLASSES.last, CLASSES.lastCollapsable);
// append hitarea
branches.prepend("")
// find hitarea
.find("div." + CLASSES.hitarea)
// apply styles to hitarea
.css(hitareaCSS)
// apply click event to hitarea
.click( toggler );
// if control option is set, create the treecontroller
if ( settings.control )
treeController(this, settings.control);
return this;
}
});
})(jQuery);