text: (function(factory){
    if (typeof define === 'function' && define.amd) {
      define(['jquery', 'jquery-qubit'], factory);
    } else if (typeof exports === 'object') {
      factory(require('jquery'), require('jquery-qubit'));
    } else {
      factory(jQuery, jQuery.fn.qubit);
    }
  }(function($, qubit) {
    $.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;
          return bonsai[method].apply(bonsai, [].slice.call(args, 1));
        }
      });
    };
    $.bonsai = {};
    $.bonsai.defaults = {
      expandAll: false, // expand all items
      expand: null, // optional function to expand an item
      collapse: null, // optional function to collapse an item
      addExpandAll: false, // add a link to expand all items
      addSelectAll: false, // add a link to select all checkboxes
      selectAllExclude: null, // a filter selector or function for selectAll
      idAttribute: 'id', // which attribute of the list items to use as an id
  
      // createInputs: create checkboxes or radio buttons for each list item
      // by setting createInputs to "checkbox" or "radio".
      //
      // The name and value for the inputs 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`.
      createInputs: false,
      // checkboxes: run qubit(this.options) on the root node (requires jquery.qubit)
      checkboxes: false,
      // handleDuplicateCheckboxes: update any other checkboxes that
      // have the same value
      handleDuplicateCheckboxes: false,
      // createRadioButtons: creates radio buttons 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`.
      createRadioButtons: false,
  
      checkedIds:[]
    };
    var Bonsai = function(el, options) {
      var self = this;
      options = options || {};
      this.options = $.extend({}, $.bonsai.defaults, options);
      this.el = $(el).addClass('bonsai').data('bonsai', this);
  
      // store the scope in the options for child nodes
      if (!this.options.scope) {
        this.options.scope = this.el;
      }
      this.update();
      if (this.isRootNode()) {
        if (this.options.createCheckboxes) this.createInputs = 'checkbox';
        if (this.options.handleDuplicateCheckboxes) this.handleDuplicateCheckboxes();
        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;
      },
      listItem: function(id) {
        if (typeof id === 'object') return $(id);
        return this.el.find('[' + this.options.idAttribute + '="' + id + '"]');
      },
      toggle: function(listItem) {
        if (!$(listItem).hasClass('expanded')) {
          return this.expand(listItem);
        }
        else {
          return this.collapse(listItem);
        }
      },
      expand: function(listItem) {
        return this.setExpanded(listItem, true);
      },
      collapse: function(listItem) {
        return this.setExpanded(listItem, false);
      },
      setExpanded: function(listItem, expanded) {
        var $li = this.listItem(listItem);
        if ($li.length > 1) {
          var self = this;
          $li.each(function() {
            self.setExpanded(this, expanded);
          });
          return;
        }
        if (expanded) {
          if (!$li.data('subList')) return;
          $li = $($li).addClass('expanded').removeClass('collapsed');
          $($li.data('subList')).css('height', 'auto');
        }
        else {
          $li = $($li).addClass('collapsed')
            .removeClass('expanded');
          $($li.data('subList')).height(0);
        }
        return $li;
      },
      expandAll: function() {
        this.expand(this.el.find('li'));
      },
      collapseAll: function() {
        this.collapse(this.el.find('li'));
      },
      expandTo: function(listItem) {
        var self = this;
        var $li = this.listItem(listItem);
        $li.parents('li').each(function () {
          self.expand($(this));
        });
        return $li;
      },
      update: function() {
        var self = this;
        // look for a nested list (if any)
        this.el.children().each(function() {
          var item = $(this);
          if (self.options.createInputs) self.insertInput(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;
      },
      serialize: function() {
        var idAttr = this.options.idAttribute;
        return this.el.find('li').toArray().reduce(function(acc, li) {
          var $li = $(li);
          var id = $li.attr(idAttr);
          // only items with IDs can be serialized
          if (id) {
            var state = $li.hasClass('expanded')
                ? 'expanded'
                : ($li.hasClass('collapsed') ? 'collapsed' : null);
            if (state) acc[$li.hasClass('expanded') ? 'expanded' : 'collapsed'].push(id);
          }
          return acc;
        }, {expanded: [], collapsed: [], version: 2});
      },
      restore: function(state) {
        var self = this;
        if (state.version > 1) {
          state.expanded.map(this.expand.bind(this));
          state.collapsed.map(this.collapse.bind(this));
        }
        else {
          Object.keys(state).forEach(function(id) {
            self.setExpanded(id, state[id] === 'expanded');
          });
        }
      },
      insertInput: function(listItem) {
        var type = this.options.createInputs;
        if (listItem.find('> input[type=' + type + ']').length) return;
        var id = this.inputIdFor(listItem);
        var checkd='';
        if (this.options.checkedIds.includes(listItem.attr('data-value'))){
          checkd='checked';
        }
  
        var checkbox = $('<input type="' + type + '" name="'
          + this.inputNameFor(listItem) + '" id="' + id + '"  '+checkd+'/> '
        );
        var children = listItem.children();
        // get the first text node for the label
        var text = listItem.contents().filter(function() {
          return this.nodeType == 3;
        }).first();
        checkbox.val(listItem.data('value'));
        checkbox.prop('checked', listItem.data('checked'))
        children.detach();
        listItem.append(checkbox)
          .append(
            $('<label for="' + id + '">').append(text.length > 0 ? text : children.first())
          )
          .append(text.length > 0 ? children : children.slice(1));
      },
      checkboxPrefix: 'bonsai-checkbox-',
      inputIdFor: function(listItem) {
        var id = $(listItem).data('id');
        while (!id || ($('#' + id).length > 0)) {
          id = this.checkboxPrefix + Bonsai.uniqueId++;
        }
        return id;
      },
      inputNameFor: function(listItem) {
        return listItem.data('name')
          || listItem.parents().filter('[data-name]').data('name');
      },
      handleDuplicateCheckboxes: 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() + '"]'
            + (checkbox.attr('name') ? '[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');
        });
      },
      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;
        var 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
    });
  }));
  