+++ /dev/null
-(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 = $('<div class="thumb"></div>');
- 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 = $('<input type="checkbox" name="'
- + this.getCheckboxName(listItem) + '" id="' + id + '" /> '
- ),
- 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(
- $('<label for="' + id + '">').append(text ? text : children.first())
- )
- .append(text ? children : children.slice(1));
- },
- handleDuplicates: function() {
- var self = this;
- self.el.on('change', 'input[type=checkbox]', function(ev) {
- var checkbox = $(ev.target);
- if (!checkbox.val()) return;
- // select all duplicate checkboxes that need to be updated
- var selector = 'input[type=checkbox]'
- + '[value="' + checkbox.val() + '"]'
- + '[name="' + checkbox.attr('name') + '"]'
- + (checkbox.prop('checked') ? ':not(:checked)' : ':checked');
- self.el.find(selector).prop({
- checked: checkbox.prop('checked'),
- indeterminate: checkbox.prop('indeterminate')
- }).trigger('change');
- });
- },
- idPrefix: 'checkbox-',
- generateId: function(listItem) {
- do {
- var id = this.idPrefix + Bonsai.uniqueId++;
- }
- while($('#' + id).length > 0);
- return id;
- },
- getCheckboxName: function(listItem) {
- return listItem.data('name')
- || listItem.parents().filter('[data-name]').data('name');
- },
- addExpandAllLink: function() {
- var self = this;
- $('<div class="expand-all">')
- .append($('<a class="all">Expand all</a>')
- .on('click', function() {
- self.expandAll();
- })
- )
- .append('<i class="separator"></i>')
- .append($('<a class="none">Collapse all</a>')
- .on('click', function() {
- self.collapseAll();
- })
- )
- .insertBefore(this.el);
- },
- addSelectAllLink: function() {
- var scope = this.options.scope,
- self = this;
- function getCheckboxes() {
- // return all checkboxes that are not in hidden list items
- return scope.find('li')
- .filter(self.options.selectAllExclude || function() {
- return $(this).css('display') != 'none';
- })
- .find('> input[type=checkbox]');
- }
- $('<div class="check-all">')
- .append($('<a class="all">Select all</a>')
- .css('cursor', 'pointer')
- .on('click', function() {
- getCheckboxes().prop({
- checked: true,
- indeterminate: false
- });
- })
- )
- .append('<i class="separator"></i>')
- .append($('<a class="none">Select none</a>')
- .css('cursor', 'pointer')
- .on('click', function() {
- getCheckboxes().prop({
- checked: false,
- indeterminate: false
- });
- })
- )
- .insertAfter(this.el);
- },
- setCheckedValues: function(values) {
- var all = this.options.scope.find('input[type=checkbox]');
- $.each(values, function(key, value) {
- all.filter('[value="' + value + '"]')
- .prop('checked', true)
- .trigger('change');
- });
- }
- };
- $.extend(Bonsai, {
- uniqueId: 0
- });
-}(jQuery));