(function($){ $.fn.bonsai = function(options) { var args = arguments; return this.each(function() { var bonsai = $(this).data('bonsai'); if (!bonsai) { bonsai = new Bonsai(this, options); $(this).data('bonsai', bonsai); } if (typeof options == 'string') { var method = options; bonsai[method].apply(bonsai, [].slice.call(args, 1)); } }); }; $.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 = options || {}; this.options = $.extend({}, $.bonsai.defaults, options); this.el = $(el).addClass('bonsai').data('bonsai', this); this.update(); if (this.isRootNode()) { if (this.options.handleDuplicateCheckboxes) this.handleDuplicates(); if (this.options.checkboxes) this.el.qubit(this.options); if (this.options.addExpandAll) this.addExpandAllLink(); if (this.options.addSelectAll) this.addSelectAllLink(); this.el.on('click', '.thumb', function(ev) { self.toggle($(ev.currentTarget).closest('li')); }); } if (this.options.expandAll) this.expandAll(); }; Bonsai.prototype = { isRootNode: function() { return this.options.scope == this.el; }, 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(this.el.find('li')); }, collapseAll: function() { this.collapse(this.el.find('li')); }, update: function() { var self = this; // store the scope in the options for child nodes if (!this.options.scope) { this.options.scope = this.el; } // look for a nested list (if any) this.el.children().each(function() { var item = $(this); if (self.options.createCheckboxes) self.insertCheckbox(item); // insert a thumb if it doesn't already exist if (item.children().filter('.thumb').length == 0) { var thumb = $('
'); item.prepend(thumb); } var subLists = item.children().filter('ol, ul'); item.toggleClass('has-children', subLists.find('li').length > 0); // if there is a child list subLists.each(function() { // that's not empty if ($('li', this).length == 0) { return; } // then this el has children item.data('subList', this); // collapse the nested list if (item.hasClass('expanded')) { self.expand(item); } else { self.collapse(item); } // handle any deeper nested lists var exists = !!$(this).data('bonsai'); $(this).bonsai(exists ? 'update' : self.options); }); }); this.expand = this.options.expand || this.expand; this.collapse = this.options.collapse || this.collapse; }, insertCheckbox: function(listItem) { if (listItem.find('> input[type=checkbox]').length) return; 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(); checkbox.val(listItem.data('value')); checkbox.prop('checked', listItem.data('checked')) children.remove(); listItem.append(checkbox) .append( $('