Skip to content

Commit 7b8a370

Browse files
authored
Merge pull request #59 from grapoza/delete-node
Adds node deletion
2 parents 36220bd + 05d661f commit 7b8a370

File tree

12 files changed

+384
-23
lines changed

12 files changed

+384
-23
lines changed

docs/index.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ Planned:
1818

1919
- Node selection ([#5](https://github.com/grapoza/vue-tree/issues/5))
2020
- Async loading ([#13](https://github.com/grapoza/vue-tree/issues/13))
21-
- Adding/deleting nodes ([#24](https://github.com/grapoza/vue-tree/issues/24), [#16](https://github.com/grapoza/vue-tree/issues/16))
21+
- Adding nodes ([#24](https://github.com/grapoza/vue-tree/issues/24))
2222
- Icons ([#22](https://github.com/grapoza/vue-tree/issues/22))
2323
- Searching ([#4](https://github.com/grapoza/vue-tree/issues/4))
2424
- Drag n' Drop ([#6](https://github.com/grapoza/vue-tree/issues/6))
@@ -153,6 +153,7 @@ The properties below can be specified for each node.
153153
| label | String | The text to show in the treeview | - | Yes |
154154
| expandable | Boolean | True to show a toggle for expanding nodes' subnode lists | `true` | |
155155
| selectable | Boolean | True to allow the node to be selected* | `false` | |
156+
| deletable | Boolean | True to allow the node to be deleted | `false` | |
156157
| input | Object | Contains data specific to the node's `input` element | `null` | |
157158
| input.type | String | The type of input; valid values are `checkbox` or `radio` | - | Yes** |
158159
| input.name | String | The name attribute of the input; used with `radio` type | `'unspecifiedRadioName'` | |
@@ -207,6 +208,7 @@ If specified, the `modelDefaults` property of the treeview will be merged with n
207208
|:----------------------------|:--------------------------------------------------------|:-----------------------------------------------------------------------|
208209
| treeViewNodeClick | Emitted when a node is clicked | `target` The model of the target node <br/> `event` The original event |
209210
| treeViewNodeDblclick | Emitted when a node is double clicked | `target` The model of the target node <br/> `event` The original event |
211+
| treeViewNodeDelete | Emitted when a node is deleted | `target` The model of the target node <br/> `event` The original event |
210212
| treeViewNodeCheckboxChange | Emitted when a node's checkbox emits a change event | `target` The model of the target node <br/> `event` The original event |
211213
| treeViewNodeRadioChange | Emitted when a node's radio button emits a change event | `target` The model of the target node <br/> `event` The original event |
212214
| treeViewNodeExpandedChange | Emitted when a node is expanded or collapsed | `target` The model of the target node <br/> `event` The original event |
@@ -229,6 +231,8 @@ The display of the treeview can be customized via CSS using the following classe
229231
| `tree-view-node-self-checkbox` | The checkbox |
230232
| `tree-view-node-self-radio` | The radio button |
231233
| `tree-view-node-self-text` | The text for a non-input node |
234+
| `tree-view-node-self-delete` | The delete button |
235+
| `tree-view-node-self-delete-icon` | The `<i>` element containing the delete icon |
232236
| `tree-view-node-children` | The list of child nodes |
233237

234238
## Customizing TreeViewNode Markup
@@ -251,4 +255,6 @@ A customizations object may have the following properties:
251255
| classes.treeViewNodeSelfCheckbox | String | Classes to add to the checkbox | Add |
252256
| classes.treeViewNodeSelfRadio | String | Classes to add to the radio button | Add |
253257
| classes.treeViewNodeSelfText | String | Classes to add to the text for a non-input node | Add |
258+
| classes.treeViewNodeSelfDelete | String | Classes to add to the delete button | Add |
259+
| classes.treeViewNodeSelfDeleteIcon | String | Classes to add to the `<i>` element containing the delete icon | Add |
254260
| classes.treeViewNodeChildren | String | Classes to add to the list of child nodes | Add |

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"description": "Yet another Vue treeview component.",
44
"author": "Gregg Rapoza <[email protected]>",
55
"license": "MIT",
6-
"version": "0.5.0",
6+
"version": "0.6.0",
77
"browser": "index.js",
88
"repository": {
99
"url": "https://github.com/grapoza/vue-tree",

src/components/TreeView.vue

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
@treeViewNodeDblclick="(t, e)=>$emit('treeViewNodeDblclick', t, e)"
1313
@treeViewNodeCheckboxChange="(t, e)=>$emit('treeViewNodeCheckboxChange', t, e)"
1414
@treeViewNodeRadioChange="(t, e)=>$emit('treeViewNodeRadioChange', t, e)"
15-
@treeViewNodeExpandedChange="(t, e)=>$emit('treeViewNodeExpandedChange', t, e)">
15+
@treeViewNodeExpandedChange="(t, e)=>$emit('treeViewNodeExpandedChange', t, e)"
16+
@treeViewNodeDelete="(t, e)=>$_treeViewNode_handleChildDeletion(t, e)">
1617
</tree-view-node>
1718
</ul>
1819
</template>
@@ -84,6 +85,16 @@
8485
}
8586
8687
return checked;
88+
},
89+
$_treeViewNode_handleChildDeletion(node, event) {
90+
// Remove the node from the array of children if this is an immediate child.
91+
// Note that only the node that was deleted fires these, not any subnode.
92+
let targetIndex = this.model.indexOf(node);
93+
if (targetIndex > -1) {
94+
this.model.splice(targetIndex, 1);
95+
}
96+
97+
this.$emit('treeViewNodeDelete', node, event);
8798
}
8899
}
89100
};

src/components/TreeViewNode.vue

Lines changed: 57 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,17 @@
6161
:class="customClasses.treeViewNodeSelfText">
6262
{{ model.label }}
6363
</span>
64+
65+
<!-- Delete button -->
66+
<button :id="nodeId + '-delete'"
67+
type="button"
68+
v-if="model.deletable"
69+
class="tree-view-node-self-delete"
70+
:class="customClasses.treeViewNodeSelfDelete"
71+
@click="$_treeViewNode_onDelete">
72+
<i class="tree-view-node-self-delete-icon"
73+
:class="customClasses.treeViewNodeSelfDeleteIcon"></i>
74+
</button>
6475
</div>
6576

6677
<!-- Children -->
@@ -81,7 +92,8 @@
8192
@treeViewNodeDblclick="(t, e)=>$emit('treeViewNodeDblclick', t, e)"
8293
@treeViewNodeCheckboxChange="(t, e)=>$emit('treeViewNodeCheckboxChange', t, e)"
8394
@treeViewNodeRadioChange="(t, e)=>$emit('treeViewNodeRadioChange', t, e)"
84-
@treeViewNodeExpandedChange="(t, e)=>$emit('treeViewNodeExpandedChange', t, e)">
95+
@treeViewNodeExpandedChange="(t, e)=>$emit('treeViewNodeExpandedChange', t, e)"
96+
@treeViewNodeDelete="(t, e)=>$_treeViewNode_handleChildDeletion(t, e)">
8597
</TreeViewNode>
8698
</ul>
8799
</li>
@@ -171,14 +183,15 @@
171183
if (!Array.isArray(this.model.children)) {
172184
this.$set(this.model, 'children', []);
173185
}
174-
175-
// Set basic node options
176186
if (typeof this.model.expandable !== 'boolean') {
177187
this.$set(this.model, 'expandable', true);
178188
}
179189
if (typeof this.model.selectable !== 'boolean') {
180190
this.$set(this.model, 'selectable', false);
181191
}
192+
if (typeof this.model.deletable !== 'boolean') {
193+
this.$set(this.model, 'deletable', false);
194+
}
182195
183196
this.$_treeViewNode_normalizeNodeInputData();
184197
this.$_treeViewNode_normalizeNodeStateData();
@@ -258,22 +271,38 @@
258271
this.$emit('treeViewNodeExpandedChange', this.model, event);
259272
},
260273
$_treeViewNode_onClick(event) {
261-
// Don't fire this if the target is the input or expander, which have their own events
262-
if (!event.target.matches("input, .tree-view-node-self-expander")) {
274+
// Don't fire this if the target is an element which has its own events
275+
if (!event.target.matches("input, .tree-view-node-self-expander, .tree-view-node-self-delete")) {
263276
this.$emit('treeViewNodeClick', this.model, event);
264277
}
265278
},
266279
$_treeViewNode_onDblclick(event) {
267-
// Don't fire this if the target is the input or expander, which have their own events
268-
if (!event.target.matches("input, .tree-view-node-self-expander")) {
280+
// Don't fire this if the target is an element which has its own events
281+
if (!event.target.matches("input, .tree-view-node-self-expander, .tree-view-node-self-delete")) {
269282
this.$emit('treeViewNodeDblclick', this.model, event);
270283
}
284+
},
285+
$_treeViewNode_onDelete(event) {
286+
this.$emit('treeViewNodeDelete', this.model, event);
287+
},
288+
$_treeViewNode_handleChildDeletion(node, event) {
289+
// Remove the node from the array of children if this is an immediate child.
290+
// Note that only the node that was deleted fires these, not any subnode.
291+
let targetIndex = this.model.children.indexOf(node);
292+
if (targetIndex > -1) {
293+
this.model.children.splice(targetIndex, 1);
294+
}
295+
296+
this.$emit('treeViewNodeDelete', node, event);
271297
}
272298
},
273299
};
274300
</script>
275301

276302
<style lang="scss">
303+
$baseHeight: 1.2rem;
304+
$itemSpacing: 1.2rem;
305+
277306
.tree-view {
278307
279308
.tree-view-node {
@@ -286,7 +315,7 @@
286315
.tree-view-node-self {
287316
display: flex;
288317
align-items: flex-start;
289-
line-height: 1.2rem;
318+
line-height: $baseHeight;
290319
291320
.tree-view-node-self-expander {
292321
padding: 0;
@@ -315,7 +344,8 @@
315344
.tree-view-node-self-expander,
316345
.tree-view-node-self-checkbox,
317346
.tree-view-node-self-radio,
318-
.tree-view-node-self-spacer {
347+
.tree-view-node-self-spacer,
348+
.tree-view-node-self-delete {
319349
min-width: 1rem;
320350
}
321351
@@ -326,17 +356,32 @@
326356
327357
.tree-view-node-self-checkbox,
328358
.tree-view-node-self-radio {
329-
margin: 0 0 0 -1.2rem;
359+
margin: 0 0 0 (-$itemSpacing);
330360
}
331361
332362
.tree-view-node-self-text,
333363
.tree-view-node-self-label {
334-
margin-left: 1.2rem;
364+
margin-left: $itemSpacing;
365+
}
366+
367+
.tree-view-node-self-delete {
368+
padding: 0;
369+
background: none;
370+
border: none;
371+
height: $baseHeight;
372+
373+
i.tree-view-node-self-delete-icon {
374+
font-style: normal;
375+
376+
&::before {
377+
content: 'x';
378+
}
379+
}
335380
}
336381
}
337382
338383
.tree-view-node-children {
339-
margin: 0 0 0 2.2rem;
384+
margin: 0 0 0 (1rem + $itemSpacing);
340385
padding: 0;
341386
list-style: none;
342387
}

tests/data/node-generator.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
* buttons will be added to the radioState parameter.
66
*
77
* The node spec's node string should be in the format:
8-
* `[eE]?[sS]?[cCrR!?]?`
9-
* The presence of e, s or c|r indicate the node is expandable, selectable, and a checkbox or radio buttton
8+
* `[eE]?[sS]?[d]?[[cCrR]!?]?`
9+
* The presence of e, s, d, or c|r indicate the node is expandable, selectable, deletable and a checkbox or radio buttton
1010
* respectively. If it is capitalized, then the related state should be True. In the case of inputs,
1111
* the capitalization means the input will be selected. The `!` indicates the input will be disabled.
1212
*
@@ -35,6 +35,7 @@ export function generateNodes(nodeSpec, radioState, baseId = "") {
3535
label: 'Node ' + index,
3636
expandable: lowerItem.includes('e'),
3737
selectable: lowerItem.includes('s'),
38+
deletable: lowerItem.includes('d'),
3839
input: lowerItem.includes('c')
3940
? { type: 'checkbox', name: `${idString}-cbx` }
4041
: lowerItem.includes('r')

tests/local/basic.html

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<title>Basic Usage</title>
6+
<script src="https://unpkg.com/vue"></script>
7+
<script src="../../dist/vue-tree.umd.js"></script>
8+
<link rel="stylesheet" href="demo.css">
9+
<link rel="stylesheet" href="../../dist/vue-tree.css">
10+
</head>
11+
<body>
12+
<div class="container">
13+
<h1>Basic Treeview Demo</h1>
14+
<div id="app">
15+
<tree id="customtree" :model="model" ref="tree"></tree>
16+
<section id="checkedStuff">
17+
<button type="button" @click="refreshCheckedList">What's been checked?</button>
18+
<ul id="checkedList">
19+
<li v-for="checkedNode in checkedNodes">{{ checkedNode.id }}</li>
20+
</ul>
21+
</section>
22+
</div>
23+
</div>
24+
25+
<script type='module'>
26+
import basicData from './basic.js';
27+
28+
new Vue({
29+
components: {
30+
tree: window['vue-tree']
31+
},
32+
data() {
33+
return {
34+
model: basicData,
35+
checkedNodes: []
36+
};
37+
},
38+
methods: {
39+
refreshCheckedList() {
40+
this.$set(this, 'checkedNodes', this.$refs.tree.getCheckedCheckboxes());
41+
}
42+
}
43+
}).$mount('#app')
44+
</script>
45+
</body>
46+
</html>

tests/local/basic.js

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
export default [
2+
{
3+
id: 'node1',
4+
label: 'My First Node',
5+
expandable: true,
6+
selectable: true,
7+
deletable: true,
8+
input: {
9+
type: 'checkbox',
10+
name: 'checkbox1'
11+
},
12+
state: {
13+
expanded: false,
14+
selected: false,
15+
input: {
16+
value: false,
17+
disabled: false
18+
}
19+
},
20+
children: []
21+
},
22+
{
23+
id: 'node2',
24+
label: 'My Second Node',
25+
expandable: true,
26+
selectable: true,
27+
input: {
28+
type: 'checkbox',
29+
name: 'checkbox2'
30+
},
31+
state: {
32+
expanded: true,
33+
selected: false,
34+
input: {
35+
value: false,
36+
disabled: false
37+
}
38+
},
39+
children: [
40+
{
41+
id: 'subnode1',
42+
label: 'This is a subnode',
43+
expandable: true,
44+
selectable: true,
45+
deletable: true,
46+
state: {
47+
expanded: false,
48+
selected: false
49+
},
50+
children: []
51+
},
52+
{
53+
id: 'subnode2',
54+
label: 'This is a checkable, checked subnode',
55+
expandable: true,
56+
selectable: true,
57+
input: {
58+
type: 'checkbox',
59+
name: 'checkbox3'
60+
},
61+
state: {
62+
expanded: false,
63+
selected: false,
64+
input: {
65+
value: true,
66+
disabled: true
67+
}
68+
},
69+
children: [
70+
{
71+
id: 'subsubnode1',
72+
label: 'An even deeper node',
73+
children: [],
74+
expandable: true,
75+
selectable: true,
76+
state: {
77+
expanded: false,
78+
selected: false
79+
}
80+
}
81+
]
82+
}
83+
]
84+
}
85+
];

tests/local/demo.css

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
body {
2+
background-color: tan;
3+
}
4+
5+
div.container {
6+
background-color: white;
7+
height: 100%;
8+
padding: 1rem;
9+
max-width: 1200px;
10+
margin: auto;
11+
}
12+
13+
section {
14+
margin-top: 2rem;
15+
}

0 commit comments

Comments
 (0)