1. Recursive Components - Preview of Simple Tree Controls and Problems
Problems encountered in writing tree components:
- How can components be called recursively?
- How do recursive component click events pass?
2. Basic Structure and Style of Tree Controls
<template> <ul class="vue-tree"> <li class="tree-item"> <div class="tree-content"><!--Node content--> <div class="expand-arrow"></div><!--Expand or contract node buttons--> <div class="tree-label">Primary school</div><!--Node text content--> </div> <ul class="sub-tree"><!--Subnode--> <li class="tree-item expand"> <div class="tree-content"> <div class="expand-arrow"></div> <div class="tree-label">Chinese</div> </div> </li> <li class="tree-item"> <div class="tree-content"> <div class="expand-arrow"></div> <div class="tree-label">Mathematics</div> </div> </li> </ul> </li> </ul> </template> <style lang="stylus"> .vue-tree{ list-style: none; padding: 0; margin: 0; .tree-item{ cursor: pointer; transition: background-color .2s; .tree-content{ position: relative; padding-left: 28px; &:hover{ background-color: #f0f7ff; } } .expand-arrow{ position: absolute; top: 0; left: 0; width: 28px; height: 28px; cursor: pointer; &::after{ position: absolute; top: 50%; left: 50%; display: block; content: ' '; border-width: 5px; border-style: solid; border-color: transparent; border-left-color: #ccc; margin: -5px 0 0 -2.5px; transition: all .2s; } } &.expand{ &>.tree-content{ background-color: #f0f7ff; &>.expand-arrow{ &::after{ transform: rotate(90deg); margin: -2.5px 0 0 -5px; } } } } .tree-label{ height: 28px; line-height: 28px; font-size: 14px; } .sub-tree{ display: none; list-style: none; padding: 0 0 0 28px; margin: 0; } &.expand>.sub-tree{ display: block; } &.no-child{ &>.tree-content{ &>.expand-arrow{ display: none; } } } } } </style>
3. Component catalogue and data structure
directory structure
vue-tree
- VueTree.vue tree control parent component
- TreeItem.vue Tree Control Recursive Component
Tree Control Data Structure
let treeData = [ { text: "Class A", // Displayed text expand: false, // Whether to expand by default children: [ // Subnode { text: "Class A-1", expand: false, }, { text: "Class A-2", expand: false, children: [ { text: "Class A-2-1", expand: false, }, { text: "Class A-2-2", expand: false, } ] } ] } ];
3.1, TreeItem.vue code
<template> <li class="tree-item" :class="{expand: isExpand, 'no-child': !treeItemData.children || treeItemData.children.length === 0}"> <div class="tree-content" @click="_clickEvent"> <div class="expand-arrow" @click.stop="expandTree()"></div> <div class="tree-label">{{treeItemData.text}}</div> </div> <ul class="sub-tree" v-if="treeItemData.children && treeItemData.children.length > 0"> <!--TreeItem Calls in components TreeItem assembly--> <TreeItem v-for="item in treeItemData.children" :tree-item-data="item" :key="uuid()" :tree-click-event="treeClickEvent"></TreeItem> </ul> </li> </template> <script> export default { name: "TreeItem", props: { treeItemData: { type: Object, default(){ return {}; } }, // Node Click Event treeClickEvent: { type: Function, default() { return function () {}; } } }, data(){ return { // Whether the node is expanded or not isExpand: this.treeItemData.expand || false } }, methods: { // Expansion/contraction expandTree(flag){ if(!this.treeItemData.children || this.treeItemData.children.length === 0){ return; } if(typeof flag === 'undefined'){ flag = !this.isExpand; }else{ flag = !!flag; } this.isExpand = flag; }, // Create a unique id uuid(){ let str = Math.random().toString(32); str = str.substr(2); return str; }, // Node Click Event _clickEvent(){ // If an event function is passed, the event function is called and the current node data and components are passed. if(this.treeClickEvent && typeof this.treeClickEvent === 'function'){ this.treeClickEvent(this.treeItemData, this); } } } } </script>
3.1.1. How can a solution component be called recursively? problem
The call itself in the component template must clearly define the name attribute of the component, and the name attribute is the name attribute when the component is called recursively. If the name of the component in the TreeItem.vue component is'TreeItem', then the component name must be <TreeItem> when invoked in the template.
Of course, you can also register components globally, and you can see official vue documents specifically. Recursive components
3.1.2. How to transfer the click events of recursive components? problem
My solution here is to use props to pass in the event function, call the event function when clicking on the node, and pass in the corresponding data.
Previous attempts have been made to use the form of $emit and pass the data over. Because it is a recursive component, the data passed by the outermost layer will change. For example, the data passed by the third layer node will become the data of the first layer node at the end of execution.
4. VueTree.vue Component
<template> <ul class="vue-tree"> <TreeItem v-for="(item, index) in treeData" :key="index" :treeItemData="item" :tree-click-event="treeClickEvent"></TreeItem> </ul> </template> <script> import TreeItem from "./TreeItem"; export default { name: "VueTreeMenu", components: { TreeItem }, props: { // Tree control data treeData: { type: Array, default(){ return []; } }, // Node Click Event treeClickEvent: { type: Function, default() { return function () {}; } } } } </script> <style lang="stylus"> .vue-tree{ list-style: none; padding: 0; margin: 0; .tree-item{ cursor: pointer; transition: background-color .2s; .tree-content{ position: relative; padding-left: 28px; &:hover{ background-color: #f0f7ff; } } .expand-arrow{ position: absolute; top: 0; left: 0; width: 28px; height: 28px; cursor: pointer; &::after{ position: absolute; top: 50%; left: 50%; display: block; content: ' '; border-width: 5px; border-style: solid; border-color: transparent; border-left-color: #ccc; margin: -5px 0 0 -2.5px; transition: all .2s; } } &.expand{ &>.tree-content{ background-color: #f0f7ff; &>.expand-arrow{ &::after{ transform: rotate(90deg); margin: -2.5px 0 0 -5px; } } } } .tree-label{ height: 28px; line-height: 28px; font-size: 14px; } .sub-tree{ display: none; list-style: none; padding: 0 0 0 28px; margin: 0; } &.expand>.sub-tree{ display: block; } &.no-child{ &>.tree-content{ /*padding-left: 0;*/ &>.expand-arrow{ display: none; } } } } } </style>
5. Using Tree Components
<template> <div class="app" id="app"> <VueTree :tree-data="treeData2" :tree-click-event="treeClickEvent"></VueTree> </div> </template> <script> import VueTree from "./components/vue-tree/VueTree"; export default { name: 'app', data(){ return { treeData2: [ { text: "Class A", // Displayed text expand: false, // Whether to expand by default children: [ { text: "second level-1", expand: false, }, { text: "second level-2", expand: false, children: [ { text: "Level three-1", expand: false, }, { text: "Level three-2", expand: false, children: [ { text: "Level Four-1", expand: false, } ] } ] } ] }, { text: "Class A-2", expand: false } ] } }, methods: { treeClickEvent(item, treeItem){ console.log(item); } }, components: { VueTree } } </script>