Element – The wrapper tree used by UI components to select components
Demand analysis
- First, similar implementation
el-select
Drop – 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
addpopover
component
<! --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
addtree
component
<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-click
Tree node click event, if the node is optional, then click and select, can also be configuredexpand-on-click-node
Let the node click to expand the tree layercheck
Emitted 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