Source: src/jquery.treeselect.js

(function($) {

  // From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
  if (!Object.keys) {
    Object.keys = (function () {
      'use strict';
      var hasOwnProperty = Object.prototype.hasOwnProperty,
        hasDontEnumBug = !({toString: null}).propertyIsEnumerable('toString'),
        dontEnums = [
          'toString',
          'toLocaleString',
          'valueOf',
          'hasOwnProperty',
          'isPrototypeOf',
          'propertyIsEnumerable',
          'constructor'
        ],
        dontEnumsLength = dontEnums.length;

      return function (obj) {
        if (typeof obj !== 'object' && (typeof obj !== 'function' || obj === null)) {
          throw new TypeError('Object.keys called on non-object');
        }

        var result = [], prop, i;

        for (prop in obj) {
          if (hasOwnProperty.call(obj, prop)) {
            result.push(prop);
          }
        }

        if (hasDontEnumBug) {
          for (i = 0; i < dontEnumsLength; i++) {
            if (hasOwnProperty.call(obj, dontEnums[i])) {
              result.push(dontEnums[i]);
            }
          }
        }
        return result;
      };
    }());
  }

  // The tree select control.
  $.fn.treeselect = function(params) {

    // Setup the default parameters for the tree select control.
    params = $.extend({
      colwidth: 18,               /** The width of the columns. */
      default_value: {},          /** An array of default values. */
      selected: null,             /** Callback when an item is selected. */
      treeloaded: null,           /** Called when the tree is loaded. */
      load: null,                 /** Callback to load new tree's */
      searcher: null,             /** Callback to search a tree */
      deepLoad: false,            /** Performs a deep load */
      onbuild: null,              /** Called when each node is building. */
      postbuild: null,            /** Called when the node is done building. */
      inputName: 'treeselect',    /** The input name. */
      autoSelectChildren: true,   /** Select chldrn when parent is selected. */
      showRoot: false,            /** Show the root item with a checkbox. */
      selectAll: false,           /** If we wish to see a select all. */
      selectAllText: 'Select All' /** The select all text. */
    }, params);

    /** Keep track of all loaded nodes */
    var loadedNodes = {};

    /** Variable for the busy states. */
    var busyloading = 'treebusy-loading';
    var busyloadingall = 'treebusy-loading-all';
    var busyselecting = 'treebusy-selecting';

    /**
     * Constructor.
     */
    var TreeNode = function(nodeparams, root) {

      // Determine if this is a root item.
      this.root = !!root;

      // Setup the parameters.
      nodeparams.title = nodeparams.title || 'anonymous';
      $.extend(this, {
        id: 0,                /** The ID of this node. */
        nodeloaded: false,    /** Flag to see if this node is loaded. */
        allLoaded: false,     /** Flag to see if we have loaded all nodes. */
        value: 0,             /** The input value for this node. */
        title: '',            /** The title of this node. */
        url: '',              /** The URL to this node. */
        has_children: true,   /** Boolean if this node has children. */
        children: [],         /** Array of children. */
        data: {},             /** Additional data to attach to the node. */
        level: 0,             /** The level of this node. */
        odd: false,           /** The odd/even state of this row. */
        checked: false,       /** If this node is checked. */
        busy: false,          /** If this node is busy. */
        display: $(),         /** The display of this node. */
        input: $(),           /** The input display. */
        link: $(),            /** The link display. */
        span: $(),            /** The span display. */
        childlist: $(),       /** The childlist display. */
        exclude: {}           /** An array of nodes to exclude for selection. */
      }, nodeparams);

      // Say that we are a TreeNode.
      this.isTreeNode = true;

      // Determine if a node is loading.
      this.loading = false;

      // The load callback queue.
      this.loadqueue = [];
    };

    /**
     * Set the busy cursor for this node.
     */
    TreeNode.prototype.setBusy = function(state, type) {

      // Make sure the state has changed.
      if (state != this.span.hasClass(type)) {
        this.busy = state;
        if (state) {

          // Set the busy type and treebusy.
          this.span.addClass(type);
          this.span.addClass('treebusy');
        }
        else {

          // Remove the busy type.
          this.span.removeClass(type);

          // Only remove the busy if the busy flags are empty.
          var othertype = (type == busyloading) ? busyselecting : busyloading;
          if (!this.span.hasClass(othertype)) {
            this.span.removeClass('treebusy');
          }
        }

      }
    };

    /**
     * Determines if this node is already loaded.
     */
    TreeNode.prototype.isLoaded = function() {
      var loaded = this.nodeloaded;
      loaded |= loadedNodes.hasOwnProperty(this.id);
      loaded |= !this.has_children;
      loaded |= (this.has_children && this.children.length > 0);
      return loaded;
    };

    /**
     * Loads the current node.
     *
     * @param {function} callback - The callback when the node is loaded.
     */
    TreeNode.prototype.loadNode = function(callback, hideBusy) {

      // If we are loading, then just add this callback to the queue and return.
      if (this.loading) {
        if (callback) {
          this.loadqueue.push(callback);
        }
        return;
      }

      // Trigger the callback when the node is done loading.
      var triggerCallback = function() {

        // Callback that we are loaded.
        if (callback) {
          callback(this);
        }

        // Process the loadqueue.
        for (var i in this.loadqueue) {
          this.loadqueue[i](this);
        }

        // Empty the loadqueue.
        this.loadqueue.length = 0;

        // Say we are not busy.
        if (!hideBusy) {
          this.setBusy(false, busyloading);
        }
      };

      // Say we are loading.
      this.loading = true;

      // Only load if we have not loaded yet.
      if (params.load && !this.isLoaded()) {

        // Make this node busy.
        if (!hideBusy) {
          this.setBusy(true, busyloading);
        }

        // Call the load function.
        params.load(this, (function(treenode) {
          return function(node) {

            // Only perform the merging and build if it hasn't loaded.
            if (!treenode.nodeloaded) {

              // Merge the result with this node.
              treenode = jQuery.extend(treenode, node);

              // Say this node is loaded.
              treenode.nodeloaded = true;

              // Add to the loaded nodes array.
              loadedNodes[treenode.id] = treenode.id;

              // Build the node.
              treenode.build(function() {

                // Callback that we are loaded.
                triggerCallback.call(treenode);
              });
            }
            else {

              // Callback that we are loaded.
              triggerCallback.call(treenode);
            }
          };
        })(this));
      }
      else if (callback) {

        // Just callback since we are already loaded.
        triggerCallback.call(this);
      }

      // Say that we are not loading anymore.
      this.loading = false;
    };

    /**
     * Recursively loads and builds all nodes beneath this node.
     *
     * @param {function} callback Called when the tree has loaded.
     * @param {function} operation Allow someone to perform an operation.
     */
    TreeNode.prototype.loadAll = function(callback, operation, hideBusy, ids) {
      ids = ids || {};

      // Make sure we are loaded first.
      this.loadNode(function(node) {

        // See if an operation needs to be performed.
        if (operation) {
          operation(node);
        }

        // Get our children count.
        var i = node.children.length, count = i;

        // If no children, then just call the callback immediately.
        if (!i || ids.hasOwnProperty(node.id)) {
          if (callback) {
            callback.call(node, node);
          }
          return;
        }

        // Add this to the ids to protect against recursion.
        ids[node.id] = node.id;

        // Make this node busy.
        if (!hideBusy) {
          node.setBusy(true, busyloadingall);
        }

        // Load children at a specific index.
        var loadChildren = function(index) {
          return function() {

            // Load this childs children...
            node.children[index].loadAll(function() {

              // Decrement the child count.
              count--;

              // If all children are done loading, call the callback.
              if (!count) {

                // Callback that we are done loading this tree.
                if (callback) {
                  callback.call(node, node);
                }

                // Make this node busy.
                if (!hideBusy) {
                  node.setBusy(false, busyloadingall);
                }
              }
            }, operation, hideBusy, ids);
          };
        };

        // Iterate through each child.
        while (i--) {

          // Load recurssion on a separate thread.
          setTimeout(loadChildren(i), 2);
        }
      });
    };

    /**
     * Expands the node.
     */
    TreeNode.prototype.expand = function(state) {
      if (state) {
        this.link.removeClass('collapsed').addClass('expanded');
        this.span.removeClass('collapsed').addClass('expanded');
        this.childlist.show('fast');

        // If this node is checked as including children, go through and select
        // all of it's children.
        if (!params.deepLoad && this.checked && this.include_children) {
          this.include_children = false;
          this.selectChildren(true);
        }
      }
      // Only collapse if they can open it back up.
      else if (this.span.length > 0) {
        this.link.removeClass('expanded').addClass('collapsed');
        this.span.removeClass('expanded').addClass('collapsed');
        this.childlist.hide('fast');
      }

      // If the state is expand, but the children have not been loaded.
      if (state && !this.isLoaded()) {

        // If there are no children, then we need to load them.
        this.loadNode(function(node) {
          if (node.checked) {
            node.selectChildren(node.checked);
          }
          node.expand(true);
        });
      }
    };

    /**
     * Selects all children of this node.
     *
     * @param {boolean} state The state of the selection or array of defaults.
     * @param {function} done Called when we are done selecting.
     */
    TreeNode.prototype.selectChildren = function(state, done, child) {

      // See if the state is a boolean.
      var defaults = (typeof state == 'object');

      // Create a function to call when we are done selecting.
      var doneSelecting = function() {
        if (!child) {

          // If they provided a selected parameter.
          if (params.selected) {
            params.selected(this, true);
          }

          // Say that we are done.
          if (done) {

            done.call(this);
          }
        }
      };

      if (params.deepLoad) {

        // Load all nodes underneath this node.
        this.loadAll(function() {

          // Set this node not busy.
          this.setBusy(false, busyselecting);

          // We are done selecting.
          doneSelecting.call(this);

        }, function(node) {

          var val = state;
          if (defaults) {
            val = state.hasOwnProperty(node.value);
            val |= state.hasOwnProperty(node.id);
          }

          // Select this node.
          node.select(val);
        });
      }
      else {

        // Select the current node.
        this.select(state);
        var name = params.inputName + '-' + this.value;
        $('input[name="' + name + '-include-below"]').attr(
          'name',
          name
        );

        // We should load children if the current node is expanded, or the
        // current node is being deselected and possibly has children selected
        // below them.
        if ((this.root === true) ||
            (state === false && !this.include_children) ||
            (this.link !== undefined && this.link[0] !== undefined &&
             this.link[0].className.indexOf('expanded') !== -1)
           ) {
          this.include_children = false;
          this.expand(state);
          var i = this.children.length;
          while (i--) {

            // Select all the children.
            this.children[i].selectChildren(state, done, true);
          }
        }
        else {
          // Flag this noad as including all children below if it has children.
          if (this.has_children > 0 && state) {
            this.include_children = true;
            $('input[name="' + name + '"]').attr(
              'name',
              name + '-include-below'
            );
          }
        }

        // We are done selecting.
        doneSelecting.call(this);
      }
    };

    /**
     * Selects default values of the TreeNode.
     *
     * @param {boolean} defaults Array of defaults.
     * @param {function} done Called when we are done selecting.
     */
    TreeNode.prototype.selectDefaults = function(defaults, done) {

      var defaultsLeft = Object.keys(defaults).length;

      var defaultsQueue = [];
      defaultsQueue.push(this);

      // Loop through nodes depth first to find the defaults.
      while (defaultsLeft > 0 && defaultsQueue.length > 0) {
        var queueItem = defaultsQueue.shift();
        var state = false;

        // Check if the queued item is listed in the defaults.
        if (defaults.hasOwnProperty(queueItem.value)) {
          delete defaults[queueItem.value];
          state = true;
          defaultsLeft--;
        }
        if (defaults.hasOwnProperty(queueItem.id)) {
          delete defaults[queueItem.id];
          state = true;
          defaultsLeft--;
        }

        // Check if the queued item is listed in the defaults and is flagged to
        // include defaults.
        if (defaults.hasOwnProperty(queueItem.value + '-include-below')) {
          delete defaults[queueItem.value + '-include-below'];
          queueItem.include_children = true;
          state = true;
          defaultsLeft--;
        }
        if (defaults.hasOwnProperty(queueItem.id + '-include-below')) {
          delete defaults[queueItem.id + '-include-below'];
          queueItem.include_children = true;
          state = true;
          defaultsLeft--;
        }

        // Select the queued item.
        queueItem.select(state);

        // Set the input name to the correct value.
        var name = params.inputName + '-' + queueItem.value;
        $('input[name="' + name + '-include-below"]').attr('name', name);
        if (!queueItem.root && state && queueItem.include_children) {
          $('input[name="' + name + '"]').attr('name', name + '-include-below');
        }
        else if (defaultsLeft > 0) {
          // Add this node's children to the queue.
          var i = queueItem.children.length;
          while (i--) {
            defaultsQueue.push(queueItem.children[i]);
          }
        }
        else if (queueItem.root && queueItem.include_children) {

          // Select the root node's children.
          queueItem.selectChildren(true);
        }
      }

      // Say this node is now fully selected.
      if (params.selected) {
        params.selected(this, true);
      }

      // Say we are now done.
      if (done) {
        done.call(this);
      }
    };

    /**
     * Sets the checked state for the input field depending on the state.
     *
     * @param {boolean} state
     */
    TreeNode.prototype.setChecked = function(state) {

      // Set the checked state.
      this.checked = state;

      // Set the checked state for this input.
      this.input.eq(0)[0].checked = state;

      // Trigger the change event.
      this.input.change();
    };

    /**
     * Selects a node.
     *
     * @param {boolean} state The state of the selection.
     */
    TreeNode.prototype.select = function(state) {

      // Only check this node if it is a selectable input.
      if (!this.input.hasClass('treenode-no-select')) {

        // Convert state to a boolean.
        state = !!state;

        // Select the element unless the state is false and we are on the root
        // element which isn't unselectable.
        if (state || !this.root || (this.showRoot && this.has_children)) {

          // Set the checked state.
          this.setChecked(state);

          // Say that this node is selected.
          if (params.selected) {
            params.selected(this);
          }
        }
      }
    };

    /**
     * Build the treenode element.
     */
    TreeNode.prototype.build_treenode = function() {
      var treenode = $();
      treenode = $(document.createElement(this.root ? 'div' : 'li'));
      treenode.addClass('treenode');
      treenode.addClass(this.odd ? 'odd' : 'even');
      return treenode;
    };

    /**
     * Build the input and return.
     */
    TreeNode.prototype.build_input = function(left) {

      // Only add an input if the input name is defined.
      if (params.inputName) {

        // If this node is excluded or has no roles enabled in the group finder,
        // then add a dummy div tag.
        if ((typeof this.exclude[this.id] !== 'undefined') ||
          (params.inputName == 'group_finder' && !this.data.roles_enabled)) {
          this.input = $(document.createElement('div'));
          this.input.addClass('treenode-no-select');
        }
        else {

          // Create the input element.
          this.input = $(document.createElement('input'));

          // Get the value for this input item.
          var value = this.value || this.id;

          // Create the attributes for this input item.
          this.input.attr({
            'type': 'checkbox',
            'value': value,
            'name': params.inputName + '-' + value,
            'id': 'choice_' + this.id
          }).addClass('treenode-input');

          // Check the input.
          this.setChecked(this.checked);

          // Bind to the click on the input.
          this.input.bind('click', (function(node) {
            return function(event) {

              // Set the checked state based on input.
              node.checked = event.target.checked;

              // Only expand/collapse and select children if auto select
              // children is enabled.
              if (params.autoSelectChildren) {
                // Expand if deep loading. Collapse if unchecked.
                if (!node.checked || params.deepLoad) {
                  node.expand(node.checked);
                }

                // Call the select method.
                node.selectChildren(node.checked);
              }
            };
          })(this));

          // If this is a root item and we are not showing the root item, then
          // just hide the input.
          if (this.root && !params.showRoot) {
            this.input.hide();
          }
        }

        // Set the input left.
        this.input.css('left', left + 'px');
      }
      return this.input;
    };

    /**
     * Creates a node link.
     */
    TreeNode.prototype.build_link = function(element) {
      element.css('cursor', 'pointer').addClass('collapsed');
      element.bind('click', {node: this}, function(event) {
        event.preventDefault();
        event.data.node.expand($(event.target).hasClass('collapsed'));
      });
      return element;
    };

    /**
     * Build the span +/- symbol.
     */
    TreeNode.prototype.build_span = function(left) {

      // If we are showing the root item or we are not root, and we have
      // children, show a +/- symbol.
      if ((!this.root || this.showRoot) && this.has_children) {
        this.span = this.build_link($(document.createElement('span')).attr({
          'class': 'treeselect-expand'
        }));
        this.span.css('left', left + 'px');
      }
      return this.span;
    };

    /**
     * Build the title link.
     */
    TreeNode.prototype.build_title = function(left) {

      // If there is a title, then build it.
      if ((!this.root || this.showRoot) && this.title) {

        // Create a node link.
        this.nodeLink = $(document.createElement('a')).attr({
          'class': 'treeselect-title',
          'href': this.url,
          'target': '_blank'
        }).css('marginLeft', left + 'px').text(this.title);

        // If this node has children, then it should be a link.
        if (this.has_children) {
          this.link = this.build_link(this.nodeLink.clone());
        }
        else {
          this.link = $(document.createElement('div')).attr({
            'class': 'treeselect-title'
          }).css('marginLeft', left + 'px').text(this.title);
        }
      }

      // Return the link.
      return this.link;
    };

    /**
     * Build the children.
     */
    TreeNode.prototype.build_children = function(done) {

      // Create the childlist element.
      this.childlist = $();

      // If this node has children.
      if (this.children.length > 0) {

        // Create the child list.
        this.childlist = $(document.createElement('ul'));

        // Set the odd state.
        var odd = this.odd;

        // Get the number of children.
        var numChildren = this.children.length;

        // Function to append children.
        var appendChildren = function(treenode, index) {
          return function() {

            // Add the child tree to the list.
            treenode.children[index].build(function(child) {

              // Decrement the number of children loaded.
              numChildren--;

              // Append the child to the list.
              treenode.childlist.append(child.display);

              // If there are no more chlidren, then say we are done.
              if (!numChildren) {
                done.call(treenode, treenode.childlist);
              }
            });
          };
        };

        // Now if there are children, iterate and build them.
        for (var i in this.children) {

          // Make sure the child is a valid object in the list.
          if (this.children.hasOwnProperty(i)) {

            // Set the child.
            var child = this.children[i];

            // Alternate the odd state.
            odd = !odd;

            // Get the checked value.
            var isChecked = this.checked;
            if (child.hasOwnProperty('checked')) {
              isChecked = child.checked;
            }

            // Create a new TreeNode for this child.
            this.children[i] = new TreeNode($.extend(child, {
              level: this.level + 1,
              odd: odd,
              checked: isChecked,
              exclude: this.exclude
            }));

            // Set timeout to help with recursion.
            setTimeout(appendChildren(this, i), 2);
          }
        }
      }
      else {

        // Call that we are done loading this child.
        done.call(this, this.childlist);
      }
    };

    /**
     * Builds the DOM and the tree for this node.
     */
    TreeNode.prototype.build = function(done) {

      // Keep track of the left margin for each element.
      var left = 5, elem = null;

      // Create the list display.
      if (this.display.length === 0) {
        this.display = this.build_treenode();
      }
      else if (this.root) {
        var treenode = this.build_treenode();
        this.display.append(treenode);
        this.display = treenode;
      }

      // Now append the input.
      if ((this.input.length === 0) &&
          (elem = this.build_input(left)) &&
          (elem.length > 0)) {

        // Add the input to the display.
        this.display.append(elem);
        left += params.colwidth;
      }

      // Now create the +/- sign if needed.
      if (this.span.length === 0) {
        this.display.append(this.build_span(left));
        left += params.colwidth;
      }

      // Now append the node title.
      if (this.link.length === 0) {
        this.display.append(this.build_title(left));
      }

      // Called when the node is done building.
      var onDone = function() {

        // See if they wish to alter the build.
        if (params.onbuild) {
          params.onbuild(this);
        }

        // Create a search item.
        this.searchItem = this.display.clone();
        $('.treeselect-expand', this.searchItem).remove();

        // If the search title is not a link, then make it one...
        var searchTitle = $('div.treeselect-title', this.searchItem);
        if (searchTitle.length > 0) {
          searchTitle.replaceWith(this.nodeLink);
        }

        // See if they wish to hook into the postbuild process.
        if (params.postbuild) {
          params.postbuild(this);
        }

        // Check if this node is excluded, and hide if so.
        if (typeof this.exclude[this.id] !== 'undefined') {
          if ($('.treenode-input', this.display).length === 0) {
            this.display.hide();
          }
        }

        // If they wish to know when we are done building.
        if (done) {
          done.call(this, this);
        }
      };

      // Append the children.
      if (this.childlist.length === 0) {
        this.build_children(function(children) {
          if (children.length > 0) {
            this.display.append(children);
          }
          onDone.call(this);
        });
      }
      else {
        onDone.call(this);
      }
    };

    /**
     * Returns the selectAll text if that applies to this node.
     */
    TreeNode.prototype.getSelectAll = function() {
      if (this.root && this.selectAll) {
        return this.selectAllText;
      }
      return false;
    };

    /**
     * Search this node for matching text.
     *
     * @param {string} text The text to search for.
     * @param {function} callback Called with the results of this search.
     */
    TreeNode.prototype.search = function(text, callback) {
      // If no text was provided, then just return the root children.
      if (!text) {
        if (callback) {
          callback(this.children, false);
        }
      }
      else {

        // Initialize our results.
        var results = {};

        // Convert the text to lowercase.
        text = text.toLowerCase();

        // See if they provided a search endpoint.
        if (params.searcher) {

          // Call the searcher for the new nodes.
          params.searcher(this, text, function(nodes, getNode) {

            // Get the number of nodes.
            var numNodes = Object.keys(nodes).length;

            // If no nodes were returned then return nothing.
            if (numNodes === 0) {
              callback(results, true);
            }

            // Called when the tree node is built.
            var onBuilt = function(id) {

              // Return the method to call when the node is built.
              return function(treenode) {

                // Decrement the counter.
                numNodes--;

                // Add the node to the results.
                results[id] = treenode;

                // If no more nodes are loading, then callback.
                if (!numNodes) {

                  // Callback with the search results.
                  callback(results, true);
                }
              };
            };

            // Iterate through all the nodes.
            for (var id in nodes) {

              // Set the treenode.
              var treenode = new TreeNode(getNode ? getNode(nodes[id]) : nodes[id]);

              // Say this node is loaded.
              treenode.nodeloaded = true;

              // Add to the loaded nodes array.
              loadedNodes[treenode.id] = treenode.id;

              // Build the node.
              treenode.build(onBuilt(id));
            }
          });
        }
        else {

          // Load all nodes.
          this.loadAll(function(node) {

            // Callback with the results of this search.
            if (callback) {
              callback(results, true);
            }
          }, function(node) {

            // If we are not the root node, and the text matches the title.
            if (!node.root && node.title.toLowerCase().search(text) !== -1) {

              // Add this to our search results.
              results[node.id] = node;
            }
          }, true);
        }
      }
    };

    // Iterate through each instance.
    return $(this).each(function() {

      // Get the tree node parameters.
      var treeParams = $.extend(params, {display: $(this)});

      // Create a root tree node and load it.
      var root = this.treenode = new TreeNode(treeParams, true);

      // Add a select all link.
      var selectAll = root.getSelectAll();
      if (selectAll !== false && !root.showRoot) {

        // See if the select all button should be checked.
        var checked = false;
        var default_value = params.default_value;
        if (default_value.hasOwnProperty(root.value + '-include-below')) {
          checked = true;
        }

        // Create an input element.
        var inputElement = $(document.createElement('input')).attr({
          'type': 'checkbox'
        });

        // Set the checked state.
        inputElement.eq(0)[0].checked = checked;

        // Bind to the click event.
        inputElement.bind('click', function(event) {
          root.selectChildren(event.target.checked);
        });

        // Add the input item to the root.
        root.display.append(inputElement);

        // If they provided select all text, add it here.
        if (selectAll) {
          var span = $(document.createElement('span')).attr({
            'class': 'treeselect-select-all'
          }).html(selectAll);
          root.display.append(span);
        }
      }

      // Create a loading span.
      var initBusy = $(document.createElement('span')).addClass('treebusy');
      root.display.append(initBusy.css('display', 'block'));

      // Called when the root node is done loading.
      var doneLoading = function() {

        // Remove the init busy cursor.
        initBusy.remove();

        // Call the treeloaded params.
        if (params.treeloaded) {
          params.treeloaded(this);
        }
      };

      // Load the node.
      root.loadNode(function(node) {

        // Check the length of children in this node.
        if (node.children.length === 0) {

          // If the root node does not have any children, then hide.
          node.display.hide();
        }

        // Expand this root node.
        node.expand(true);

        // Select this node based on the default value.
        node.select(node.checked);

        // Set the defaults for all the children.
        var defaults = node.checked;
        if (!jQuery.isEmptyObject(params.default_value)) {
          defaults = params.default_value;
        }

        // If there are defaults, then select the children with them.
        if (defaults) {

          // Select the children based on the defaults.
          if (params.deepLoad) {
            node.selectChildren(defaults, function() {
              doneLoading.call(node);
            });
          }
          else {
            // When not deep loading, use selectDefaults to search for defaults
            // using breadth first check.
            node.selectDefaults(defaults, function() {
              doneLoading.call(node);
            });
          }
        }
        else {
          doneLoading.call(node);
        }
      });

      // If the root element doesn't have children, then hide the treeselect.
      if (!root.has_children) {
        this.parentElement.style.display = 'none';
      }
    });
  };
})(jQuery);