Skip to content

Commit 482b1de

Browse files
committed
add getNextNode and getPrevNode
1 parent d4330fb commit 482b1de

File tree

9 files changed

+296
-15
lines changed

9 files changed

+296
-15
lines changed

README.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,12 +127,14 @@ interface ICursorPosition<TDataType> {
127127
| getNode(path: number[]): ISlTreeNode | Find the node by using it's path |
128128
| traverse(cb: (node: ISlTreeNode, nodeModel: ISlTreeNodeModel, siblings: ISlTreeNodeModel[]) => boolean) | Helpful method to traverse all nodes. The traversing will be stopped if callback returns `false`. |
129129
| updateNode(path: number[], patch: Partial<ISlTreeNodeModel>) | Update the node by using it's path |
130-
| select(path: number[], addToSelection = false) | Select the node by using it's path |
131-
| getFirstNode(): ISlTreeNode | Get the first node in the tree |
132-
| getLastNode(): ISlTreeNode | Get the last node in the tree |
130+
| select(path: number[], addToSelection = false) | Select the node by using it's path | |
133131
| getNodeEl(): HTMLElement | Get the node HTMLElement by using it's path |
134132
| getSelected(): ISlTreeNode[] | Get selected nodes |
135133
| remove(paths: number[][]) | Remove nodes by paths. For example `.remove([[0,1], [0,2]])`
134+
| getFirstNode(): ISlTreeNode | Get the first node in the tree |
135+
| getLastNode(): ISlTreeNode | Get the last node in the tree
136+
| getNextNode(path: number[], filter?: (node: ISlTreeNode<TDataType>) => boolean): ISlTreeNode<TDataType>; | Get the next node. You can skip the next nodes by using `filter`
137+
| getPrevNode(path: number[], filter?: (node: ISlTreeNode<TDataType>) => boolean): ISlTreeNode<TDataType>; | Get the previous node. You can skip the previous nodes by using `filter`
136138

137139
# Slots
138140

@@ -144,6 +146,11 @@ interface ICursorPosition<TDataType> {
144146
| sidebar | ISlTreeNode | Sidebar content |
145147
| draginfo | SlVueTree | Slot that follows the mouse cursor while dragging. By default shows the dragging nodes count. |
146148

149+
## Example:
150+
151+
handle keydow and keyup events via `getNextNode` and `getPrevNode` methods
152+
153+
[demo](https://stream-labs.github.io/sl-vue-tree/demo/keyboardcontrol)
147154

148155
## Example:
149156

demo/keyboardcontrol.html

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<title>sl-vue-tree</title>
6+
<link rel="stylesheet" href="../dist/sl-vue-tree-dark.css">
7+
<link href="https://use.fontawesome.com/releases/v5.0.8/css/all.css" rel="stylesheet">
8+
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
9+
<script src="../dist/sl-vue-tree.js"></script>
10+
</head>
11+
<body>
12+
13+
<div id="app"
14+
>
15+
16+
<h2> Sl-vue-tree - Press Up and Down to move selection, Space and Enter to expand / collapse</h2>
17+
18+
<a href="./dark-theme.html" target="_blank">Source code</a>
19+
<br><br>
20+
21+
22+
<div class="row">
23+
<div class="tree-container">
24+
<sl-vue-tree
25+
v-model="nodes"
26+
ref="slVueTree"
27+
>
28+
29+
<template slot="title" slot-scope="{ node }">
30+
<span class="item-icon">
31+
<i class="fa fa-file" v-if="node.isLeaf"></i>
32+
<i class="fa fa-folder" v-if="!node.isLeaf"></i>
33+
</span>
34+
35+
{{ node.title }}
36+
</template>
37+
38+
39+
<template slot="toggle" slot-scope="{ node }">
40+
<span v-if="!node.isLeaf">
41+
<i v-if="node.isExpanded" class="fa fa-chevron-down"></i>
42+
<i v-if="!node.isExpanded" class="fa fa-chevron-right"></i>
43+
</span>
44+
</template>
45+
46+
47+
</sl-vue-tree>
48+
</div>
49+
50+
51+
<div class="json-preview">
52+
<pre>{{ JSON.stringify(nodes, null, 4)}}</pre>
53+
</div>
54+
55+
</div>
56+
57+
58+
</div>
59+
60+
<script>
61+
62+
var nodes = [
63+
{title: 'Item1', isLeaf: true},
64+
{title: 'Item2', isLeaf: true},
65+
{
66+
title: 'Folder1', isExpanded: false, children: [
67+
{title: 'Item3', isLeaf: true},
68+
{title: 'Item4', isLeaf: true},
69+
{
70+
title: 'Folder2', children: [
71+
{title: 'Item5', isLeaf: true}
72+
]
73+
}
74+
]
75+
},
76+
{title: 'Item6', isLeaf: true}
77+
];
78+
79+
new Vue({
80+
el: '#app',
81+
components: { SlVueTree },
82+
data: function () {
83+
return {
84+
nodes: nodes
85+
}
86+
},
87+
88+
mounted() {
89+
// expose instance to the global namespace for better debugging
90+
window.slVueTree = this.$refs.slVueTree;
91+
92+
// handle key events
93+
window.addEventListener('keydown', (event) => this.onArrowDownHandler(event));
94+
},
95+
96+
97+
98+
methods: {
99+
onArrowDownHandler(event) {
100+
event.preventDefault();
101+
const keyCode = event.code;
102+
const slVueTree = this.$refs.slVueTree;
103+
104+
if (slVueTree.selectionSize === 1) {
105+
const selectedNode = slVueTree.getSelected()[0];
106+
let nodeToSelect;
107+
108+
if (keyCode === 'ArrowDown') {
109+
nodeToSelect = slVueTree.getNextNode(selectedNode.path, node => node.isVisible);
110+
} else if (keyCode === 'ArrowUp') {
111+
nodeToSelect = slVueTree.getPrevNode(selectedNode.path, node => node.isVisible);
112+
} else if (keyCode === 'Enter' || keyCode === 'Space') {
113+
if (selectedNode.isLeaf) return;
114+
slVueTree.updateNode(selectedNode.path, { isExpanded: !selectedNode.isExpanded });
115+
}
116+
117+
if (!nodeToSelect) return;
118+
119+
slVueTree.select(nodeToSelect.path);
120+
121+
} else if (keyCode === 'ArrowDown') {
122+
slVueTree.select(slVueTree.getFirstNode().path);
123+
} else if (keyCode === 'ArrowUp') {
124+
slVueTree.select(slVueTree.getLastNode().path);
125+
}
126+
}
127+
}
128+
})
129+
</script>
130+
131+
<style>
132+
133+
body {
134+
background: #050d12;
135+
font-family: Arial;
136+
color: rgba(255, 255, 255, 0.5);
137+
}
138+
139+
a {
140+
color: #bbb;
141+
}
142+
143+
.row {
144+
display: flex;
145+
margin-bottom: 10px;
146+
}
147+
148+
.tree-container {
149+
flex-grow: 1;
150+
}
151+
152+
.sl-vue-tree.sl-vue-tree-root {
153+
flex-grow: 1;
154+
overflow-x: hidden;
155+
overflow-y: auto;
156+
height: 300px;
157+
}
158+
159+
160+
.json-preview {
161+
flex-grow: 1;
162+
margin-left: 10px;
163+
background-color: #13242d;
164+
border: 1px solid black;
165+
padding: 10px;
166+
}
167+
168+
.item-icon {
169+
display: inline-block;
170+
text-align: left;
171+
width: 20px;
172+
}
173+
174+
175+
</style>
176+
177+
</body>
178+
</html>

dist/sl-vue-tree.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export interface ISlTreeNodeModel<TDataType> {
88
data?: TDataType;
99
}
1010
export interface ISlTreeNode<TDataType> extends ISlTreeNodeModel<TDataType> {
11+
isVisible?: boolean;
1112
isFirstChild: boolean;
1213
isLastChild: boolean;
1314
ind: number;
@@ -37,6 +38,10 @@ export default class SlVueTree<TDataType> extends Vue {
3738
draggingNode: ISlTreeNode<TDataType>;
3839
readonly nodes: ISlTreeNode<TDataType>[];
3940
getNode(path: number[]): ISlTreeNode<TDataType>;
41+
getFirstNode(): ISlTreeNode<TDataType>;
42+
getLastNode(): ISlTreeNode<TDataType>;
43+
getNextNode(path: number[], filter?: (node: ISlTreeNode<TDataType>) => boolean): ISlTreeNode<TDataType>;
44+
getPrevNode(path: number[], filter?: (node: ISlTreeNode<TDataType>) => boolean): ISlTreeNode<TDataType>;
4045
updateNode(nodeToUpdate: ISlTreeNode<TDataType>, patch: Partial<ISlTreeNodeModel<TDataType>>): void;
4146
getSelected(): ISlTreeNode<TDataType>[];
4247
traverse(cb: (node: ISlTreeNode<TDataType>, nodeModel: ISlTreeNodeModel<TDataType>, siblings: ISlTreeNodeModel<TDataType>[]) => boolean | void, nodeModels?: ISlTreeNodeModel<TDataType>[], parentPath?: number[]): ISlTreeNode<TDataType>[] | boolean;

0 commit comments

Comments
 (0)