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