Skip to content

Commit

Permalink
feat: optimize loadChildren and update doc
Browse files Browse the repository at this point in the history
  • Loading branch information
ChuChencheng committed Aug 10, 2024
1 parent 64ae8dc commit 0897763
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 25 deletions.
16 changes: 14 additions & 2 deletions site/.vitepress/code/ReloadChildren.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
<template>
<button @click="handleSetChildren">Set node-1 children</button>
<button @click="handleClearChildren">Clear node-1 children</button>
<button @click="handleSetChildren">Set node-1 children</button>
<button @click="handleUpdateChildren">Update node-1 children</button>
<div :style="{ height: '300px' }">
<VTree ref="tree" />
<VTree ref="tree" checkable selectable />
</div>
</template>

Expand Down Expand Up @@ -47,6 +48,17 @@ const handleSetChildren = () => {
const handleClearChildren = () => {
tree.value.updateNode('node-1', { children: [] })
}
const handleUpdateChildren = () => {
tree.value.updateNode('node-1', {
children: children.map((child) => {
return {
...child,
title: `${child.title} ${Date.now()}`,
checked: true,
}
})
})
}
</script>

<style scoped>
Expand Down
66 changes: 66 additions & 0 deletions site/.vitepress/code/UpdateCustomField.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<template>
<button @click="handleUpdateCount">Update node-1 count</button>
<VTree ref="tree">
<template #node="{ node }">
<span>{{ node.title }}</span>
<span v-if="typeof node.count === 'number'">
Count: {{ node.count }}
</span>
</template>
</VTree>
</template>

<script setup lang="ts">
import { onMounted, ref } from 'vue'
import VTree from '@wsfe/vue-tree'
const tree = ref()
const data = [
{
title: 'node-1',
id: 'node-1',
count: 0,
children: [
{
title: 'node-1-1',
id: 'node-1-1',
},
{
title: 'node-1-2',
id: 'node-1-2',
},
],
},
{
title: 'node-2',
id: 'node-2',
children: [
{
title: 'node-2-1',
id: 'node-2-1',
},
],
},
]
onMounted(() => {
tree.value.setData(data)
})
const handleUpdateCount = () => {
const key = 'node-1'
const currentCount = tree.value.getNode(key).count
tree.value.updateNode(key, { count: currentCount + 1 })
}
</script>

<style scoped>
button {
border: 1px solid lightgray;
border-radius: 8px;
padding-left: 10px;
padding-right: 10px;
margin-right: 20px;
}
</style>
2 changes: 2 additions & 0 deletions site/api/vtree.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@
| filter | 过滤节点 | `keyword: string`: 过滤关键词<br/>`filterMethod: (keyword: string, node: TreeNode) => boolean`: 过滤方法,默认为 filterMethod Prop ,如果没有传 filterMethod Prop 则为搜索 title 字段的一个内置方法 | `void` |
| showCheckedNodes | 展示已选节点 | `showUnloadCheckedNodes: boolean`: 是否显示未加载的选中节点,默认为 Prop 传入的值 | `void` |
| loadRootNodes | 从远程加载根节点 || `Promise<void>` |
| updateNode `4.1.0` | 更新单个节点 | `key: string \| number`: 节点 key<br/>`newNode: object`: 新节点数据,某些字段将被忽略,例如以下划线 "_" 开头的字段,以及 key 字段和 `indeterminate`, `visible`, `isLeaf`| `void` |
| updateNodes `4.1.0` | 更新多个节点 | `newNodes: object[]`: 新节点数据数组,与 `updateNode` 相同,特定的字段会被忽略,且没有 key 字段的元素将被忽略 | `void` |
| scrollTo | 滚动到指定节点位置 | `key: string \| number`: 节点 key<br/>`verticalPosition: 'top' \| 'center' \| 'bottom' \| number`: 滚动的垂直位置 | `void` |

## VTree Slots
Expand Down
2 changes: 2 additions & 0 deletions site/en/api/vtree.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ Note: Since `2.0.8`, the node info returned in events contains the full node inf
| filter | Filter nodes | `keyword: string`: filter keyword<br/>`filterMethod: (keyword: string, node: TreeNode) => boolean`: filter method, default to filterMethod prop. if filterMethod prop is not present, it's an internal method that searches node title | `void` |
| showCheckedNodes | Show checked nodes | `showUnloadCheckedNodes: boolean`: whether to show checked nodes that are not loaded, default to prop value | `void` |
| loadRootNodes | Load root nodes from remote | None | `Promise<void>` |
| updateNode `4.1.0` | Update single node | `key: string \| number`: node key<br/>`newNode: object`: new node data, some fields will be ignored, like those start with underscore '_', the key field and `indeterminate`, `visible`, `isLeaf`, etc. | `void` |
| updateNodes `4.1.0` | Update multiple nodes | `newNodes: object[]`: new nodes array, some specific fields will be ignored like `updateNode`, and the elements without key field also will be ignored | `void` |
| scrollTo | Scroll to specific node position | `key: string \| number`: node key<br/>`verticalPosition: 'top' \| 'center' \| 'bottom' \| number`: vertical position of scrolling | `void` |

## VTree Slots
Expand Down
20 changes: 20 additions & 0 deletions site/en/examples/node-manipulation.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,23 @@ Enable `draggable` and `droppable`
- Invoke `remove` to remove a node

<CodeDemo component="NodeCreationAndRemoval" />

## Update Node Title {#update-node-title}

Invoke `updateNode` method to update some fields of tree node

Invoke `updateNodes` to update multiple nodes

<CodeDemo component="UpdateNodeTitle" />

## Update Custom Field {#update-custom-field}

Invoke `updateNode` method to update custom fields in tree node

<CodeDemo component="UpdateCustomField" />

## Reload Child Nodes {#reload-children}

Invoke `updateNode` and pass a new `children` list to reload child nodes

<CodeDemo component="ReloadChildren" />
6 changes: 6 additions & 0 deletions site/examples/node-manipulation.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@

<CodeDemo component="UpdateNodeTitle" />

## 更新自定义字段 {#update-custom-field}

调用树组件的 `updateNode` 方法更新自定义字段

<CodeDemo component="UpdateCustomField" />

## 重新加载子节点 {#reload-children}

调用 `updateNode` 传入新的 `children` 列表可以重新加载子节点
Expand Down
71 changes: 49 additions & 22 deletions src/store/tree-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,18 +291,15 @@ export default class TreeStore extends TreeEventTarget {
} else {
// 设置的节点不是当前已选中节点,要么当前没有选中节点,要么当前有选中节点
if (value) {
if (this.currentSelectedKey === null) {
// 当前没有选中节点
node.selected = value
this.currentSelectedKey = node[this.options.keyField]
} else {
if (this.currentSelectedKey !== null) {
// 取消当前已选中,设置新的选中节点
if (this.mapData[this.currentSelectedKey]) {
this.mapData[this.currentSelectedKey].selected = false
}
node.selected = value
this.currentSelectedKey = node[this.options.keyField]
}
node.selected = value
this.currentSelectedKey = node[this.options.keyField]
this.unloadSelectedKey = null
}
}

Expand All @@ -327,9 +324,7 @@ export default class TreeStore extends TreeEventTarget {
triggerDataChange: boolean = true
): void {
if (value) {
if (this.currentSelectedKey) {
this.setSelected(this.currentSelectedKey, false, false, false)
}
this.currentSelectedKey = null
this.unloadSelectedKey = key
} else {
if (this.unloadSelectedKey === key) {
Expand Down Expand Up @@ -492,8 +487,13 @@ export default class TreeStore extends TreeEventTarget {
}
}

private isChildrenChanged(node: TreeNode, newNode: ITreeNodeOptions): boolean {
return ('children' in newNode) && (!!node.children.length || !!newNode.children?.length)
}

updateNode(key: TreeNodeKeyType, newNode: ITreeNodeOptions, triggerEvent = true, triggerDataChange = true) {
if (!this.mapData[key]) return
const node = this.mapData[key]
if (!node) return

const newNodeCopy: ITreeNodeOptions = {}
const notAllowedFields = [
Expand All @@ -512,14 +512,15 @@ export default class TreeStore extends TreeEventTarget {

const previousCheckedKeys = this.getCheckedKeys()
const previousSelectedKey = this.getSelectedKey()
let triggerSetDataFlag = this.isChildrenChanged(node, newNodeCopy)

if ('children' in newNodeCopy) {
if (('children' in newNodeCopy) && (!!node.children.length || !!newNodeCopy.children?.length)) {
// remove all children
this.removeChildren(key, false, false)

// add new children
if (Array.isArray(newNodeCopy.children)) {
this.loadChildren(this.mapData[key], newNodeCopy.children, this.mapData[key].expand)
this.loadChildren(node, newNodeCopy.children, node.expand)
}

delete newNodeCopy.children
Expand All @@ -537,7 +538,7 @@ export default class TreeStore extends TreeEventTarget {
delete newNodeCopy.expand
}
Object.keys(newNodeCopy).forEach((field) => {
this.mapData[key][field] = newNodeCopy[field]
node[field] = newNodeCopy[field]
})

const currentCheckedKeys = this.getCheckedKeys()
Expand All @@ -554,6 +555,9 @@ export default class TreeStore extends TreeEventTarget {
}

if (triggerDataChange) {
if (triggerSetDataFlag) {
this.emit('set-data')
}
this.emit('visible-data-change')
}
}
Expand All @@ -564,9 +568,15 @@ export default class TreeStore extends TreeEventTarget {

const previousCheckedKeys = this.getCheckedKeys()
const previousSelectedKey = this.getSelectedKey()
let triggerSetDataFlag = false

validNodes.forEach((node) => {
this.updateNode(node[this.options.keyField], node, false, false)
validNodes.forEach((newNode) => {
const key = newNode[this.options.keyField]
const node = this.mapData[key]
if (node) {
triggerSetDataFlag = triggerSetDataFlag || this.isChildrenChanged(node, newNode)
this.updateNode(key, newNode, false, false)
}
})

const currentCheckedKeys = this.getCheckedKeys()
Expand All @@ -580,6 +590,10 @@ export default class TreeStore extends TreeEventTarget {
this.triggerSelectedChange(true, false)
}

if (triggerSetDataFlag) {
this.emit('set-data')
}

this.emit('visible-data-change')
}

Expand Down Expand Up @@ -894,6 +908,7 @@ export default class TreeStore extends TreeEventTarget {
if (!node || !node.children.length) return null

const firstChild = node.children[0]
let movingNode = firstChild

// 从 flatData 中移除
const index = this.findIndex(node)
Expand All @@ -905,6 +920,11 @@ export default class TreeStore extends TreeEventTarget {
// 从 mapData 中移除
delete this.mapData[this.flatData[i][this.options.keyField]]
deleteCount++

// 如果是 Selected 的节点,则记录
if (this.flatData[i].selected) {
movingNode = this.flatData[i]
}
} else break
}
this.flatData.splice(index + 1, deleteCount)
Expand All @@ -915,7 +935,7 @@ export default class TreeStore extends TreeEventTarget {
node.indeterminate = false

// 更新被移除处父节点状态
this.updateMovingNodeStatus(firstChild, triggerEvent, triggerDataChange)
this.updateMovingNodeStatus(movingNode, triggerEvent, triggerDataChange)

if (triggerDataChange) {
this.emit('visible-data-change')
Expand All @@ -935,14 +955,16 @@ export default class TreeStore extends TreeEventTarget {
const currentCheckedKeys = this.getCheckedKeys()
const flattenChildren = this.flattenData(
node.children,
this.getSelectedKey === null
this.getSelectedKey() === null
)
this.insertIntoFlatData(parentIndex + 1, flattenChildren)
// 如果有未加载的选中节点,判断其是否已加载
this.setUnloadCheckedKeys(currentCheckedKeys)
if (this.unloadSelectedKey !== null) {
this.setUnloadSelectedKey(this.unloadSelectedKey)
}

this.checkNodeUpward(node, true)
}

private getInsertedNode(
Expand Down Expand Up @@ -1168,8 +1190,6 @@ export default class TreeStore extends TreeEventTarget {
if (node.checked && this.options.cascade) {
// 向下勾选,包括自身
this.checkNodeDownward(node, true)
// 向上勾选父节点直到根节点
this.checkNodeUpward(node)
}

if (node.selected && overrideSelected) {
Expand All @@ -1191,6 +1211,12 @@ export default class TreeStore extends TreeEventTarget {
this.flattenData(node.children, overrideSelected, result)
}
}

if (this.options.cascade && !!length) {
// 向上勾选父节点直到根节点
this.checkNodeUpward(nodes[0])
}

return result
}

Expand Down Expand Up @@ -1230,9 +1256,10 @@ export default class TreeStore extends TreeEventTarget {
/**
* 向上勾选/取消勾选父节点,不包括自身
* @param node 需要勾选的节点
* @param fromCurrentNode 是否从当前节点开始处理
*/
private checkNodeUpward(node: TreeNode) {
let parent = node._parent
private checkNodeUpward(node: TreeNode, fromCurrentNode = false) {
let parent = fromCurrentNode ? node : node._parent
while (parent) {
this.checkParentNode(parent)
parent = parent._parent
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/tree.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ describe('树展示测试', () => {
).toBe(true)
expect(
treeNodes[1].find('.vtree-tree-node__checkbox_indeterminate').exists()
).toBe(true)
).toBe(false)
expect(
treeNodes[2].find('.vtree-tree-node__title_selected').exists()
).toBe(true)
Expand Down

0 comments on commit 0897763

Please sign in to comment.