jquery.treeview.js 8.0 KB

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