(function($){ $.fn.bonsai = function( options ) { return this.each(function() { var bonsai = new Bonsai(this, options); $(this).data('bonsai', bonsai); }); }; $.bonsai = {}; $.bonsai.defaults = { expandAll: false, // boolean expands all items expand: null, // function to expand an item collapse: null, // function to collapse an item checkboxes: false, // requires jquery.qubit // createCheckboxes: creates checkboxes for each list item. // // The name and value for the checkboxes can be declared in the // markup using `data-name` and `data-value`. // // The name is inherited from parent items if not specified. // // Checked state can be indicated using `data-checked`. createCheckboxes: false, // handleDuplicateCheckboxes: adds onChange bindings to update // any other checkboxes that have the same value. handleDuplicateCheckboxes: false, selectAllExclude: null }; var Bonsai = function( el, options ) { var self = this, options = $.extend({}, $.bonsai.defaults, options), checkboxes, isRootNode; this.el = el = $(el); // store the scope in the options for child nodes if( !options.scope ) { options.scope = el; isRootNode = true; } this.options = options; el.addClass('bonsai'); if( options.checkboxes ) { checkboxes = true; // handle checkboxes once at the root of the tree, not on each element options.checkboxes = false; } // look for a nested list (if any) el.children().each(function() { var item = $(this), // insert a thumb thumb = $('
'); if( options.createCheckboxes ) { // insert a checkbox after the thumb self.insertCheckbox(item); } item.prepend(thumb); // if there is a child list $(this).children().filter('ol, ul').last().each(function() { // that is not empty if( $('li', this).length == 0 ) { return; } // then this el has children item.addClass('has-children') // attach the sub-list to the item .data('subList', this); thumb.on('click', function() { self.toggle(item); }); // collapse the nested list if( options.expandAll || item.hasClass('expanded') ) { self.expand(item); } else { self.collapse(item); } // handle any deeper nested lists $(this).bonsai(options) }); }); // if this is root node of the tree if( isRootNode ) { if( checkboxes ) el.qubit(options); if( this.options.addExpandAll ) this.addExpandAll(); if( this.options.addSelectAll ) this.addSelectAll(); } this.expand = options.expand || this.expand; this.collapse = options.collapse || this.collapse; this.el.data('bonsai', this); this.initialised = true; }; Bonsai.prototype = { initialised: false, toggle: function( listItem ) { if( !$(listItem).hasClass('expanded') ) { this.expand(listItem); } else { this.collapse(listItem); } }, expand: function( listItem ) { this.setExpanded(listItem, true); }, collapse: function( listItem ) { this.setExpanded(listItem, false); }, setExpanded: function( listItem, expanded ) { listItem = $(listItem); if( listItem.length > 1 ) { var self = this; listItem.each(function() { self.setExpanded(this, expanded); }); return; } if( expanded ) { if( !listItem.data('subList') ) return; listItem = $(listItem).addClass('expanded') .removeClass('collapsed'); $(listItem.data('subList')).css('height', 'auto'); } else { listItem = $(listItem).addClass('collapsed') .removeClass('expanded'); $(listItem.data('subList')).height(0); } }, expandAll: function() { this.expand($('li', this.el)); }, collapseAll: function() { this.collapse($('li', this.el)); }, insertCheckbox: function( listItem ) { var id = this.generateId(listItem), checkbox = $(' ' ), children = listItem.children(), // get the first text node for the label text = listItem.contents().filter(function() { return this.nodeType == 3; }).first(), self = this; checkbox.val(listItem.data('value')); checkbox.prop('checked', listItem.data('checked')) children.remove(); listItem.append(checkbox) .append($('