Tip: Click lines to highlight, hold ctrl/cmd to multi-select

explorer style jquery treeview (23-Oct @ 14:32)

Exstended version of the jQuery treeview plugin http://docs.jquery.com/Plugins/Treeview

Now supports a stayopen option that makes it behave like the windows explorer

www.flickr.com-photos-bjarlestam

Syntax Highlighted Code

  1. /*
  2.  * Treeview 1.4 - 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 4684 2008-02-07 19:08:06Z joern.zaefferer $
  14.  *
  15.  * Additions by Andreas Bjärlestam:
  16.  *   - Added stayopen option
  17.  *   - Added expandAll jQuery object method
  18.  *   - Added removeFolders jQuery object method
  19.  */
  20.  
  21. ;(function($) {
  22.  
  23.     $.extend($.fn, {
  24.         swapClass: function(c1, c2) {
  25.             var c1Elements = this.filter('.' + c1);
  26.             this.filter('.' + c2).removeClass(c2).addClass(c1);
  27.             c1Elements.removeClass(c1).addClass(c2);
  28.             return this;
  29.         },
  30.         replaceClass: function(c1, c2) {
  31.             return this.filter('.' + c1).removeClass(c1).addClass(c2).end();
  32.         },
  33.         hoverClass: function(className) {
  34.             className = className || "hover";
  35.             return this.hover(function() {
  36.                 $(this).addClass(className);
  37.             }, function() {
  38.                 $(this).removeClass(className);
  39.             });
  40.         },
  41.         heightToggle: function(animated, callback) {
  42.             animated ?
  43.                 this.animate({ height: "toggle" }, animated, callback) :
  44.                 this.each(function(){
  45.                     jQuery(this)[ jQuery(this).is(":hidden") ? "show" : "hide" ]();
  46.                     if(callback)
  47.                         callback.apply(this, arguments);
  48.                 });
  49.         },
  50.         heightHide: function(animated, callback) {
  51.             if (animated) {
  52.                 this.animate({ height: "hide" }, animated, callback);
  53.             } else {
  54.                 this.hide();
  55.                 if (callback)
  56.                     this.each(callback);                
  57.             }
  58.         },
  59.         prepareBranches: function(settings) {
  60.             if (!settings.prerendered) {
  61.                 // mark last tree items
  62.                 this.filter(":last-child:not(ul)").addClass(CLASSES.last);
  63.                 // collapse whole tree, or only those marked as closed, anyway except those marked as open
  64.                 this.filter((settings.collapsed ? "" : "." + CLASSES.closed) + ":not(." + CLASSES.open + ")").find(">ul").hide();
  65.             }
  66.             // return all items with sublists
  67.             return this.filter(":has(>ul)");
  68.         },
  69.         applyClasses: function(settings, toggler) {
  70.             this.filter(":has(>ul):not(:has(>a))").find(">span").click(function(event) {
  71.                 toggler.apply($(this).next());
  72.             }).add( $("a", this) ).hoverClass();
  73.            
  74.             if (!settings.prerendered) {
  75.                 // handle closed ones first
  76.                 this.filter(":has(>ul:hidden)")
  77.                         .addClass(CLASSES.expandable)
  78.                         .replaceClass(CLASSES.last, CLASSES.lastExpandable);
  79.                        
  80.                 // handle open ones
  81.                 this.not(":has(>ul:hidden)")
  82.                         .addClass(CLASSES.collapsable)
  83.                         .replaceClass(CLASSES.last, CLASSES.lastCollapsable);
  84.                        
  85.                 // create hitarea
  86.                 this.prepend("<div class=\"" + CLASSES.hitarea + "\"/>").find("div." + CLASSES.hitarea).each(function() {
  87.                     var classes = "";
  88.                     $.each($(this).parent().attr("class").split(" "), function() {
  89.                         classes += this + "-hitarea ";
  90.                     });
  91.                     $(this).addClass( classes );
  92.                 });
  93.             }
  94.            
  95.             // apply event to hitarea
  96.             this.find("div." + CLASSES.hitarea).click( toggler );
  97.         },
  98.         expandAll: function() {
  99.             if(!this.hasClass("treeview")) {throw "can't expand element that is not a tree"};
  100.             $("div." + CLASSES.hitarea, this)
  101.                 .replaceClass( CLASSES.expandableHitarea, CLASSES.collapsableHitarea )
  102.                 .replaceClass( CLASSES.lastExpandableHitarea, CLASSES.lastCollapsableHitarea )
  103.                 .parent()
  104.                 .replaceClass( CLASSES.expandable, CLASSES.collapsable )
  105.                 .replaceClass( CLASSES.lastExpandable, CLASSES.lastCollapsable )
  106.                 .find( ">ul" ).show();
  107.             return this;
  108.         },
  109.         removeFolders: function(folderIds) {
  110.             if(!this.hasClass("treeview")) {throw "can't remove folders from element that is not a tree"};
  111.             var tree = this;
  112.             jQuery.each(folderIds, function() {
  113.                 tree.find('input.folderId[value='+ this +']').parents('li:first').remove();
  114.             });
  115.             return this;
  116.         },
  117.         treeview: function(settings) {
  118.            
  119.             settings = $.extend({
  120.                 cookieId: "treeview"
  121.             }, settings);
  122.            
  123.             if (settings.add) {
  124.                 return this.trigger("add", [settings.add]);
  125.             }
  126.            
  127.             if ( settings.toggle ) {
  128.                 var callback = settings.toggle;
  129.                 settings.toggle = function() {
  130.                     return callback.apply($(this).parent()[0], arguments);
  131.                 };
  132.             }
  133.        
  134.             // factory for treecontroller
  135.             function treeController(tree, control) {
  136.                 // factory for click handlers
  137.                 function handler(filter) {
  138.                     return function() {
  139.                         // reuse toggle event handler, applying the elements to toggle
  140.                         // start searching for all hitareas
  141.                         toggler.apply( $("div." + CLASSES.hitarea, tree).filter(function() {
  142.                             // for plain toggle, no filter is provided, otherwise we need to check the parent element
  143.                             return filter ? $(this).parent("." + filter).length : true;
  144.                         }) );
  145.                         return false;
  146.                     };
  147.                 }
  148.                 // click on first element to collapse tree
  149.                 $("a:eq(0)", control).click( handler(CLASSES.collapsable) );
  150.                 // click on second to expand tree
  151.                 $("a:eq(1)", control).click( handler(CLASSES.expandable) );
  152.                 // click on third to toggle tree
  153.                 $("a:eq(2)", control).click( handler() );
  154.             }
  155.        
  156.             // handle toggle event
  157.             function toggler() {
  158.                 if( settings.stayopen
  159.                         && $(this).is(':not(div.hitarea)')
  160.                         && $(this).parent().find(">.hitarea").hasClass(CLASSES.collapsableHitarea)) {
  161.                     return;
  162.                 }
  163.                
  164.                 $(this)
  165.                     .parent()
  166.                     // swap classes for hitarea
  167.                     .find(">.hitarea")
  168.                         .swapClass( CLASSES.collapsableHitarea, CLASSES.expandableHitarea )
  169.                         .swapClass( CLASSES.lastCollapsableHitarea, CLASSES.lastExpandableHitarea )
  170.                     .end()
  171.                     // swap classes for parent li
  172.                     .swapClass( CLASSES.collapsable, CLASSES.expandable )
  173.                     .swapClass( CLASSES.lastCollapsable, CLASSES.lastExpandable )
  174.                     // find child lists
  175.                     .find( ">ul" )
  176.                     // toggle them
  177.                     .heightToggle( settings.animated, settings.toggle );
  178.                 if ( settings.unique ) {
  179.                     $(this).parent()
  180.                         .siblings()
  181.                         // swap classes for hitarea
  182.                         .find(">.hitarea")
  183.                             .replaceClass( CLASSES.collapsableHitarea, CLASSES.expandableHitarea )
  184.                             .replaceClass( CLASSES.lastCollapsableHitarea, CLASSES.lastExpandableHitarea )
  185.                         .end()
  186.                         .replaceClass( CLASSES.collapsable, CLASSES.expandable )
  187.                         .replaceClass( CLASSES.lastCollapsable, CLASSES.lastExpandable )
  188.                         .find( ">ul" )
  189.                         .heightHide( settings.animated, settings.toggle );
  190.                 }
  191.             }
  192.            
  193.             function serialize() {
  194.                 function binary(arg) {
  195.                     return arg ? 1 : 0;
  196.                 }
  197.                 var data = [];
  198.                 branches.each(function(i, e) {
  199.                     data[i] = $(e).is(":has(>ul:visible)") ? 1 : 0;
  200.                 });
  201.                 $.cookie(settings.cookieId, data.join("") );
  202.             }
  203.            
  204.             function deserialize() {
  205.                 var stored = $.cookie(settings.cookieId);
  206.                 if ( stored ) {
  207.                     var data = stored.split("");
  208.                     branches.each(function(i, e) {
  209.                         $(e).find(">ul")[ parseInt(data[i]) ? "show" : "hide" ]();
  210.                     });
  211.                 }
  212.             }
  213.            
  214.             // add treeview class to activate styles
  215.             this.addClass("treeview");
  216.            
  217.             // prepare branches and find all tree items with child lists
  218.             var branches = this.find("li").prepareBranches(settings);
  219.            
  220.             switch(settings.persist) {
  221.             case "cookie":
  222.                 var toggleCallback = settings.toggle;
  223.                 settings.toggle = function() {
  224.                     serialize();
  225.                     if (toggleCallback) {
  226.                         toggleCallback.apply(this, arguments);
  227.                     }
  228.                 };
  229.                 deserialize();
  230.                 break;
  231.             case "location":
  232.                 var current = this.find("a").filter(function() { return this.href.toLowerCase() == location.href.toLowerCase(); });
  233.                 if ( current.length ) {
  234.                     current.addClass("selected").parents("ul, li").add( current.next() ).show();
  235.                 }
  236.                 break;
  237.             }
  238.            
  239.             branches.applyClasses(settings, toggler);
  240.                
  241.             // if control option is set, create the treecontroller and show it
  242.             if ( settings.control ) {
  243.                 treeController(this, settings.control);
  244.                 $(settings.control).show();
  245.             }
  246.            
  247.             return this.bind("add", function(event, branches) {
  248.                 $(branches).prev()
  249.                     .removeClass(CLASSES.last)
  250.                     .removeClass(CLASSES.lastCollapsable)
  251.                     .removeClass(CLASSES.lastExpandable)
  252.                 .find(">.hitarea")
  253.                     .removeClass(CLASSES.lastCollapsableHitarea)
  254.                     .removeClass(CLASSES.lastExpandableHitarea);
  255.                 $(branches).find("li").andSelf().prepareBranches(settings).applyClasses(settings, toggler);
  256.             });
  257.         }
  258.     });
  259.    
  260.     // classes used by the plugin
  261.     // need to be styled via external stylesheet, see first example
  262.     var CLASSES = $.fn.treeview.classes = {
  263.         open: "open",
  264.         closed: "closed",
  265.         expandable: "expandable",
  266.         expandableHitarea: "expandable-hitarea",
  267.         lastExpandableHitarea: "lastExpandable-hitarea",
  268.         collapsable: "collapsable",
  269.         collapsableHitarea: "collapsable-hitarea",
  270.         lastCollapsableHitarea: "lastCollapsable-hitarea",
  271.         lastCollapsable: "lastCollapsable",
  272.         lastExpandable: "lastExpandable",
  273.         last: "last",
  274.         hitarea: "hitarea"
  275.     };
  276.    
  277.     // provide backwards compability
  278.     $.fn.Treeview = $.fn.treeview;
  279.    
  280. })(jQuery);

Plain Code

/*
 * 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 $
 *
 * Additions by Andreas Bjärlestam:
 *   - Added stayopen option 
 *   - Added expandAll jQuery object method
 *   - Added removeFolders jQuery object method
 */

;(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);
                        
                // 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 );
        },
        expandAll: function() {
            if(!this.hasClass("treeview")) {throw "can't expand element that is not a tree"};
            $("div." + CLASSES.hitarea, this)
                .replaceClass( CLASSES.expandableHitarea, CLASSES.collapsableHitarea )
                .replaceClass( CLASSES.lastExpandableHitarea, CLASSES.lastCollapsableHitarea )
                .parent()
                .replaceClass( CLASSES.expandable, CLASSES.collapsable )
                .replaceClass( CLASSES.lastExpandable, CLASSES.lastCollapsable )
                .find( ">ul" ).show();
            return this;
        },
        removeFolders: function(folderIds) {
            if(!this.hasClass("treeview")) {throw "can't remove folders from element that is not a tree"};
            var tree = this;
            jQuery.each(folderIds, function() {
                tree.find('input.folderId[value='+ this +']').parents('li:first').remove();
            });
            return this;
        },
        treeview: function(settings) {
            
            settings = $.extend({
                cookieId: "treeview"
            }, settings);
            
            if (settings.add) {
                return this.trigger("add", [settings.add]);
            }
            
            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() {
                if( settings.stayopen 
                        && $(this).is(':not(div.hitarea)')
                        && $(this).parent().find(">.hitarea").hasClass(CLASSES.collapsableHitarea)) {
                    return;
                }
                
                $(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();
            }
            
            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);

Codedump Run

Permalink: http://codedumper.com/explorer-style-jquery-treeview