/*
 * Treeview 1.4 - jQuery plugin to hide and show branches of a tree
 * 
 * http://bassistance.de/jquery-plugins/jquery-plugin-treeview/
 * http://docs.jquery.com/Plugins/Treeview
 *
 * Copyright (c) 2007 Jörn Zaefferer
 *
 * 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: jquery.treeview.js 4684 2008-02-07 19:08:06Z joern.zaefferer $
 *
 */

;
(function($) {

   $.extend($.fn, {
      swapClass: function(c1, c2) {
         var c1Elements = this.filter('.' + c1);
         this.filter('.' + c2).removeClass(c2).addClass(c1);
         c1Elements.removeClass(c1).addClass(c2);
         return this;
      },
      replaceClass: function(c1, c2) {
         return this.filter('.' + c1).removeClass(c1).addClass(c2).end();
      },
      hoverClass: function(className) {
         className = className || "hover";
         return this.hover(function() {
            $(this).addClass(className);
         }, function() {
            $(this).removeClass(className);
         });
      },
      heightToggle: function(animated, callback) {
         animated ?
         this.animate({
            height: "toggle"
         }, animated, callback) :
         this.each(function(){
            jQuery(this)[ jQuery(this).is(":hidden") ? "show" : "hide" ]();
            if(callback)
               callback.apply(this, arguments);
         });
      },
      heightHide: function(animated, callback) {
         if (animated) {
            this.animate({
               height: "hide"
            }, animated, callback);
         } else {
            this.hide();
            if (callback)
               this.each(callback);
         }
      },
      prepareBranches: function(settings) {
         if (!settings.prerendered) {
            // mark last tree items
            this.filter(":last-child:not(ul)").addClass(CLASSES.last);
            // collapse whole tree, or only those marked as closed, anyway except those marked as open
            this.filter((settings.collapsed ? "" : "." + CLASSES.closed) + ":not(." + CLASSES.open + ")").find(">ul").hide();
         }
         // return all items with sublists
         return this.filter(":has(>ul)");
      },
      applyClasses: function(settings, toggler) {
         //			this.filter(":has(>ul):not(:has(>a))").find(">span").click(function(event) {
         //				toggler.apply($(this).next());
         //			}).add( $("a", this) ).hoverClass();
			
         if (!settings.prerendered) {
            // handle closed ones first
            //				this.filter(":has(>ul:hidden)")
            //						.addClass(CLASSES.expandable)
            //						.replaceClass(CLASSES.last, CLASSES.lastExpandable);
						
            // handle open ones
            //				this.not(":has(>ul:hidden)")
            //						.addClass(CLASSES.collapsable)
            //						.replaceClass(CLASSES.last, CLASSES.lastCollapsable);

            this.filter("li:has(>ul)").not('.open')
               .addClass(CLASSES.expandable)
               .replaceClass(CLASSES.last, CLASSES.lastExpandable);

            this.filter('.open')
               .addClass(CLASSES.collapsable)
               .replaceClass(CLASSES.last, CLASSES.lastCollapsable);

            // create hitarea
            this.prepend("<div class=\"" + CLASSES.hitarea + "\"/>").find("div." + CLASSES.hitarea).each(function() {
               var classes = "";
               $.each($(this).parent().attr("class").split(" "), function() {
                  classes += this + "-hitarea ";
               });
               $(this).addClass( classes );
            });
         }
			
         // apply event to hitarea
         this.find("div." + CLASSES.hitarea).click( toggler );
      },
      treeview: function(settings) {
			
         settings = $.extend({
            cookieId: "treeview"
         }, settings);
			
         if (settings.add) {
            return this.trigger("add", [settings.add]);
         }

         if (settings.remove) {
            return this.trigger("remove", [settings.remove])
         }
			
         if ( settings.toggle ) {
            var callback = settings.toggle;
            settings.toggle = function() {
               return callback.apply($(this).parent()[0], arguments);
            };
         }
		
         // 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
            $("a:eq(0)", control).click( handler(CLASSES.collapsable) );
            // click on second to expand tree
            $("a:eq(1)", control).click( handler(CLASSES.expandable) );
            // click on third to toggle tree
            $("a:eq(2)", control).click( handler() );
         }
		
         // handle toggle event
         function toggler() {
            $(this)
            .parent()
            // swap classes for hitarea
            .find(">.hitarea")
            .swapClass( CLASSES.collapsableHitarea, CLASSES.expandableHitarea )
            .swapClass( CLASSES.lastCollapsableHitarea, CLASSES.lastExpandableHitarea )
            .end()
            // swap classes for parent li
            .swapClass( CLASSES.collapsable, CLASSES.expandable )
            .swapClass( CLASSES.lastCollapsable, CLASSES.lastExpandable )
            // find child lists
            .find( ">ul" )
            // toggle them
            .heightToggle( settings.animated, settings.toggle );
            if ( settings.unique ) {
               $(this).parent()
               .siblings()
               // swap classes for hitarea
               .find(">.hitarea")
               .replaceClass( CLASSES.collapsableHitarea, CLASSES.expandableHitarea )
               .replaceClass( CLASSES.lastCollapsableHitarea, CLASSES.lastExpandableHitarea )
               .end()
               .replaceClass( CLASSES.collapsable, CLASSES.expandable )
               .replaceClass( CLASSES.lastCollapsable, CLASSES.lastExpandable )
               .find( ">ul" )
               .heightHide( settings.animated, settings.toggle );
            }
         }
			
         function serialize() {
            function binary(arg) {
               return arg ? 1 : 0;
            }
            var data = [];
            branches.each(function(i, e) {
               data[i] = $(e).is(":has(>ul:visible)") ? 1 : 0;
            });
            $.cookie(settings.cookieId, data.join("") );
         }
			
         function deserialize() {
            var stored = $.cookie(settings.cookieId);
            if ( stored ) {
               var data = stored.split("");
               branches.each(function(i, e) {
                  $(e).find(">ul")[ parseInt(data[i]) ? "show" : "hide" ]();
               });
            }
         }
			
         // add treeview class to activate styles
         this.addClass("treeview");
			
         // prepare branches and find all tree items with child lists
         var branches = this.find("li").prepareBranches(settings);
			
         switch(settings.persist) {
            case "cookie":
               var toggleCallback = settings.toggle;
               settings.toggle = function() {
                  serialize();
                  if (toggleCallback) {
                     toggleCallback.apply(this, arguments);
                  }
               };
               deserialize();
               break;
            case "location":
               var current = this.find("a").filter(function() {
                  return this.href.toLowerCase() == location.href.toLowerCase();
               });
               if ( current.length ) {
                  current.addClass("selected").parents("ul, li").add( current.next() ).show();
               }
               break;
         }
			
         branches.applyClasses(settings, toggler);
				
         // if control option is set, create the treecontroller and show it
         if ( settings.control ) {
            treeController(this, settings.control);
            $(settings.control).show();
         }

         this.bind("remove", function(event, branches) {
            var b = (branches == 'all') ? $(this).find('li') : $(branches);
            b.remove();
         });

         return this.bind("add", function(event, branches) {
            $(branches).prev()
            .removeClass(CLASSES.last)
            .removeClass(CLASSES.lastCollapsable)
            .removeClass(CLASSES.lastExpandable)
            .find(">.hitarea")
            .removeClass(CLASSES.lastCollapsableHitarea)
            .removeClass(CLASSES.lastExpandableHitarea);
            $(branches).find("li").andSelf().prepareBranches(settings).applyClasses(settings, toggler);
         });
      }
   });
	
   // classes used by the plugin
   // need to be styled via external stylesheet, see first example
   var CLASSES = $.fn.treeview.classes = {
      open: "open",
      closed: "closed",
      expandable: "expandable",
      expandableHitarea: "expandable-hitarea",
      lastExpandableHitarea: "lastExpandable-hitarea",
      collapsable: "collapsable",
      collapsableHitarea: "collapsable-hitarea",
      lastCollapsableHitarea: "lastCollapsable-hitarea",
      lastCollapsable: "lastCollapsable",
      lastExpandable: "lastExpandable",
      last: "last",
      hitarea: "hitarea"
   };
	
   // provide backwards compability
   $.fn.Treeview = $.fn.treeview;
	
})(jQuery);
