jquery.treeview.js 7.9 KB

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