Element – The wrapper tree used by UI components to select components

Demand analysis

  • First, similar implementationel-selectDrop – down box with shrink function
  • Two, the expansion structure for the tree component, support lazy load data and complete tree structure data
  • Three, support two-way data binding, data static search, limited selection and so on

implementationel-select

Use el-Input to simulate the SELECT component and add ICONS

<template> <div slot="reference" class="popover-input"> <el-input placeholder=" please select :value="inputValue" size="mini" readonly /> <i class="el-icon-arrow-down input-icon" :class="{ 'input-focus': popoverShow }" /> </div> </template> <script> export default { data() { return { inputValue: '', popoverShow: false, } } } </script> <style lang="scss" scoped> .popover-input{ position: relative; display: inline-block; .input-icon{ position: absolute; top: 6px; right: 6px; color: #C0C4CC; transition: transform .3s; } .input-focus{ transform: rotate(-180deg) } } </style>Copy the code

Use the popoverShow variable to control the direction of ICONS

addpopovercomponent

<! --selectTree --> <template> <el-popover v-model="popoverShow" placement="bottom" width="240" trigger="click" > <div Slot ="reference" class="popover-input"> < EL-input placeholder=" please select ":value="inputValue" size="mini" readonly /> < I class="el-icon-arrow-down input-icon" :class="{ 'input-focus': popoverShow }" /> </div> </el-popover> </template>Copy the code

When popover expands the icon down, popover shrinks the icon up

addtreecomponent

<template> <div class="tree"> <div class="tree-search"> <el-input v-model="filterText" clearable size="mini" Placeholder =" input keyword filter "/> </div> <el-tree ref="tree" /> </div> </template> <script> export default {data() {return {placeholder=" input keyword filter" /> </div> </template> <script> export default {data() {return { filterText: '' } }, watch: { filterText(val) { this.$refs.tree.filter(val) } }, } </script> <style lang="scss" scoped> .tree{ height: 100%; background-color: #ffffff; .tree-search{ height: 40px; line-height: 40px; } .tree-node{ max-height: 300px; overflow-y: scroll; < span style = "max-width :100%; clear: both; min-width:100%; display: inline-block; } } } </style>Copy the code

Tree component configuration details

parameter instructions type
data Display data array
load Method of loading subtree data, valid only if lazy is true function(node, resolve)
props Attribute name Configuration options (Label, Children, Disabled, isLeaf) object
lazy Whether to load child nodes lazily must be used together with the load method boolean
node-key Each tree node is used as a unique identifier of the property, the entire tree should be unique, fixed to id String
show-checkbox Whether a node can be selected boolean
check-strictly In the case of checkboxes, whether to strictly follow the parent-child disassociation rule boolean
expand-on-click-node Whether to expand or contract a node when it is clicked boolean
filter-node-method The method performed when filtering tree nodes Function(value, data, node)
highlight-current Whether to highlight the currently selected node boolean

Tree component adds configuration

<template> <div class="tree"> <div class="tree-search"> <el-input v-model="filterText" clearable size="mini" Placeholder =" input keyword "/> </div> <el-tree ref="tree" :load="loadNode" :props="defaultProps" lazy node-key="id" show-checkbox check-strictly expand-on-click-node :filter-node-method="filterNode" highlight-current /> </div> </template> <script> export default { props: { loadNode: Function, defaultProps: { type: Object, default() { return { children: 'children', label: 'label', disabled: 'disabled' } } }, }, data() { return { filterText: '' } }, watch: { filterText(val) { this.$refs.tree.filter(val) } }, methods: { filterNode(value, data) { if (! value) return true return data[this.defaultProps.label].includes(value) }, } } </script>Copy the code

It is then time to put the wrapped tree component into the selectTree component

Simulated data

Referring to the official website, we simulate a method to obtain data. For ease of use, each data is added with ID

let index = 0 
loadNode(node, resolve) {
        if (node.level === 0) {
          return resolve([{ name: 'region'.id: index++ }]);
        }
        if (node.level > 1) return resolve([]);

        setTimeout(() = > {
          const data = [{
            name: 'leaf'.leaf: true.id: index++
          }, {
            name: 'zone'.id: index++
          }];

          resolve(data);
        }, 500);
 }
Copy the code

Parent component call

<template>
  <select-tree v-model="value" :load-node="loadNode" :default-props="defaultProps" />
</template>

<script>
import selectTree from './components/selectTree'
let index = 0
export default {
  components: {
    selectTree
  },
  data() {
    return {
      value: [],
      defaultProps: {
        label: 'name',
        isLeaf: 'leaf'
      }
    }
  },
  methods: {
    loadNode(node, resolve) {
      if (node.level === 0) {
        return resolve([{ name: 'region', id: index++ }])
      }
      if (node.level > 1) return resolve([])

      setTimeout(() => {
        const data = [{
          name: 'leaf',
          leaf: true,
          id: index++
        }, {
          name: 'zone',
          id: index++
        }]

        resolve(data)
      }, 500)
    }
  }
}
</script>
Copy the code

Now we can click on the input box to expand the tree component, but the data binding is not there, so let’s start to implement two-way binding of data

Tree component event listener

The tree component needs to listen for two events

  • node-clickTree node click event, if the node is optional, then click and select, can also be configuredexpand-on-click-nodeLet the node click to expand the tree layer
  • checkEmitted when the check box is clicked, not when the content method is called

When one of these two events is triggered, the selected value should change, so define a data change method (radio for now).

First of all, we need to define what value is stored in the V-Model. Considering that we need to write back data later, we need to get all levels of data of the selected data, so the value should be an array, and the array should be all its ancestor elements and contain itself

For example, [‘0’, ‘0-1’, ‘0-1-1’] indicates that the ID of the currently selected data is 0-1-1. The ID of the parent element is 0-1. The parent of the parent element is the highest level, and the ID is 0

Since the value data needs to store the information of all the parent nodes, but the input parameters of the two events it triggers are only the information of the current node, we need to encapsulate a method to get the set of its ancestor elements

In the elder-UI el-Tree instance, we can find a nodesMap object with all the node information, so we can iterate through this object to get the set of all the parent elements of the selected node

// Get the data cache
getNodesMap() {
  return this.$refs.tree.store.nodesMap
},
/** * Get all parent */ by id
getParentID(id) {
  const nodesMap = this.getNodesMap()
  const parentId = []
  let node = nodesMap[id]
  while (node.parent) {
    parentId.unshift(node.data.id)
    node = node.parent
  }
  return parentId
}
Copy the code

Multiple selection let’s not do for the moment, first control radio, that is, after selection if the selected data is the same as before then we will reverse the selection value, and we need to set the text content of the selected value

And element-UI gives us a way to toggle choices

 setCheckedKeys(keys) {
   this.$refs.tree.setCheckedKeys(keys)
 }
Copy the code
function selectChange(data) {
      const alTreeData = this.$refs.alTree
      const id = data.id
      let selectValue = alTreeData.getParentID(id)
      if (selectValue === this.value) {
        selectValue = []
        this.inputValue = ''
      } else {
        const { label } = alTreeData.getNode(id)
        this.inputValue = label
      }
      alTreeData.setCheckedKeys([id])
      this.$emit('value:change', selectValue)
}
Copy the code

Now that two-way data binding is implemented, implement data write back