VUE implements Studio management background: tree structure, file tree, node tree share a set of code NodeTree

The content of this introduction is a little more complicated. It uses VUE to realize the tree structure. At present, this attribute structure has no editing function, just display. I'll open another article tomorrow to introduce how to add editing functions. I've thought about the title. Let's take a look at today's display:

It is necessary to use recursion to build the tree. Using slot as an intuitive way is no longer feasible. You can only pass a tree shaped data structure to components through attribute parameters. The data structure passed in is roughly like this:

[
        {
          title:'page '
          selected:false,
          opened:false,
          isFolder:true,
          children:[
            {
              title:'index.html',
              selected:false,
              opened:false,
              icon:"far fa-file-code",
            },
            {
              title:'product.html',
              selected:false,
              opened:false,
              icon:"far fa-file-code",
            },
          ],
        },
        {
          title:'style'
          selected:false,
          opened:false,
          isFolder:true,
          children:[
            {
              title:'style.css',
              selected:false,
              opened:false,
              icon:"far fa-file-code",
            },
          ],
        },
]

 

Each node nested its children through children. It should be noted that we hope that this tree can be edited, and its nodes can be added, deleted and edited. Therefore, we need two-way binding of data, which can not be passed to components through the ordinary attribute props, but through the v-model.
In the RXEditor project, there are only two places where the tree structure is used. The components to be made can meet these two requirements, because it is relatively simple to build a general class library. One of these two places is used to display and edit the file directory structure, the other is the node tree, which is pure display and has no editing function. Only leaf nodes in the file tree can be selected, and all nodes in the node tree can be selected. All are single choice, no need to check.
Give this control an atmospheric name, NodeTree. Let's see how to use NodeTree first.
First call:

<NodeTree v-model="files" 
:openIcon="'fas fa-folder-open'" 
:closeIcon="'fas fa-folder'" >
</NodeTree>

 

The second call:

<NodeTree v-model="nodes" 
:openIcon="'fas fa-caret-down'" 
:closeIcon="'fas fa-caret-right'" 
:leafIcon="''"
:folderCanbeSelected = 'true'>
</NodeTree>

The tree data structure is passed through v-model. openIcon is the icon when the node is expanded, closeicon is the icon when the node is closed, and leafIcon is the icon when there is no child node. If these icons are not set, they will have default values, which are the appearance of folders and files. In order to increase the extensibility, the tree data structure can also place icons. The icons in the data structure have high priority and can override the control settings. Understand the principle, what you want to do, and see your project requirements. The folderCanbeSelected parameter indicates whether a node (such as a folder) with child nodes can be selected.

Create a new tree directory in the src directory and place two files:

NodeTree is a tree control. TreeNode is a node inside the tree control. Its name has some advantages, but it's my favorite naming method.

Code of NodeTree.vue (CSS omitted):

<template>
  <div class="node-tree">
    <TreeNode v-for = "(node, i) in inputValue" 
      :key = "i" 
      v-model = "inputValue[i]"
      :openIcon = "openIcon"
      :closeIcon = "closeIcon"
      :leafIcon = "leafIcon"
      :folderCanbeSelected = "folderCanbeSelected"
      @nodeSelected = "nodeSelected"
      ></TreeNode>
  </div>
</template>

<script>
import TreeNode from "./TreeNode.vue"

export default {
  name: 'FileTree',
  props: {
    value: { default: []},
    openIcon:{ default: 'fas fa-folder-open'},
    closeIcon:{ default: 'fas fa-folder'},
    leafIcon:{ default: 'fas fa-file' },
    folderCanbeSelected:{ default:false }
  },
  components:{
    TreeNode
  },
  data() {
    return {
    };
  },

  computed:{
    inputValue: {
        get:function() {
          return this.value;
        },
        set:function(val) {
          this.$emit('input', val);
        },
    },
  },

  methods: {
    nodeSelected(selectedNode){
      this.inputValue.forEach(child=>{
        this.resetSelected(selectedNode, child)
      })
      this.$emit('nodeSelected', selectedNode)
    },

    //Recursive charge selection state
    resetSelected(selectedNode, node){
      node.selected = (node === selectedNode)
      if(node.children){
        node.children.forEach(child=>{
          this.resetSelected(selectedNode, child)
        })
      }
    }
  },
}
</script>

 

The logic of this code is very simple. It is to receive external parameters and call TreeNode circularly. To customize v-model, you need to use props value, and calculate inputValue to modify value. For details, please refer to official VUE documents.
Special attention should be paid to the nodeSelected event, which is generated in the child node, sent to the parent node layer by layer through bubbling, and finally reached the NodeTree component. The NodeTree component is then distributed to the outer calling component through the $emit method.
The control implemented this time is radio selection and exclusive. It needs to recursively call the resetSelected method to eliminate the selected state of other nodes.

The code of TreeNode component is as follows (omit CSS, if necessary, please get it from GIthub):

<template>
  <div class="tree-node" :class="inputValue.selected ? 'selected' :''"

  >
    <div class="node-title" 
      @click="click"  
      @contextmenu.prevent = 'onContextMenu'
    >
      <div  class="node-icon" @click="iconClick">
        <i v-show="icon" :class="icon"></i>
      </div>
      {{inputValue.title}}
    </div>
    <div v-show="showChild" class="children-nodes">
      <TreeNode v-for="(child, i) in inputValue.children" 
        :openIcon = "openIcon"
        :closeIcon = "closeIcon"
        :leafIcon = "leafIcon"
        :key="i" 
        :folderCanbeSelected = "folderCanbeSelected"
        v-model="inputValue.children[i]"
        @nodeSelected = "nodeSelected"
      ></TreeNode>
    </div>
  </div>
</template>

<script>
export default {
  name: 'TreeNode',
  props: {
    value: { default: {}},
    openIcon:{ default: 'fas fa-folder-open'},
    closeIcon:{ default: 'fas fa-folder'},
    leafIcon:{ default: 'fas fa-file' },
    folderCanbeSelected:{default: false},
  },
  data() {
    return {
    }
  },

  computed:{
    inputValue: {
        get:function() {
          return this.value;
        },
        set:function(val) {
          this.$emit('input', val);
        },
    },

    icon(){
      if(this.hasChildren){
        return this.inputValue.opened ? this.openIcon : this.closeIcon
      }
      return this.inputValue.icon !== undefined ? this.inputValue.icon : this.leafIcon
    },

    showChild(){
      return this.hasChildren && this.inputValue.opened
    },

    hasChildren(){
      return this.inputValue.children
         &&this.inputValue.children.length > 0
    },
  },

  methods: {
    click(){
      if((this.hasChildren && this.folderCanbeSelected) || !this.hasChildren){
        this.inputValue.selected = true
        this.$emit('nodeSelected', this.inputValue)
      }
      else {
        this.inputValue.opened = !this.inputValue.opened
      }
    },

    iconClick(event){
      if(this.hasChildren && this.folderCanbeSelected){
        event.stopPropagation()
        this.inputValue.opened = !this.inputValue.opened
      }
    },

    nodeSelected(node){
      this.$emit('nodeSelected', node)
    },

    onContextMenu(event){
      console.log(event)
    }
  },

}
</script>

When the parent component is called, the data of the whole node is passed into the control through v-mode. This component calls itself recursively to form a tree structure. Three states: opened (expanded), closed (closed), selected (selected) are stored in the model data, so that the node state can also be controlled outside the control by modifying the model.

After the introduction of this function, please go to github to get the corresponding historical version:
https://github.com/vularsoft/studio-ui

Tags: Javascript Vue Attribute github

Posted on Fri, 06 Mar 2020 20:53:35 -0800 by misslilbit02