treeview 插件绑定 自定义事件 拖拽

/* =========================================================
 * bootstrap-treeview.js v1.2.0
 * =========================================================
 * Copyright 2013 Jonathan Miles
 * Project URL : http://www.jondmiles.com/bootstrap-treeview
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * ========================================================= */

; (function ($, window, document, undefined) {

  /*global jQuery, console*/

  'use strict';

  var pluginName = 'treeview';

  var _default = {};

  _default.settings = {

    injectStyle: true,

    levels: 2,

    expandIcon: 'glyphicon glyphicon-plus',
    collapseIcon: 'glyphicon glyphicon-minus',
    emptyIcon: 'glyphicon',
    nodeIcon: '',
    selectedIcon: '',
    checkedIcon: 'glyphicon glyphicon-check',
    uncheckedIcon: 'glyphicon glyphicon-unchecked',

    color: undefined, // '#000000',
    backColor: undefined, // '#FFFFFF',
    borderColor: undefined, // '#dddddd',
    onhoverColor: '#F5F5F5',
    selectedColor: '#FFFFFF',
    selectedBackColor: '#428bca',
    searchResultColor: '#D9534F',
    searchResultBackColor: undefined, //'#FFFFFF',

    enableLinks: false,
    highlightSelected: true,
    highlightSearchResults: true,
    showBorder: true,
    showIcon: true,
    showCheckbox: false,
    showTags: false,
    multiSelect: false,

    // Event handlers
    onNodeChecked: undefined,
    onNodeCollapsed: undefined,
    onNodeDisabled: undefined,
    onNodeEnabled: undefined,
    onNodeExpanded: undefined,// 先找事件 gz
    onNodeSelected: undefined,
    onNodeUnchecked: undefined,
    onNodeUnselected: undefined,
    onSearchComplete: undefined,
    onSearchCleared: undefined
    // 拖拽相关事件 gz


  };

  _default.options = {
    silent: false,
    ignoreChildren: false
  };

  _default.searchOptions = {
    ignoreCase: true,
    exactMatch: false,
    revealResults: true
  };

  var Tree = function (element, options) {
    console.log("Tree", options);
    this.$element = $(element);
    this.elementId = element.id;
    this.styleId = this.elementId + '-style';

    this.init(options);

    return {

      // Options (public access)
      options: this.options,

      // Initialize / destroy methods
      init: $.proxy(this.init, this),
      remove: $.proxy(this.remove, this),

      // Get methods
      getNode: $.proxy(this.getNode, this),
      getParent: $.proxy(this.getParent, this),
      getSiblings: $.proxy(this.getSiblings, this),
      getSelected: $.proxy(this.getSelected, this),
      getUnselected: $.proxy(this.getUnselected, this),
      getExpanded: $.proxy(this.getExpanded, this),
      getCollapsed: $.proxy(this.getCollapsed, this),
      getChecked: $.proxy(this.getChecked, this),
      getUnchecked: $.proxy(this.getUnchecked, this),
      getDisabled: $.proxy(this.getDisabled, this),
      getEnabled: $.proxy(this.getEnabled, this),

      // Select methods
      selectNode: $.proxy(this.selectNode, this),
      unselectNode: $.proxy(this.unselectNode, this),
      toggleNodeSelected: $.proxy(this.toggleNodeSelected, this),

      // Expand / collapse methods
      collapseAll: $.proxy(this.collapseAll, this),
      collapseNode: $.proxy(this.collapseNode, this),
      expandAll: $.proxy(this.expandAll, this),
      expandNode: $.proxy(this.expandNode, this),
      toggleNodeExpanded: $.proxy(this.toggleNodeExpanded, this),
      revealNode: $.proxy(this.revealNode, this),

      // Expand / collapse methods
      checkAll: $.proxy(this.checkAll, this),
      checkNode: $.proxy(this.checkNode, this),
      uncheckAll: $.proxy(this.uncheckAll, this),
      uncheckNode: $.proxy(this.uncheckNode, this),
      toggleNodeChecked: $.proxy(this.toggleNodeChecked, this),

      // Disable / enable methods
      disableAll: $.proxy(this.disableAll, this),
      disableNode: $.proxy(this.disableNode, this),
      enableAll: $.proxy(this.enableAll, this),
      enableNode: $.proxy(this.enableNode, this),
      toggleNodeDisabled: $.proxy(this.toggleNodeDisabled, this),

      // Search methods
      search: $.proxy(this.search, this),
      clearSearch: $.proxy(this.clearSearch, this),

      // 拖拽相关事件绑定

    };
  };

  Tree.prototype.init = function (options) {

    this.tree = [];
    this.nodes = [];

    if (options.data) {
      if (typeof options.data === 'string') {
        options.data = $.parseJSON(options.data);
      }
      this.tree = $.extend(true, [], options.data);
      delete options.data;
    }
    this.options = $.extend({}, _default.settings, options);

    this.destroy();
    this.subscribeEvents();
    this.setInitialStates({ nodes: this.tree }, 0);
    this.render();
  };

  Tree.prototype.remove = function () {
    this.destroy();
    $.removeData(this, pluginName);
    $('#' + this.styleId).remove();
  };

  Tree.prototype.destroy = function () {

    if (!this.initialized) return;

    this.$wrapper.remove();
    this.$wrapper = null;

    // Switch off events
    this.unsubscribeEvents();

    // Reset this.initialized flag
    this.initialized = false;
  };

  Tree.prototype.unsubscribeEvents = function () {

    this.$element.off('click');
    this.$element.off('nodeChecked');
    this.$element.off('nodeCollapsed');
    this.$element.off('nodeDisabled');
    this.$element.off('nodeEnabled');
    this.$element.off('nodeExpanded');
    this.$element.off('nodeSelected');
    this.$element.off('nodeUnchecked');
    this.$element.off('nodeUnselected');
    this.$element.off('searchComplete');
    this.$element.off('searchCleared');

    // 拖拽相关事件绑定 gz
  };

  Tree.prototype.subscribeEvents = function () {

    this.unsubscribeEvents();

    this.$element.on('click', $.proxy(this.clickHandler, this));

    if (typeof (this.options.onNodeChecked) === 'function') {
      this.$element.on('nodeChecked', this.options.onNodeChecked);
    }

    if (typeof (this.options.onNodeCollapsed) === 'function') {
      this.$element.on('nodeCollapsed', this.options.onNodeCollapsed);
    }

    if (typeof (this.options.onNodeDisabled) === 'function') {
      this.$element.on('nodeDisabled', this.options.onNodeDisabled);
    }

    if (typeof (this.options.onNodeEnabled) === 'function') {
      this.$element.on('nodeEnabled', this.options.onNodeEnabled);
    }

    if (typeof (this.options.onNodeExpanded) === 'function') {

      // 这是他封装的事件  gz  然后再找事件源
      this.$element.on('nodeExpanded', this.options.onNodeExpanded);
    }

    if (typeof (this.options.onNodeSelected) === 'function') {


      // this.$element.on('nodeSelected', this.options.onNodeSelected);
      this.$element.on('nodeSelected', this.options.onNodeSelected);
    }
    if (typeof (this.options.onNodeUnchecked) === 'function') {
      this.$element.on('nodeUnchecked', this.options.onNodeUnchecked);
    }

    if (typeof (this.options.onNodeUnselected) === 'function') {
      this.$element.on('nodeUnselected', this.options.onNodeUnselected);
    }

    if (typeof (this.options.onSearchComplete) === 'function') {
      this.$element.on('searchComplete', this.options.onSearchComplete);
    }

    if (typeof (this.options.onSearchCleared) === 'function') {
      this.$element.on('searchCleared', this.options.onSearchCleared);
    }

    // 拖拽相关事件 gz

  };

  /*
    Recurse the tree structure and ensure all nodes have
    valid initial states.  User defined states will be preserved.
    For performance we also take this opportunity to
    index nodes in a flattened structure
  */
  Tree.prototype.setInitialStates = function (node, level) {

    if (!node.nodes) return;
    level += 1;

    var parent = node;
    var _this = this;
    $.each(node.nodes, function checkStates(index, node) {

      // nodeId : unique, incremental identifier
      node.nodeId = _this.nodes.length;

      // parentId : transversing up the tree
      node.parentId = parent.nodeId;

      // if not provided set selectable default value
      if (!node.hasOwnProperty('selectable')) {
        node.selectable = true;
      }

      // where provided we should preserve states
      node.state = node.state || {};

      // set checked state; unless set always false
      if (!node.state.hasOwnProperty('checked')) {
        node.state.checked = false;
      }

      // set enabled state; unless set always false
      if (!node.state.hasOwnProperty('disabled')) {
        node.state.disabled = false;
      }

      // set expanded state; if not provided based on levels
      if (!node.state.hasOwnProperty('expanded')) {
        if (!node.state.disabled &&
          (level < _this.options.levels) &&
          (node.nodes && node.nodes.length > 0)) {
          node.state.expanded = true;
        }
        else {
          node.state.expanded = false;
        }
      }

      // set selected state; unless set always false
      if (!node.state.hasOwnProperty('selected')) {
        node.state.selected = false;
      }

      // index nodes in a flattened structure for use later
      _this.nodes.push(node);

      // recurse child nodes and transverse the tree
      if (node.nodes) {
        _this.setInitialStates(node, level);
      }
    });
  };

  Tree.prototype.clickHandler = function (event) {

    if (!this.options.enableLinks) event.preventDefault();

    var target = $(event.target);
    var node = this.findNode(target);
    if (!node || node.state.disabled) return;

    var classList = target.attr('class') ? target.attr('class').split(' ') : [];
    if ((classList.indexOf('expand-icon') !== -1)) {

      this.toggleExpandedState(node, _default.options);
      this.render();
    }
    else if ((classList.indexOf('check-icon') !== -1)) {

      this.toggleCheckedState(node, _default.options);
      this.render();
    }
    else {

      if (node.selectable) {
        this.toggleSelectedState(node, _default.options);
      } else {
        this.toggleExpandedState(node, _default.options);
      }

      this.render();
    }
  };

  // Looks up the DOM for the closest parent list item to retrieve the
  // data attribute nodeid, which is used to lookup the node in the flattened structure.
  Tree.prototype.findNode = function (target) {

    var nodeId = target.closest('li.list-group-item').attr('data-nodeid');
    var node = this.nodes[nodeId];

    if (!node) {
      console.log('Error: node does not exist');
    }
    return node;
  };
  // 得出 他在这里做的,然后再找这个方法 gz
  Tree.prototype.toggleExpandedState = function (node, options) {
    if (!node) return;
    this.setExpandedState(node, !node.state.expanded, options);
  };
  // 看看 这个方法在哪里调用的 gz
  Tree.prototype.setExpandedState = function (node, state, options) {

    if (state === node.state.expanded) return;

    if (state && node.nodes) {

      // Expand a node
      node.state.expanded = true;
      if (!options.silent) {
        // 然后再看事件源怎么处理的 gz
        this.$element.trigger('nodeExpanded', $.extend(true, {}, node));
      }
    }
    else if (!state) {

      // Collapse a node
      node.state.expanded = false;
      if (!options.silent) {
        this.$element.trigger('nodeCollapsed', $.extend(true, {}, node));
      }

      // Collapse child nodes
      if (node.nodes && !options.ignoreChildren) {
        $.each(node.nodes, $.proxy(function (index, node) {
          this.setExpandedState(node, false, options);
        }, this));
      }
    }
  };

  Tree.prototype.toggleSelectedState = function (node, options) {
    if (!node) return;
    this.setSelectedState(node, !node.state.selected, options);
  };

  Tree.prototype.setSelectedState = function (node, state, options) {

    if (state === node.state.selected) return;

    if (state) {

      // If multiSelect false, unselect previously selected
      if (!this.options.multiSelect) {
        $.each(this.findNodes('true', 'g', 'state.selected'), $.proxy(function (index, node) {
          this.setSelectedState(node, false, options);
        }, this));
      }

      // Continue selecting node
      node.state.selected = true;
      if (!options.silent) {
        this.$element.trigger('nodeSelected', $.extend(true, {}, node));
      }
    }
    else {

      // Unselect node
      node.state.selected = false;
      if (!options.silent) {
        this.$element.trigger('nodeUnselected', $.extend(true, {}, node));
      }
    }
  };

  Tree.prototype.toggleCheckedState = function (node, options) {
    if (!node) return;
    this.setCheckedState(node, !node.state.checked, options);
  };

  Tree.prototype.setCheckedState = function (node, state, options) {

    if (state === node.state.checked) return;

    if (state) {

      // Check node
      node.state.checked = true;

      if (!options.silent) {
        this.$element.trigger('nodeChecked', $.extend(true, {}, node));
      }
    }
    else {

      // Uncheck node
      node.state.checked = false;
      if (!options.silent) {
        this.$element.trigger('nodeUnchecked', $.extend(true, {}, node));
      }
    }
  };

  Tree.prototype.setDisabledState = function (node, state, options) {

    if (state === node.state.disabled) return;

    if (state) {

      // Disable node
      node.state.disabled = true;

      // Disable all other states
      this.setExpandedState(node, false, options);
      this.setSelectedState(node, false, options);
      this.setCheckedState(node, false, options);

      if (!options.silent) {
        this.$element.trigger('nodeDisabled', $.extend(true, {}, node));
      }
    }
    else {

      // Enabled node
      node.state.disabled = false;
      if (!options.silent) {
        this.$element.trigger('nodeEnabled', $.extend(true, {}, node));
      }
    }
  };


  //拖拽相关事件绑定 gz



  Tree.prototype.render = function () {

    if (!this.initialized) {

      // Setup first time only components
      this.$element.addClass(pluginName);
      this.$wrapper = $(this.template.list);

      this.injectStyle();

      this.initialized = true;
    }

    this.$element.empty().append(this.$wrapper.empty());

    // Build tree
    this.buildTree(this.tree, 0);
  };

  // Starting from the root node, and recursing down the
  // structure we build the tree one node at a time
  Tree.prototype.buildTree = function (nodes, level) {

    if (!nodes) return;
    level += 1;

    var _this = this;

    $.each(nodes, function addNodes(id, node) {
      var treeItem = $(_this.template.item)
        .addClass('node-' + _this.elementId)
        .addClass(node.state.checked ? 'node-checked' : '')
        .addClass(node.state.disabled ? 'node-disabled' : '')
        .addClass(node.state.selected ? 'node-selected' : '')
        .addClass(node.searchResult ? 'search-result' : '')
        .attr('data-nodeid', node.nodeId)
        // .attr('data-parent-code', node.parentCode) //  这个是当前节点的父级节点的code,主要是为了实现父级拖拽的时候可以把子级同时移动 gz。
        .attr('data-level', level) // 这个是当前节点的层级数据,之前有就可以不写 gz。
        .attr('draggable', true) // 激发拖拽 gz
        // .attr("ondrop", "drop(event)") // 结束拖拽 gz
        // .attr("ondragover", "allowDrop(event)") // 拖拽中 gz
        // .attr("ondragstart", _this.options.onDragStart) // 目标开始拖拽事件 gz
        // .attr("ondragenter", "dragEnter(event)") //当拖拽对象进入投放区时触发
        // .attr("ondragleave", "dragLeave(event)") //元素被拖出了投放区时触发
        .attr('style', _this.buildStyleOverride(node));
      console.log(_this);
      // 主要是这几个函数,你通过_this 就是可以拿到插件内的数据了
      $(treeItem).on("dragstart", function (event) {
        console.log("drop-开始拖拽", event, _this);
        // _this.options.onDragStart(event, _this)
      })
      $(treeItem).on("drop", function (event) {
        console.log("drop-结束拖拽", event, _this);
        // _this.options.onDragStart(event, _this)
      })
      $(treeItem).on("dragover", function (event) {
        event.preventDefault(); // 拖拽中
        // _this.options.onDragStart(event, _this)
      })
      $(treeItem).on("dragenter", function (event) {
        console.log("dragenter"); // 当拖拽对象进入投放区时触发
        // _this.options.onDragStart(event, _this)
      })
      $(treeItem).on("dragleave", function (event) {
        console.log("dragleave");// 元素被拖出了投放区时触发
        // _this.options.onDragStart(event, _this)
      })

      // Add indent/spacer to mimic tree structure
      for (var i = 0; i < (level - 1); i++) {
        treeItem.append(_this.template.indent);
      }

      // Add expand, collapse or empty spacer icons
      var classList = [];
      if (node.nodes) {
        classList.push('expand-icon');
        if (node.state.expanded) {
          classList.push(_this.options.collapseIcon);
        }
        else {
          classList.push(_this.options.expandIcon);
        }
      }
      else {
        classList.push(_this.options.emptyIcon);
      }

      treeItem
        .append($(_this.template.icon)
          .addClass(classList.join(' '))
        );


      // Add node icon
      if (_this.options.showIcon) {

        var classList = ['node-icon'];

        classList.push(node.icon || _this.options.nodeIcon);
        if (node.state.selected) {
          classList.pop();
          classList.push(node.selectedIcon || _this.options.selectedIcon ||
            node.icon || _this.options.nodeIcon);
        }

        treeItem
          .append($(_this.template.icon)
            .addClass(classList.join(' '))
          );
      }

      // Add check / unchecked icon
      if (_this.options.showCheckbox) {

        var classList = ['check-icon'];
        if (node.state.checked) {
          classList.push(_this.options.checkedIcon);
        }
        else {
          classList.push(_this.options.uncheckedIcon);
        }

        treeItem
          .append($(_this.template.icon)
            .addClass(classList.join(' '))
          );
      }

      // Add text
      if (_this.options.enableLinks) {
        // Add hyperlink
        treeItem
          .append($(_this.template.link)
            .attr('href', node.href)
            .append(node.text)
          );
      }
      else {
        // otherwise just text
        treeItem
          .append(node.text);
      }

      // Add tags as badges
      if (_this.options.showTags && node.tags) {
        $.each(node.tags, function addTag(id, tag) {
          treeItem
            .append($(_this.template.badge)
              .append(tag)
            );
        });
      }

      // Add item to the tree
      _this.$wrapper.append(treeItem);

      // Recursively add child ndoes
      if (node.nodes && node.state.expanded && !node.state.disabled) {
        return _this.buildTree(node.nodes, level);
      }
    });
  };

  Tree.prototype.onDragStart = function (event, data) {
    console.log("---ondragstart", event, data);
  }

  // Define any node level style override for
  // 1. selectedNode
  // 2. node|data assigned color overrides
  Tree.prototype.buildStyleOverride = function (node) {

    if (node.state.disabled) return '';

    var color = node.color;
    var backColor = node.backColor;

    if (this.options.highlightSelected && node.state.selected) {
      if (this.options.selectedColor) {
        color = this.options.selectedColor;
      }
      if (this.options.selectedBackColor) {
        backColor = this.options.selectedBackColor;
      }
    }

    if (this.options.highlightSearchResults && node.searchResult && !node.state.disabled) {
      if (this.options.searchResultColor) {
        color = this.options.searchResultColor;
      }
      if (this.options.searchResultBackColor) {
        backColor = this.options.searchResultBackColor;
      }
    }

    return 'color:' + color +
      ';background-color:' + backColor + ';';
  };

  // Add inline style into head
  Tree.prototype.injectStyle = function () {

    if (this.options.injectStyle && !document.getElementById(this.styleId)) {
      $('<style type="text/css" id="' + this.styleId + '"> ' + this.buildStyle() + ' </style>').appendTo('head');
    }
  };

  // Construct trees style based on user options
  Tree.prototype.buildStyle = function () {

    var style = '.node-' + this.elementId + '{';

    if (this.options.color) {
      style += 'color:' + this.options.color + ';';
    }

    if (this.options.backColor) {
      style += 'background-color:' + this.options.backColor + ';';
    }

    if (!this.options.showBorder) {
      style += 'border:none;';
    }
    else if (this.options.borderColor) {
      style += 'border:1px solid ' + this.options.borderColor + ';';
    }
    style += '}';

    if (this.options.onhoverColor) {
      style += '.node-' + this.elementId + ':not(.node-disabled):hover{' +
        'background-color:' + this.options.onhoverColor + ';' +
        '}';
    }

    return this.css + style;
  };

  Tree.prototype.template = {
    list: '<ul class="list-group"></ul>',
    item: '<li class="list-group-item"></li>',
    indent: '<span class="indent"></span>',
    icon: '<span class="icon"></span>',
    link: '<a href="#" style="color:inherit;"></a>',
    badge: '<span class="badge"></span>'
  };

  Tree.prototype.css = '.treeview .list-group-item{cursor:pointer}.treeview span.indent{margin-left:10px;margin-right:10px}.treeview span.icon{width:12px;margin-right:5px}.treeview .node-disabled{color:silver;cursor:not-allowed}'


  /**
    Returns a single node object that matches the given node id.
    @param {Number} nodeId - A node's unique identifier
    @return {Object} node - Matching node
  */
  Tree.prototype.getNode = function (nodeId) {
    return this.nodes[nodeId];
  };

  /**
    Returns the parent node of a given node, if valid otherwise returns undefined.
    @param {Object|Number} identifier - A valid node or node id
    @returns {Object} node - The parent node
  */
  Tree.prototype.getParent = function (identifier) {
    var node = this.identifyNode(identifier);
    return this.nodes[node.parentId];
  };

  /**
    Returns an array of sibling nodes for a given node, if valid otherwise returns undefined.
    @param {Object|Number} identifier - A valid node or node id
    @returns {Array} nodes - Sibling nodes
  */
  Tree.prototype.getSiblings = function (identifier) {
    var node = this.identifyNode(identifier);
    var parent = this.getParent(node);
    var nodes = parent ? parent.nodes : this.tree;
    return nodes.filter(function (obj) {
      return obj.nodeId !== node.nodeId;
    });
  };

  /**
    Returns an array of selected nodes.
    @returns {Array} nodes - Selected nodes
  */
  Tree.prototype.getSelected = function () {
    return this.findNodes('true', 'g', 'state.selected');
  };

  /**
    Returns an array of unselected nodes.
    @returns {Array} nodes - Unselected nodes
  */
  Tree.prototype.getUnselected = function () {
    return this.findNodes('false', 'g', 'state.selected');
  };

  /**
    Returns an array of expanded nodes.
    @returns {Array} nodes - Expanded nodes
  */
  Tree.prototype.getExpanded = function () {
    return this.findNodes('true', 'g', 'state.expanded');
  };

  /**
    Returns an array of collapsed nodes.
    @returns {Array} nodes - Collapsed nodes
  */
  Tree.prototype.getCollapsed = function () {
    return this.findNodes('false', 'g', 'state.expanded');
  };

  /**
    Returns an array of checked nodes.
    @returns {Array} nodes - Checked nodes
  */
  Tree.prototype.getChecked = function () {
    return this.findNodes('true', 'g', 'state.checked');
  };

  /**
    Returns an array of unchecked nodes.
    @returns {Array} nodes - Unchecked nodes
  */
  Tree.prototype.getUnchecked = function () {
    return this.findNodes('false', 'g', 'state.checked');
  };

  /**
    Returns an array of disabled nodes.
    @returns {Array} nodes - Disabled nodes
  */
  Tree.prototype.getDisabled = function () {
    return this.findNodes('true', 'g', 'state.disabled');
  };

  /**
    Returns an array of enabled nodes.
    @returns {Array} nodes - Enabled nodes
  */
  Tree.prototype.getEnabled = function () {
    return this.findNodes('false', 'g', 'state.disabled');
  };


  /**
    Set a node state to selected
    @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
    @param {optional Object} options
  */
  Tree.prototype.selectNode = function (identifiers, options) {
    this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
      this.setSelectedState(node, true, options);
    }, this));

    this.render();
  };

  /**
    Set a node state to unselected
    @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
    @param {optional Object} options
  */
  Tree.prototype.unselectNode = function (identifiers, options) {
    this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
      this.setSelectedState(node, false, options);
    }, this));

    this.render();
  };

  /**
    Toggles a node selected state; selecting if unselected, unselecting if selected.
    @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
    @param {optional Object} options
  */
  Tree.prototype.toggleNodeSelected = function (identifiers, options) {
    this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
      this.toggleSelectedState(node, options);
    }, this));

    this.render();
  };


  /**
    Collapse all tree nodes
    @param {optional Object} options
  */
  Tree.prototype.collapseAll = function (options) {
    var identifiers = this.findNodes('true', 'g', 'state.expanded');
    this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
      this.setExpandedState(node, false, options);
    }, this));

    this.render();
  };

  /**
    Collapse a given tree node
    @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
    @param {optional Object} options
  */
  Tree.prototype.collapseNode = function (identifiers, options) {
    this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
      this.setExpandedState(node, false, options);
    }, this));

    this.render();
  };

  /**
    Expand all tree nodes
    @param {optional Object} options
  */
  Tree.prototype.expandAll = function (options) {
    options = $.extend({}, _default.options, options);

    if (options && options.levels) {
      this.expandLevels(this.tree, options.levels, options);
    }
    else {
      var identifiers = this.findNodes('false', 'g', 'state.expanded');
      this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
        this.setExpandedState(node, true, options);
      }, this));
    }

    this.render();
  };

  /**
    Expand a given tree node
    @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
    @param {optional Object} options
  */
  Tree.prototype.expandNode = function (identifiers, options) {
    this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
      this.setExpandedState(node, true, options);
      if (node.nodes && (options && options.levels)) {
        this.expandLevels(node.nodes, options.levels - 1, options);
      }
    }, this));

    this.render();
  };

  Tree.prototype.expandLevels = function (nodes, level, options) {
    options = $.extend({}, _default.options, options);

    $.each(nodes, $.proxy(function (index, node) {
      this.setExpandedState(node, (level > 0) ? true : false, options);
      if (node.nodes) {
        this.expandLevels(node.nodes, level - 1, options);
      }
    }, this));
  };

  /**
    Reveals a given tree node, expanding the tree from node to root.
    @param {Object|Number|Array} identifiers - A valid node, node id or array of node identifiers
    @param {optional Object} options
  */
  Tree.prototype.revealNode = function (identifiers, options) {
    this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
      var parentNode = this.getParent(node);
      while (parentNode) {
        this.setExpandedState(parentNode, true, options);
        parentNode = this.getParent(parentNode);
      };
    }, this));

    this.render();
  };

  /**
    Toggles a nodes expanded state; collapsing if expanded, expanding if collapsed.
    @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
    @param {optional Object} options
  */
  Tree.prototype.toggleNodeExpanded = function (identifiers, options) {
    this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
      this.toggleExpandedState(node, options);
    }, this));

    this.render();
  };


  /**
    Check all tree nodes
    @param {optional Object} options
  */
  Tree.prototype.checkAll = function (options) {
    var identifiers = this.findNodes('false', 'g', 'state.checked');
    this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
      this.setCheckedState(node, true, options);
    }, this));

    this.render();
  };

  /**
    Check a given tree node
    @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
    @param {optional Object} options
  */
  Tree.prototype.checkNode = function (identifiers, options) {
    this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
      this.setCheckedState(node, true, options);
    }, this));

    this.render();
  };

  /**
    Uncheck all tree nodes
    @param {optional Object} options
  */
  Tree.prototype.uncheckAll = function (options) {
    var identifiers = this.findNodes('true', 'g', 'state.checked');
    this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
      this.setCheckedState(node, false, options);
    }, this));

    this.render();
  };

  /**
    Uncheck a given tree node
    @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
    @param {optional Object} options
  */
  Tree.prototype.uncheckNode = function (identifiers, options) {
    this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
      this.setCheckedState(node, false, options);
    }, this));

    this.render();
  };

  /**
    Toggles a nodes checked state; checking if unchecked, unchecking if checked.
    @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
    @param {optional Object} options
  */
  Tree.prototype.toggleNodeChecked = function (identifiers, options) {
    this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
      this.toggleCheckedState(node, options);
    }, this));

    this.render();
  };


  /**
    Disable all tree nodes
    @param {optional Object} options
  */
  Tree.prototype.disableAll = function (options) {
    var identifiers = this.findNodes('false', 'g', 'state.disabled');
    this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
      this.setDisabledState(node, true, options);
    }, this));

    this.render();
  };

  /**
    Disable a given tree node
    @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
    @param {optional Object} options
  */
  Tree.prototype.disableNode = function (identifiers, options) {
    this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
      this.setDisabledState(node, true, options);
    }, this));

    this.render();
  };

  /**
    Enable all tree nodes
    @param {optional Object} options
  */
  Tree.prototype.enableAll = function (options) {
    var identifiers = this.findNodes('true', 'g', 'state.disabled');
    this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
      this.setDisabledState(node, false, options);
    }, this));

    this.render();
  };

  /**
    Enable a given tree node
    @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
    @param {optional Object} options
  */
  Tree.prototype.enableNode = function (identifiers, options) {
    this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
      this.setDisabledState(node, false, options);
    }, this));

    this.render();
  };

  /**
    Toggles a nodes disabled state; disabling is enabled, enabling if disabled.
    @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
    @param {optional Object} options
  */
  Tree.prototype.toggleNodeDisabled = function (identifiers, options) {
    this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
      this.setDisabledState(node, !node.state.disabled, options);
    }, this));

    this.render();
  };


  /**
    Common code for processing multiple identifiers
  */
  Tree.prototype.forEachIdentifier = function (identifiers, options, callback) {

    options = $.extend({}, _default.options, options);

    if (!(identifiers instanceof Array)) {
      identifiers = [identifiers];
    }

    $.each(identifiers, $.proxy(function (index, identifier) {
      callback(this.identifyNode(identifier), options);
    }, this));
  };

  /*
    Identifies a node from either a node id or object
  */
  Tree.prototype.identifyNode = function (identifier) {
    return ((typeof identifier) === 'number') ?
      this.nodes[identifier] :
      identifier;
  };

  /**
    Searches the tree for nodes (text) that match given criteria
    @param {String} pattern - A given string to match against
    @param {optional Object} options - Search criteria options
    @return {Array} nodes - Matching nodes
  */
  Tree.prototype.search = function (pattern, options) {
    options = $.extend({}, _default.searchOptions, options);

    this.clearSearch({ render: false });

    var results = [];
    if (pattern && pattern.length > 0) {

      if (options.exactMatch) {
        pattern = '^' + pattern + '$';
      }

      var modifier = 'g';
      if (options.ignoreCase) {
        modifier += 'i';
      }

      results = this.findNodes(pattern, modifier);

      // Add searchResult property to all matching nodes
      // This will be used to apply custom styles
      // and when identifying result to be cleared
      $.each(results, function (index, node) {
        node.searchResult = true;
      })
    }

    // If revealResults, then render is triggered from revealNode
    // otherwise we just call render.
    if (options.revealResults) {
      this.revealNode(results);
    }
    else {
      this.render();
    }

    this.$element.trigger('searchComplete', $.extend(true, {}, results));

    return results;
  };

  /**
    Clears previous search results
  */
  Tree.prototype.clearSearch = function (options) {

    options = $.extend({}, { render: true }, options);

    var results = $.each(this.findNodes('true', 'g', 'searchResult'), function (index, node) {
      node.searchResult = false;
    });

    if (options.render) {
      this.render();
    }

    this.$element.trigger('searchCleared', $.extend(true, {}, results));
  };

  /**
    Find nodes that match a given criteria
    @param {String} pattern - A given string to match against
    @param {optional String} modifier - Valid RegEx modifiers
    @param {optional String} attribute - Attribute to compare pattern against
    @return {Array} nodes - Nodes that match your criteria
  */
  Tree.prototype.findNodes = function (pattern, modifier, attribute) {

    modifier = modifier || 'g';
    attribute = attribute || 'text';

    var _this = this;
    return $.grep(this.nodes, function (node) {
      var val = _this.getNodeValue(node, attribute);
      if (typeof val === 'string') {
        return val.match(new RegExp(pattern, modifier));
      }
    });
  };

  /**
    Recursive find for retrieving nested attributes values
    All values are return as strings, unless invalid
    @param {Object} obj - Typically a node, could be any object
    @param {String} attr - Identifies an object property using dot notation
    @return {String} value - Matching attributes string representation
  */
  Tree.prototype.getNodeValue = function (obj, attr) {
    var index = attr.indexOf('.');
    if (index > 0) {
      var _obj = obj[attr.substring(0, index)];
      var _attr = attr.substring(index + 1, attr.length);
      return this.getNodeValue(_obj, _attr);
    }
    else {
      if (obj.hasOwnProperty(attr)) {
        return obj[attr].toString();
      }
      else {
        return undefined;
      }
    }
  };

  var logError = function (message) {
    if (window.console) {
      window.console.error(message);
    }
  };

  // Prevent against multiple instantiations,
  // handle updates and method calls
  $.fn[pluginName] = function (options, args) {

    var result;

    this.each(function () {
      var _this = $.data(this, pluginName);
      if (typeof options === 'string') {
        if (!_this) {
          logError('Not initialized, can not call method : ' + options);
        }
        else if (!$.isFunction(_this[options]) || options.charAt(0) === '_') {
          logError('No such method : ' + options);
        }
        else {
          if (!(args instanceof Array)) {
            args = [args];
          }
          result = _this[options].apply(_this, args);
        }
      }
      else if (typeof options === 'boolean') {
        result = _this;
      }
      else {
        $.data(this, pluginName, new Tree(this, $.extend(true, {}, options)));
      }
    });

    return result || this;
  };

})(jQuery, window, document);

 

posted on 2022-04-24 18:06  水行者  阅读(60)  评论(0编辑  收藏  举报

导航