This article for the analysis of el-Tree source code of the second article, in the last article analysis of the tree component required tool method and tree Node (Node class) implementation, this article analysis of tree-store.js file method. By the end of this article you will know (1) what methods a tree class (TreeStore) should have, (2) how the whole tree and its nodes communicate, and (3) how recursion is used in tree components

Tree-store. js file analysis

Tree-store. js defines methods and data related to the whole tree, and the content is also more, let’s analyze one by one.

TreeStore class constructor method

constructor(options) {
  this.currentNode = null;
  this.currentNodeKey = null;

  for (let option in options) {
    if (options.hasOwnProperty(option)) {
      this[option] = options[option]; }}this.nodesMap = {};

  this.root = new Node({
    data: this.data,
    store: this
  });

  if (this.lazy && this.load) {
    const loadFn = this.load;
    loadFn(this.root, (data) = > {
      this.root.doCreateChildren(data);
      this._initDefaultCheckedNodes();
    });
  } else {
    this._initDefaultCheckedNodes(); }}Copy the code

The constructor method does the initialization. Initialize the current node and the current node’s key is empty; Assign properties from objects in Options to TreeStore; Declare a nodesMap, which holds the current number of nodes. When the registerNode method is called, a new key/value pair is assigned to nodesMap. The root attribute refers to an instance of the Node class and represents the root Node of the tree. If the lazy attribute is true and the load method exists, the child node is called to load, the child node is created, and then initialized by default; Otherwise, if lazy is false, it is initialized by default.

2 Filter method of TreeStore class

filter(value) {
  const filterNodeMethod = this.filterNodeMethod;
  const lazy = this.lazy;
  const traverse = function(node) {
    const childNodes = node.root ? node.root.childNodes : node.childNodes;

    childNodes.forEach((child) = > {
      child.visible = filterNodeMethod.call(child, value, child.data, child);

      traverse(child);
    });

    if(! node.visible && childNodes.length) {let allHidden = true; allHidden = ! childNodes.some(child= > child.visible);

      if (node.root) {
        node.root.visible = allHidden === false;
      } else {
        node.visible = allHidden === false; }}if(! value)return;

    if(node.visible && ! node.isLeaf && ! lazy) node.expand(); }; traverse(this);
}
Copy the code

FilterNodeMethod is the method for filtering nodes, as described in the documentation. See the following figure:

Traverse method is a recursive method, which first gets the child nodes, then traverses the child nodes, and calls the filterNodeMethod method on the child node. The three parameters are value representing the filter value, child.data representing the data of the child node, respectively. Child represents the child node itself. We know the meaning of the three parameters filterNodeMethod by reading the source code, which is not specified in the official documentation.

The filterNodeMethod method returns true or false, which is assigned to the Visible property of the child. Traverse methods are recursively called to process child nodes. Then judge that if the visible attribute of a node is false and the length of childNodes is not 0 (with childNodes), then set allHidden to true to mark whether the childNodes should be hidden. After marking, the value of allHidden should be corrected according to whether the childNodes are not hidden. We use some of the array method. Then fix node.root or Node visible properties according to allHidden.

After determining the Visible property of a node, call the expand method to expand the child nodes of the node if visible is true and is not a leaf node or lazy loading. Let’s look at how visible is used in tree-node.vue:

3 setData method of TreeStore class

setData(newVal) {
  constinstanceChanged = newVal ! = =this.root.data;
  if (instanceChanged) {
    this.root.setData(newVal);
    this._initDefaultCheckedNodes();
  } else {
    this.root.updateChildren(); }}Copy the code

SetData is a setter that’s called when you assign a value to the data property. First check whether newValue is equal to root’s data (whether it is an instance, comparing the reference address). If not, call root’s setData method (defined in Node.js) and initialize the default selected node. If they are equal, the updateChildren method is called to update them.

4 getNode method of the TreeStore class

getNode(data) {
  if (data instanceof Node) return data;
  const key = typeofdata ! = ='object' ? data : getNodeKey(this.key, data);
  return this.nodesMap[key] || null;
}
Copy the code

Returns a Node of type Node based on the data argument. Return data if data is an instance of type Node. If not, then determine whether data is an object or not. If not, then assign the key directly. If data is an object, the getNodeKey method defined by util.js is called with the key property and data as arguments. Once the key is determined, the node with the given key value is searched from the nodesMap.

The insertBefore method of the TreeStore class

insertBefore(data, refData) {
  const refNode = this.getNode(refData);
  refNode.parent.insertBefore({ data }, refNode);
}
Copy the code

InsertBefore receives two parameters data representing the data to be inserted into the new node, and refData is the target location node inserted before whom. First call the getNode method to get the target location Node, then get the parent Node of the target location Node, call the insertBefore method (Node class insertBefore method).

InsertAfter method of TreeStore class

insertAfter(data, refData) {
  const refNode = this.getNode(refData);
  refNode.parent.insertAfter({ data }, refNode);
}
Copy the code

InsertAfter is similar to insertBefore, and ultimately calls the insertAfter method of the Node class.

7 Remove method of TreeStore class

remove(data) {
  const node = this.getNode(data);

  if (node && node.parent) {
    if (node === this.currentNode) {
      this.currentNode = null; } node.parent.removeChild(node); }}Copy the code

Find the corresponding node node according to data, and then find its parent node. If the parent node exists, you can delete it. If the node to be deleted is a current node, set the current node to NULL. Finally, the parent node calls removeChild to remove the node.

8 Append method of TreeStore class

append(data, parentData) {
  const parentNode = parentData ? this.getNode(parentData) : this.root;

  if(parentNode) { parentNode.insertChild({ data }); }}Copy the code

ParentNode is found based on the parentData argument, and parentNode calls insertChild.

The _initDefaultCheckedNodes method of the TreeStore class

_initDefaultCheckedNodes() {
  const defaultCheckedKeys = this.defaultCheckedKeys || [];
  const nodesMap = this.nodesMap;

  defaultCheckedKeys.forEach((checkedKey) = > {
    const node = nodesMap[checkedKey];

    if (node) {
      node.setChecked(true,!this.checkStrictly); }}); }Copy the code

_initDefaultCheckedNodes Initializes the nodes that are selected by default. Obtain the key value of the selected node by default. Then the key values are iterated and the corresponding nodes are found in nodesMap according to the key values. Call the setChecked method when the node is found.

The _initDefaultCheckedNode method of the TreeStore class

_initDefaultCheckedNode(node) {
  const defaultCheckedKeys = this.defaultCheckedKeys || [];

  if(defaultCheckedKeys.indexOf(node.key) ! = = -1) {
    node.setChecked(true,!this.checkStrictly); }}Copy the code

_initDefaultCheckedNode Is used to initialize the check status of a node. This method is not used.

11 setDefaultCheckedKey method of the TreeStore class

setDefaultCheckedKey(newVal) {
  if(newVal ! = =this.defaultCheckedKeys) {
    this.defaultCheckedKeys = newVal;
    this._initDefaultCheckedNodes(); }}Copy the code

SetDefaultCheckedKey is used to set the default check, calling the _initDefaultCheckedNodes method.

RegisterNode method of the TreeStore class

registerNode(node) {
  const key = this.key;
  if(! key || ! node || ! node.data)return;

  const nodeKey = node.key;
  if(nodeKey ! = =undefined) this.nodesMap[node.key] = node;
}
Copy the code

The registerNode method is used to register a node, which is recorded in a nodesMap.

13 TreeStore deregisterNode method

deregisterNode(node) {
  const key = this.key;
  if(! key || ! node || ! node.data)return;

  node.childNodes.forEach(child= > {
    this.deregisterNode(child);
  });

  delete this.nodesMap[node.key];
}
Copy the code

DeregisterNode is a recursive method used to unregister a node. When a node has children, each child is called recursively. The essence of unregistering a node is to remove it from nodesMap.

14 The getCheckedNodes method of the TreeStore class

getCheckedNodes(leafOnly = false, includeHalfChecked = false) {
  const checkedNodes = [];
  const traverse = function(node) {
    const childNodes = node.root ? node.root.childNodes : node.childNodes;

    childNodes.forEach((child) = > {
      if((child.checked || (includeHalfChecked && child.indeterminate)) && (! leafOnly || (leafOnly && child.isLeaf))) { checkedNodes.push(child.data); } traverse(child); }); }; traverse(this);

  return checkedNodes;
}
Copy the code

GetCheckedNodes Is used to get selected nodes. If the root node of the current node exists, it is preferred that childNodes of the root node of the current node be traversed. If the root node of the current node does not exist, childNodes of the current node be traversed. For each element in childNodes, the judgment conditions are complicated. We can understand them from two aspects :(1) check the status; (2) consider leaf nodes. What is checked state? The state of the child is undetermined when the child’s checked attribute is true or if half-selection is allowed. If leafOnly is false or false and the current node happens to be a leaf node, the condition is met.

The getCheckedKeys method of the TreeStore class

getCheckedKeys(leafOnly = false) {
  return this.getCheckedNodes(leafOnly).map((data) = > (data || {})[this.key]);
}
Copy the code

GetCheckedKeys Gets the key property of the selected node. First call getCheckedNodes to get the selected nodes, then use the Map method to get the key property of the node. This key is the node-key specified when using the El-tree component. See below:

The figure above shows that the key specified by TreeStore is derived from nodeKey.

The above figure shows that nodeKey is an attribute (props) defined by el-Tree.

16 getHalfCheckedNodes method of the TreeStore class

getHalfCheckedNodes() {
  const nodes = [];
  const traverse = function(node) {
    const childNodes = node.root ? node.root.childNodes : node.childNodes;

    childNodes.forEach((child) = > {
      if (child.indeterminate) {
        nodes.push(child.data);
      }

      traverse(child);
    });
  };

  traverse(this);

  return nodes;
}
Copy the code

GetHalfCheckedNodes Nodes used to get half-selected status. Inside the function is defined a recursive traverse. Similar to getCheckedNodes, this is the childNodes of the current node’s parent node first traversed. If the indeTerminate attribute of an element in childNodes is true, it is put into the result union and the result is returned.

TreeStore getHalfCheckedKeys

getHalfCheckedKeys() {
  return this.getHalfCheckedNodes().map((data) = > (data || {})[this.key]);
}
Copy the code

GetCheckedKeys gets the key property of a semi-selected node. The logic is exactly the same as the getCheckedKeys method.

18 _getAllNodes method of the TreeStore class

_getAllNodes() {
  const allNodes = [];
  const nodesMap = this.nodesMap;
  for (let nodeKey in nodesMap) {
    if(nodesMap.hasOwnProperty(nodeKey)) { allNodes.push(nodesMap[nodeKey]); }}return allNodes;
}
Copy the code

_getAllNodes is used to get all nodes of the current tree. Iterate over the key in nodesMap and put the value into the result combined with allNodes.

19 updateChildren method of the TreeStore class

updateChildren(key, data) {
  const node = this.nodesMap[key];
  if(! node)return;
  const childNodes = node.childNodes;
  for (let i = childNodes.length - 1; i >= 0; i--) {
    const child = childNodes[i];
    this.remove(child.data);
  }
  for (let i = 0, j = data.length; i < j; i++) {
    const child = data[i];
    this.append(child, node.data); }}Copy the code

UpdateChildren Updates the child nodes of a node. Get node from nodesMap based on key value, return node if node does not exist. Get the childNodes of Node, followed by two for loops. The first for loop is used to delete the original node, and the second for loop is used to create new nodes. The two for loops call the remove and Append methods analyzed above, respectively. The entire method call is shown as follows:

20 TreeStore _setCheckedKeys method

_setCheckedKeys(key, leafOnly = false, checkedKeys) {
  const allNodes = this._getAllNodes().sort((a, b) = > b.level - a.level);
  const cache = Object.create(null);
  const keys = Object.keys(checkedKeys);
  allNodes.forEach(node= > node.setChecked(false.false));
  for (let i = 0, j = allNodes.length; i < j; i++) {
    const node = allNodes[i];
    const nodeKey = node.data[key].toString();
    let checked = keys.indexOf(nodeKey) > -1;
    if(! checked) {if(node.checked && ! cache[nodeKey]) { node.setChecked(false.false);
      }
      continue;
    }

    let parent = node.parent;
    while (parent && parent.level > 0) {
      cache[parent.data[key]] = true;
      parent = parent.parent;
    }

    if (node.isLeaf || this.checkStrictly) {
      node.setChecked(true.false);
      continue;
    }
    node.setChecked(true.true);

    if (leafOnly) {
      node.setChecked(false.false);
      const traverse = function(node) {
        const childNodes = node.childNodes;
        childNodes.forEach((child) = > {
          if(! child.isLeaf) { child.setChecked(false.false); } traverse(child); }); }; traverse(node); }}}Copy the code

_setCheckedKeys is used to set the check of the node. Key is the key property of the node, and checkedKeys is the key object passed in (e.g. {1: true, 3: true}). We first call _getAllNodes to get allNodes, and then call sort to sort the nodes by their level value (that is, the child nodes are processed first) and assign the value to allNodes. An example of the sort method is as follows:

[1, 2, 3, 4, 5]. Sort ((a, b) = > b - a) / / [5, 4, 3, 2, 1)Copy the code

A cache object is then created, and cached is used to hold the ancestor node of the node being processed if the current node should be checked. Keys (checkedKeys) Extracts the key value of the checkedKeys argument as an array assigned to keys.

It then iterates through each of the allNodes, all initialized to unchecked. Next, each node in allNodes is iterated to check whether the key value of the node is in keys.

If the current node should not be selected, check whether its checked property is true and whether it has not been cached before. If the condition is true, the node node should be set to unselected and then enter the next loop. If the condition is not true and the next loop is entered, either Node.checked is false (the node was not checked originally) or the node is cached because its children should have been checked.

All ancestor nodes of the current node are cached if the current node should be selected.

The above is to determine whether the node should be selected. After the determination, the node should be set to be selected. When set, check whether the current node is a leaf node or not associated with child nodes, if so, setChecked’s second parameter deep passes false. Review the setChecked method signature:

setChecked(value, deep, recursion, passValue)
Copy the code

Finally, check was corrected according to leafOnly. If the code can be executed here, node is not a leaf node. First, set the check state of node to false, and then use recursive function traverse recursion to process child nodes.

The _setCheckedKeys method is quite complex in general. Here’s a picture of what this function does at a macro level:

21 setCheckedNodes method of the TreeStore class

setCheckedNodes(array, leafOnly = false) {
  const key = this.key;
  const checkedKeys = {};
  array.forEach((item) = > {
    checkedKeys[(item || {})[key]] = true;
  });

  this._setCheckedKeys(key, leafOnly, checkedKeys);
}
Copy the code

SetCheckedNodes is used to set the selected nodes. The parameter array is an array of nodes (of type Node), traversed to obtain the key value, stored in checkedKeys. Finally, call the _setCheckedKeys method that you just analyzed.

The setCheckedKeys method of TreeStore

setCheckedKeys(keys, leafOnly = false) {
  this.defaultCheckedKeys = keys;
  const key = this.key;
  const checkedKeys = {};
  keys.forEach((key) = > {
    checkedKeys[key] = true;
  });

  this._setCheckedKeys(key, leafOnly, checkedKeys);
}
Copy the code

The setCheckedKeys method is also used to check nodes. The checkedKeys argument is an array of keys for the nodes to check, iterating over each item as an attribute of the checkedKeys object. Finally, call the _setCheckedKeys method. Take a look at the documentation for this method:

The setDefaultExpandedKeys method of the TreeStore class

setDefaultExpandedKeys(keys) {
  keys = keys || [];
  this.defaultExpandedKeys = keys;

  keys.forEach((key) = > {
    const node = this.getNode(key);
    if (node) node.expand(null.this.autoExpandParent);
  });
}
Copy the code

SetDefaultExpandedKeys is used to set the nodes that are expanded by default. Each item in the array is iterated to get node by key. Node calls expand. This method is called when the defaultExpandedKeys property of el-Treee changes, as shown below:

The setChecked method of the TreeStore class

setChecked(data, checked, deep) {
  const node = this.getNode(data);

  if (node) {
    node.setChecked(!!checked, deep);
  }
}
Copy the code

The setChecked method is used to set the checked status of an object. The getNode method is called to get the node, and then the setChecked method of the node is called.

25 TreeStore class getCurrentNode method

getCurrentNode() {
  return this.currentNode;
}
Copy the code

GetCurrentNode is used to return the currently selected node.

26 TreeStore setCurrentNode method

setCurrentNode(currentNode) {
  const prevCurrentNode = this.currentNode;
  if (prevCurrentNode) {
    prevCurrentNode.isCurrent = false;
  }
  this.currentNode = currentNode;
  this.currentNode.isCurrent = true;
}
Copy the code

GetCurrentNode is used to set the current selected status of a node. Get the original prevCurrentNode and determine if it exists. If it does, set its isCurrent property to false. The currentNode parameter is then assigned to the currently selected node and its isCurrent property is set to true.

27 setUserCurrentNode method of TreeStore class

setUserCurrentNode(node) {
  const key = node[this.key];
  const currNode = this.nodesMap[key];
  this.setCurrentNode(currNode);
}
Copy the code

SetUserCurrentNode retrieves the key based on the node data passed in by the user. The node is then obtained from nodesMap based on its key value. Finally, the setCurrentNode method is called.

The setCurrentNodeKey method of the TreeStore class

setCurrentNodeKey(key) {
  if (key === null || key === undefined) {
    this.currentNode && (this.currentNode.isCurrent = false);
    this.currentNode = null;
    return;
  }
  const node = this.getNode(key);
  if (node) {
    this.setCurrentNode(node); }}Copy the code

SetCurrentNodeKey is used to set the current node by key. If key is null or undefined, the current node is cancelled. If the key has a value, get the node node based on the key and set node to the current node.

29 TreeSotre class summary

Tree-store. js, the TreeSotre class, has many methods. From the operation can be divided into filter node, insert node, delete node, update node; In terms of node status, it can be divided into updating the selected state of the node, updating the expansion state of the node, setting to the current node, etc. Let’s sum it up with a picture: