Add/delete tree in ant-design-vue
1. Use context
The Ant-Design-Vue component library is used in the new project. The component library is entirely based on the pattern implementation of two-way data binding. Only the form component provides a small number of methods. Therefore, when using Ant-Design-Vue, it is important to switch the UI display from the perspective of changing data. However, in the use of tree control A-tree, only from the data driven consideration, the experience effect is really bad.
2. Current pain points
Read the official help documentation for tree control data binding. The data needs to be constructed into a large object containing the children,title, and key attributes. Such an object is either constructed by the back end of the JSON object, or the back end gives the front end a JSON array, and the front end builds such a tree object based on the relationship between the supervisor and the subordinate. With the data bound, we can successfully render the UI we want. But where is the pain point?
- After the tree is successfully loaded, I need to add a sibling and a child node to the current tree.
- After the tree is loaded successfully, I want to modify how any tree node should operate.
- After the tree is loaded successfully, I want to delete a tree node.
The above operations are not required to reload the tree control conditions to complete. After testing sorted out three feasible schemes
- Data driven
- Scope slot
- Node event
3. Add, delete and modify tree nodes driven by data
You can find a property called selectedKeys(.sync) in the help documentation,sync means that this property supports bidirectional operation. However, you only get a key value, not the required binding object. So, we need to find this object by this key. It’s pretty gross to have to find that
- If the back end returns built data, you need to traverse the tree to find the object that corresponds to the key. All I can think of is recursively looking through the top node. But the controls are all rendered and know the data of each node. Why should I look it up again?
- If the back end returns only an array, as mentioned earlier, it needs to be rebuilt as an object. A. If the list data and the tree object after construction are cloned, that is, the address of the object in the list is different from that of the object with the same key value in the tree. Method 1 traverses the reconstructed tree data b. If the object in the list data is the same object address as the corresponding node after construction. You can directly look up the list data to get the corresponding object.
So, what’s disgusting is that I have to build a tree, and I have to walk through the tree again to find some node, or I have to do plan B where I swap space for time
We’re going to assume that the data is already in a tree format. Two core approaches are required to implement data-driven priorities
- Finds node objects based on the current node key value
getTreeDataByKey
- Search the children set of the parent node according to the current node key value
getTreeParentChilds
The code for the two methods is as follows
// author:herbert date:20201024 qq:464884492
// Get the equivalent data object based on key
getTreeDataByKey(childs = [], findKey) {
let finditem = null;
for (let i = 0, len = childs.length; i < len; i++) {
let item = childs[i]
if(item.key ! == findKey && item.children && item.children.length >0) {
finditem = this.getTreeDataByKey(item.children, findKey)
}
if (item.key == findKey) {
finditem = item
}
if(finditem ! =null) {
break}}return finditem
},
// author:herbert date:20201024 qq:464884492
// Get the children array of the parent node according to key
getTreeParentChilds(childs = [], findKey) {
let parentChilds = []
for (let i = 0, len = childs.length; i < len; i++) {
let item = childs[i]
if(item.key ! == findKey && item.children && item.children.length >0) {
parentChilds = this.getTreeParentChilds(item.children, findKey)
}
if (item.key == findKey) {
parentChilds = childs
}
if (parentChilds.length > 0) {
break}}return parentChilds
},
Copy the code
3.1 Adding nodes at the same level
To add a sibling node, add the newly added data to the children array of the parent of the currently selected node. Therefore, the difficulty of adding nodes is how to find the parent object of the binding object of the currently selected node. The page code is as follows
<! -- author:herbert date:20201030 qq:464884492-->
<a-card style="width: 450px; height:550px; float: left;">
<div slot="title">
<h2>Tree operation (purely data-driven)<span style="color:blue">@herbert</span></h2>
<div>
<a-button @click="dataDriveAddSame">Add at the same level</a-button>
<a-divider type="vertical" />
<a-button @click="dataDriveAddSub">Add the lower</a-button>
<a-divider type="vertical" />
<a-button @click="dataDriveModify">Modify the</a-button>
<a-divider type="vertical" />
<a-button @click="dataDriveDelete">delete</a-button>
</div>
</div>
<a-tree :tree-data="treeData" :defaultExpandAll="true"
:selectedKeys.sync="selectKeys" showLine />
<img src="./assets/gamelogo.png" width="100%" style="margin-top:20px" />
</a-card>
Copy the code
As can be seen from the page code, there are two properties bound to the tree: tree-data,selectedKeys. Here we can obtain the current selected key value of the tree through selectedKeys binding value. Peer addition can then be achieved using the method getTreeParentChilds. So, the dataDriveAddSame code is implemented as follows
// author:herbert date:20201030 qq:464884492
dataDriveAddSame() {
let parentChilds = this.getTreeParentChilds(this.treeData, this.selectKeys[0])
parentChilds.forEach(item= > console.log(item.title));
parentChilds.push({
title: 'Geocentric, you can't stop playing.'.key: new Date().getTime()
})
},
Copy the code
3.2 Adding a Child
Once you have the top base, it’s easy to add children. The only thing to be aware of is that the children property of the object may not exist. In this case, we need to add the dataDriveAddSub property in $set mode
// author:herbert date:20201030 qq:464884492
dataDriveAddSub() {
let selectItem = this.getTreeDataByKey(this.treeData, this.selectKeys[0])
if(! selectItem.children) {this.$set(selectItem, "children", [])
}
selectItem.children.push({
titleGeocentric, worth your while,key: new Date().getTime()
})
this.$forceUpdate()
},
Copy the code
3.3 Modifying a Node
With the binding object in hand, it is easy to change the value of the node, using getTreeDataByKey to get the current object as you would add a child, and then modifying the value directly. The dataDriveModify code is implemented as follows
// author:herbert date:20201030 qq:464884492
dataDriveModify() {
let selectItem = this.getTreeDataByKey(this.treeData, this.selectKeys[0])
selectItem.title = 'Scan the qr code below to explore the earth's core'
},
Copy the code
3.4 Deleting a Node
To delete, as to add siblings, we need to find the children array of the parent node, which has the corresponding index of the current object in the parent series group
// author:herbert date:20201030 qq:464884492
dataDriveDelete() {
let parentChilds = this.getTreeParentChilds(this.treeData, this.selectKeys[0])
let delIndex = parentChilds.findIndex(item= > item.key == this.selectKeys[0])
parentChilds.splice(delIndex, 1)},Copy the code
4. Add, delete, or modify nodes in the tree using slots
In the API of ant – tree, the tree node properties title type can be a string, can also be a slot [string | slot | slot – scope], I need to get the action object here, here the use scope slot, the corresponding page code is as follows
<! -- author:herbert date:20201030 qq:464884492-->
<a-card style="width: 450px; height:550px; float: left;">
<div slot="title">
<h2>Tree operations (with scope slots)</h2>
<div>With scope slots, the operation buttons are uniformly placed in the tree<span style="color:blue">@Small yard is not small</span>
</div>
</div>
<a-tree ref="tree1" :tree-data="treeData1" :defaultExpandAll="true" :selectedKeys.sync="selectKeys1" showLine blockNode>
<template v-slot:title="nodeData">
<span>{{nodeData.title}}</span>
<a-button-group style="float:right">
<a-button size="small" @click="slotAddSame(nodeData)" icon="plus-circle" title="Add sibling"></a-button>
<a-button size="small" @click="slotAddSub(nodeData)" icon="share-alt" title="Add child"></a-button>
<a-button size="small" @click="slotModify(nodeData)" icon="form" title="Change"></a-button>
<a-button size="small" @click="slotDelete(nodeData)" icon="close-circle" title="Delete"></a-button>
</a-button-group>
</template>
</a-tree>
<img src="./assets/gamelogo.png" width="100%" style="margin-top:20px" />
</a-card>
Copy the code
4.1 Adding a Peer
The object is actually the property value of the current node, and it is a shallow copy copy. RenderSelector can be found in the source code vc-tree\ SRC \ treenode.jsx
const currentTitle = title;
let $title = currentTitle ? (
<span class={` ${prefixCls}-title`} >{typeof currentTitle === 'function' ? currentTitle({ ... this.$props, ... this.$props.dataRef }, h) : currentTitle}</span>
) : (
<span class={` ${prefixCls}-title`} >{defaultTitle}</span>
);
Copy the code
From this code, you can see a dataRef. But there is no description of this property at all in the official help documentation. Do not know who is willing to look at the source of a benefit of the students. No matter from the code level, or debugging results. SlotAddSame: slotAddSame: slotAddSame: slotAddSame: slotAddSame: slotAddSame: slotAddSame: slotAddSame
// author:herbert date:20201030 qq:464884492
slotAddSame(nodeItem) {
console.log(nodeItem)
this.$warn({ content: "Using slot mode, cannot find parent object, add failed! Forget it. Go play Geocentric."})},Copy the code
4.2 Adding a Child
You get the object, but it’s just a copy. So setting children is useless!!
// author:herbert date:20201030 qq:464884492
slotAddSub(nodeItem) {
if(! nodeItem.children) {console.log('Well, that doesn't work. This is just a copy.')
this.$set(nodeItem, "children", [])
}
nodeItem.children.push({
title: this.addSubTitle,
key: new Date().getTime(),
scopedSlots: { title: 'title' },
children: []})},Copy the code
4.3 Modifying a Node
DataRef (dataRef) : dataRef (dataRef) : dataRef (dataRef) : dataRef
// author:herbert date:20201030 qq:464884492
slotModify(nodeItem) {
console.log(nodeItem)
console.log('nodeItem only renders a shallow copy of the Treenode property. Modifying the Title directly does not work ')
nodeItem.title = 'This is useless, go play the game and take a break.'
// dataRef updates are available here
nodeItem.dataRef.title = nodeItem.title
},
Copy the code
4.4 Deleting a Node
Obviously, deletion is also a no-no.
// author:herbert date:20201030 qq:464884492
slodDelete(nodeItem) {
console.log(nodeItem)
this.$warn({ content: "Unable to find parent object using slot mode, delete failed! Obviously, go play "Geocentric." })
delete nodeItem.dataRef
},
Copy the code
5. Tree events combined with dataRef implementation
Above through the slot way, only to achieve the modification function. Especially disappointed there is no. But from a design point of view, it’s much better to give you objects just to help you make custom renderings. Reading through the official Api to find the values provided by the SELECT event gives us a lot of scope again. How big in the end, we go to the source code to see. JSX = components\vc-tree\ SRC \ treenode.jsx
onSelect(e) {
if (this.isDisabled()) return;
const {
vcTree: { onNodeSelect },
} = this;
e.preventDefault();
onNodeSelect(e, this);
},
Copy the code
TreeNodeonSelect = onNodeSelected; components\vc-tree\ SRC \ tree. JSX
onNodeSelect(e, treeNode) {
let { _selectedKeys: selectedKeys } = this.$data;
const { _keyEntities: keyEntities } = this.$data;
const { multiple } = this.$props;
const { selected, eventKey } = getOptionProps(treeNode);
consttargetSelected = ! selected;// Update selected keys
if(! targetSelected) { selectedKeys = arrDel(selectedKeys, eventKey); }else if(! multiple) { selectedKeys = [eventKey]; }else {
selectedKeys = arrAdd(selectedKeys, eventKey);
}
// [Legacy] Not found related usage in doc or upper libs
const selectedNodes = selectedKeys
.map(key= > {
const entity = keyEntities.get(key);
if(! entity)return null;
return entity.node;
})
.filter(node= > node);
this.setUncontrolledState({ _selectedKeys: selectedKeys });
const eventObj = {
event: 'select'.selected: targetSelected,
node: treeNode,
selectedNodes,
nativeEvent: e,
};
this.__emit('update:selectedKeys', selectedKeys);
this.__emit('select', selectedKeys, eventObj);
},
Copy the code
Select not only renders TreeNode cache data selectedNodes, but also provides the actual TreeNode node to the caller. With the Node property, we can get the hierarchy of the corresponding node
Let’s talk about what a dataRef is that doesn’t appear in the help file. JSX in the corresponding render function we know that the tree needs to pass a treeData property to the VC-tree component, and we end up using the same property name to pass the node data. Two key pieces of code are as follows
render(){...let treeData = props.treeData || treeNodes;
if (treeData) {
treeData = this.updateTreeData(treeData); }...if (treeData) {
vcTreeProps.props.treeData = treeData;
}
return <VcTree {. vcTreeProps} / >;
}
Copy the code
As you can see from the code above, the underlying component calls the updateTreeData method to process the data we passed in. The key code for this method is as follows
updateTreeData(treeData) {
const { $slots, $scopedSlots } = this;
const defaultFields = { children: 'children'.title: 'title'.key: 'key' };
constreplaceFields = { ... defaultFields, ... this.$props.replaceFields };return treeData.map(item= > {
const key = item[replaceFields.key];
const children = item[replaceFields.children];
const { on = {}, slots = {}, scopedSlots = {}, class: cls, style, ... restProps } = item;consttreeNodeProps = { ... restProps,icon: $scopedSlots[scopedSlots.icon] || $slots[slots.icon] || restProps.icon,
switcherIcon:
$scopedSlots[scopedSlots.switcherIcon] ||
$slots[slots.switcherIcon] ||
restProps.switcherIcon,
title:
$scopedSlots[scopedSlots.title] ||
$slots[slots.title] ||
restProps[replaceFields.title],
dataRef: item,
on,
key,
class: cls,
style,
};
if (children) {
// Herbert 20200928 Add attributes to only operate on leaf nodes
if (this.onlyLeafEnable === true) {
treeNodeProps.disabled = true;
}
return { ...treeNodeProps, children: this.updateTreeData(children) };
}
returntreeNodeProps; }); }},Copy the code
The dataRef property is found in the treeNodeProps property, whose value is the item we passed in to the treeData, so this property supports bidirectional binding. The treeNodeProps will render to the Components \vc-tree\ SRC \ treenode.jsx component.
Now that we know about these two things, it’s going to be easy. The event-driven page code is as follows
<! -- author:herbert date:20201101 qq:464884492 -->
<a-card style="width: 450px; height:550px; float: left;">
<div slot="title">
<h2>Tree events (combined with dataRef)<span style="color:blue">@ 464884492</span></h2>
<div>
<a-button @click="eventAddSame">Add at the same level</a-button>
<a-divider type="vertical" />
<a-button @click="eventAddSub">Add the lower</a-button>
<a-divider type="vertical" />
<a-button @click="eventModify">Modify the</a-button>
<a-divider type="vertical" />
<a-button @click="eventDelete">delete</a-button>
</div>
</div>
<a-tree :tree-data="treeData2" @select="onEventTreeNodeSelected" :defaultExpandAll="true" :selectedKeys.sync="selectKeys2" showLine />
<img src="./assets/gamelogo.png" width="100%" style="margin-top:20px" />
</a-card>
Copy the code
Since it is event-driven, we first need to register the due event, as shown below
// author:herbert date:20201101 qq:464884492
onEventTreeNodeSelected(seleteKeys, e) {
if (e.selected) {
this.eventSelectedNode = e.node
return
}
this.eventSelectedNode = null
},
Copy the code
In the event, we save the current selection of TreeNode for subsequent addition, modification and deletion
5.1 Adding a Peer
Using the Vue virtual DOM, find the parent
// author:herbert date:20201101 qq:464884492
eventAddSame() {
// Find the parent
let dataRef = this.eventSelectedNode.$parent.dataRef
if(! dataRef.children) {this.$set(dataRef, 'children', [])
}
dataRef.children.push({
title: 'Geocentric Warrior is fun and worth sharing'.key: new Date().getTime()
})
},
Copy the code
5.2 Adding a Layer
Use the dataRef object directly, noting that children use the $set method
// author:herbert date:20201101 qq:464884492
eventAddSub() {
let dataRef = this.eventSelectedNode.dataRef
if(! dataRef.children) {this.$set(dataRef, 'children', [])
}
dataRef.children.push({
title: "Geocentric, there are a lot of bugs.".key: new Date().getTime(),
scopedSlots: { title: 'title' },
children: []})},Copy the code
5.3 Modifying a Node
Modify Modify the value of dataRef directly
// author:herbert date:20201101 qq:464884492
eventModify() {
let dataRef = this.eventSelectedNode.dataRef
dataRef.title = 'Geocentric Warrior, finish fixing bugs by tomorrow'
},
Copy the code
5.4 Deleting a Node
Find the parent dataRef using the Vue virtual DOM and remove the selection from children
// author:herbert date:20201101 qq:464884492
eventDelete() {
let parentDataRef = this.eventSelectedNode.$parent.dataRef
// Check if it is the top layer
const children = parentDataRef.children
const currentDataRef = this.eventSelectedNode.dataRef
const index = children.indexOf(currentDataRef)
children.splice(index, 1)}Copy the code
6. Summary
This knowledge point, from demo to finished. It took almost a month to get back and forth. Period check source code, do test, very time-consuming. But to make this point clear, I think it’s worth it! If you need Demo source code, please scan the qr code below, follow the public account [xiaoyuan No Small], reply to ant-tree to obtain. As for the ant-Desgin-Vue component library, compared with the easyUi component library I used before, it feels like a kind of suitable web page display. Do some background systems, need to provide a lot of operations, the feeling is still relatively weak