Element-ui Tree

Presents information in a clear hierarchy that can be expanded or collapsed.

Tree Attributes

parameter instructions type An optional value The default value
data Data presented Array
node-key Each tree node is used as a unique identifier. The entire tree should be unique String
show-checkbox Whether a node can be selected boolean false
default-expand-all Whether all nodes are expanded by default boolean fasle

The basic tree structure is shown

<template>
    <el-tree 
        :data="data" 
        :props="defaultProps" >
    </el-tree>
</template>
<script>
  export default {
    data() {
      return {
        data: [{
          label: 'level 1'.children: [{
            label: 'secondary 1-1'.children: [{
              label: 'triple the 1-1-1'}}]]}, {label: 'level 2',}],defaultProps: {
          children: 'children'.label: 'label'}}; }};</script>
Copy the code

The Tree structure

Implementation approach

  • Tree. Vue render tree
  • Tree-node. vue Renders subtrees
  • Tree-store. js State manager for the tree
  • Node.js defines the tree and methods of tree nodes

tree.vue

Tree.vue is the entry to the tree component

  • Receive the data passed by the props
  • Generate tree node root based on data
  • Render the tree according to root
Receive the data passed by the props
props: {
    // Tree to display the data
    data: {
        type: Array,},// Key of the tree node
    nodeKey: String./ / configuration items
    props: {
        type: Object.default: function() {
            return {
                // Specify that the subtree is the value of an object of the node, i.e. "children", representing the data of the child node
                children: 'children'.// Specify the node label as the value of an attribute of the node object
                label: 'label',}}},}Copy the code
Generate tree node root based on data
created() {
    // Determine whether the parent component is a tree for the child tree
    this.isTree = true;

    // Create a store for the tree
    this.store = new TreeStore({
        key: this.nodeKey,
        data: this.data,
        props: this.props,
    });

    // Start with the root
    this.root = this.store.root;
},
Copy the code

Render the tree according to root

<template>
    <div
        class="y-tree">
        <! How do subtrees render? Cyclic generation? Multiple layers of nesting? -->
        <y-tree-node
            v-for="(child) in root.childNodes"
            :node="child"
            :show-checkbox="showCheckbox"
            :key="getNodeKey(child)"
        >
        </y-tree-node>
    </div>
</template>
Copy the code

tree-node.vue

Tree-node. vue Renders subtrees

How to render subtrees of subtrees… Loop nested render subtrees? My original idea was loop nesting was just down, down; In fact, a loop nested a loop that goes down and then goes back up

  • Renders what the node displays
  • Renders the subtree of the node
Rendering subtree
<template>
    <div
        class="y-tree-node"
        :aria-expanded="expanded"
        @click.stop="handleClick"
    >
        <! -- 1. Render the display of the node content of the tree node, the el-tree node is divided into four parts (expand icon display, multi-select box, loading icon, node content display) dynamically calculate the offset :(node.level-1) * treec. indent + 'px', Create a ladder tree effect -->
        <div
            class="y-tree-node__content"
            :style="{'padding-left': (node.level - 1) * treeC.indent + 'px'}"
        >
            <! - boxes, @ click. Native. Stop stop bubbling events modifier - the official documentation: https://cn.vuejs.org/v2/guide/events.html - >
            <y-checkbox
                v-if="showCheckbox"
                v-model="node.checked"
                @click.native.stop
            >
            </y-checkbox>
            <! - content - >
            <node-content :node="node"></node-content>
        </div>
        <! How to render the subtree of the node? Component YCollapseTransition is a functional component The official document: https://cn.vuejs.org/v2/guide/render-function.html Here why use functional components (stateless, instance-free) to wrap tree node components to render subtrees?
         <y-collapse-transition>
             <! -- v-if="node.expanded && node.childnodes.length "v-if: dynamically add and remove DOM elements v-show: display and hide elements with CSS display -->
            <div
                v-if="childNodeRendered"
                v-show="expanded"
                class="y-tree-node__children"
                :aria-expanded="expanded"
            >
                <y-tree-node
                    v-for="(child) in node.childNodes"
                    :key="getNodeKey(child)"
                    :node="child"
                    :show-checkbox="showCheckbox"
                >
                </y-tree-node>
            </div>
         </y-collapse-transition>
    </div>
</template>
Copy the code

tree-store.js

Tree-store State manager of a tree, generating a tree node set

import Node from './node';

export default class TreeStore {
    constructor(options) {

        // Assignment initialization: Options are objects, traversal using for... in...
        for(let option in options) {
            if(options.hasOwnProperty(option)) {
                this[option] = options[option]; }}/** * instantiate root Node Node * Root Node instantiate root Node * start tree from root Node * Root Node -> Root Node childNodes->... * /
        this.root = new Node({
            data: this.data,
            store: this}); }}Copy the code

node.js

Node.js tree node attributes and methods, each node has, to ensure the independence of the node

import objectAssign from '.. /.. /.. /.. /src/utils/merge';
import {
    markNodeData,
} from './utils';

/** * getPropertyFromData(this, 'children') * node.store = tree-store this * node.store. Function | | string undefined * from node in the data to derive prop the corresponding value. * * store props is tree configuration items * *@param {*} node 
 * @param {*} prop 
 */
const getPropertyFromData = function(node, prop) {
    const props = node.store.props;

    const data = node.data || {};

    const config = props && props[prop];
    // console.log('888', props, config, data[config]);

    if(typeof config === 'function') {
        return config(data, node);
    } else if (typeof config === 'string') {
        return data[config];
    } else if (typeof config === 'undefined') {
        const dataProp = data[prop];
        // console.log('children', dataProp)
        return dataProp === undefined ? ' ': dataProp; }}// Id of the tree node
let nodeIdSeed = 0;

export default class Node {
    constructor(options) {
        this.id = nodeIdSeed++;

        / / the node data
        this.data = null;

        // Check whether it is selected. The default value is false: uncheck (true: check).
        this.checked = false;

        // Half-selected, false by default
        this.indeterminate = false;

        // Parent node
        this.parent = null;

        // Whether to expand
        this.expanded = false;
        
        // Is the current node
        this.isCurrent = false;

        / / initialization
        for(let option in options) {
            if(options.hasOwnProperty(option)) {
                this[option] = options[option]; }}// internal
        // The level of this object, which defaults to 0
        this.level = 0;
        // Children of this node
        this.childNodes = [];

        // Computing level The root node level is 0
        if(this.parent) {
            this.level = this.parent.level + 1;
        }

        const store = this.store;
        if(! store) {throw new Error('[Node]store is required! ');
        }

        // Build a subtree
        this.setData(this.data);

        // Sets the expansion properties of the node
        // console.log('store', this.store.defaultExpandAll);
        if(store.defaultExpandAll) {
            this.expanded = true;
        }

        // The node is registered in tree-store. Why register?
        // store.registerNode(this);

        // console.log('Node', this, options);
    }

    /** ** is called via node.label (that is, the get method) */
    get label() {
        return getPropertyFromData(this.'label');
    }

    /** * A instanceof B: whether A is an instanceof B * sets the node's data and childNodes * the data under the root node is an array from which the children are generated *@param {*} data 
     */
    setData(data) {
        // console.log('setData', Array.isArray(data), data instanceof Array);
        
        // If data is not an array or a root node, the node needs to be marked with an ID
        if(!Array.isArray(data)) {
            markNodeData(this, data);
        }

        this.data = data;
        this.childNodes = [];

        let children;
        // If the level of this object is 0 and the data is an array type
        // Data under the root node is an array from which its children are generated
        if(this.level === 0 && this.data instanceof Array) {
            children = this.data;
        } else {
            // See if the children field is still present
            children = getPropertyFromData(this.'children') | | []; }// Generation of child nodes
        for(let i = 0, j = children.length; i < j; i++) {
            this.insertChild({data: children[i]});
        }

        // console.log('ndoe', this);
    }

    /** * inserts childNodes@param {*} child 
     * @param {*} index 
     */
    insertChild(child, index) {
        // console.log('insertChild', child, child instanceof Node);

        // If child is not an instance object of Node
        if(! (childinstanceof Node)) {
            // Add the following object values to child
            objectAssign(child, {
                parent: this.store: this.store,
            });

            // Create the child node
            child = new Node(child);
            // console.log('chi', child);
        }

        child.level = this.level + 1;
        // console.log('ch', child, index);

        /** * typeof index ! == 'undefined' * index ! == undefined * * inserts child into childNodes */
        if(typeof index === 'undefined' || index < 0) {
            // console.log(typeof index ! == 'undefined')
            this.childNodes.push(child);
        } else {
            // console.log(index, index === undefined)
            this.childNodes.splice(index, 0, child)
        }
    }

    /** * Subtree shrink * Set expansion property * node.expanded = false */
    collapse() {
        this.expanded = false;
        // console.log('collapse', this, this.expanded);
    }

    /** * Expand the subtree * Set the node's expand attribute * node.expanded = true ** Note: Each node in the tree has methods to expand and expand the subtree, and the fact that the two methods are not shared ensures the independent nature of the tree nodes */
    expand() {
        // console.log(' expand subtree ', this);
        this.expanded = true; }}Copy the code

conclusion

Implementation approach

  • Tree. Vue render tree
  • Tree-node. vue Renders subtrees
  • Tree-store. js State manager for the tree
  • Node.js defines the properties and methods of tree nodes

To acquire knowledge

  • Vue functional components
  • Recursive calls to Vue components