Skip to content

Commit 15597c1

Browse files
committed
feat(node menu): add support of node renaming
1 parent be3d56e commit 15597c1

7 files changed

Lines changed: 207 additions & 61 deletions

File tree

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import {Directive, ElementRef, Input, OnInit, Output, EventEmitter, HostListener} from 'angular2/core';
2+
import {Ng2TreeService} from './ng2-tree.service';
3+
4+
@Directive({
5+
selector: '[editable]'
6+
})
7+
export class EditableNodeDirective implements OnInit {
8+
@Input()
9+
nodeValue: string;
10+
11+
@Output()
12+
valueChanged: EventEmitter<any> = new EventEmitter(false);
13+
14+
private element: any;
15+
private treeService: Ng2TreeService;
16+
17+
constructor(elementRef: ElementRef, treeService: Ng2TreeService) {
18+
this.element = elementRef;
19+
this.treeService = treeService;
20+
}
21+
22+
ngOnInit(): void {
23+
this.element.nativeElement.focus();
24+
this.element.nativeElement.value = this.nodeValue;
25+
}
26+
27+
@HostListener('keyup', ['$event', '$event.target.value'])
28+
private editCompleted($event: any, newValue: any) {
29+
//13 - enter
30+
//27 - esc
31+
if ($event.keyCode === 13) {
32+
// http://stackoverflow.com/questions/35515254/what-is-a-dehydrated-detector-and-how-am-i-using-one-here
33+
setTimeout(() => this.valueChanged.emit({value: newValue}), 1);
34+
}
35+
}
36+
37+
@HostListener('blur', ['$event', '$event.target.value'])
38+
private editCompletedByMouse($event: any, newValue: any) {
39+
//13 - enter
40+
//27 - esc
41+
this.valueChanged.emit({value: newValue});
42+
}
43+
}

components/ng2-tree.component.ts

Lines changed: 100 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,123 @@
1-
import {Input, Component} from "angular2/core";
2-
import {CORE_DIRECTIVES} from "angular2/common";
1+
import {Input, Component, OnInit} from 'angular2/core';
2+
import {CORE_DIRECTIVES} from 'angular2/common';
3+
import {Ng2TreeService} from './ng2-tree.service';
4+
import {EditableNodeDirective} from './editable-node.directive.ts';
35

46
@Component({
5-
selector: 'ng2-tree',
6-
template: `
7-
<ul class="tree">
7+
selector: 'ng2-tree',
8+
template: `
9+
<ul class="tree" (keyup)="cancelActions($event)">
810
<li>
9-
<span class="folding" (click)="switchFolding($event, tree)" [ngClass]="foldingType(tree)"></span>
10-
<span class="node-value">{{tree.value}}</span>
11+
<div (mouseup)="showMenu($event)">
12+
<span class="folding" (click)="switchFolding($event, tree)" [ngClass]="foldingType(tree)"></span>
13+
<span class="node-value" *ngIf="!edit">{{tree.value}}</span>
14+
<input type="text" class="node-value" editable [nodeValue]="tree.value" (valueChanged)="applyNewValue($event, tree)" *ngIf="edit"/>
15+
</div>
16+
<div class="node-menu" *ngIf="isMenuVisible">
17+
<ul class="node-menu-content">
18+
<li (click)="rename($event, tree)">Rename node</li>
19+
<li>Add node</li>
20+
<li>Remove node</li>
21+
</ul>
22+
</div>
1123
<ng2-tree *ngFor="#child of tree.children" [tree]="child"></ng2-tree>
1224
</li>
1325
</ul>
1426
`,
15-
directives: [Ng2Tree, CORE_DIRECTIVES]
27+
directives: [EditableNodeDirective, Ng2Tree, CORE_DIRECTIVES]
1628
})
17-
export class Ng2Tree {
18-
private static COMPONENT_TAG_NAME: string = 'NG2-TREE';
19-
private static FOLDING_NODE_EXPANDED: string = 'node-expanded';
20-
private static FOLDING_NODE_COLLAPSED: string = 'node-collapsed';
21-
private static FOLDING_NODE_LEAF: string = 'node-leaf';
29+
export class Ng2Tree implements OnInit {
30+
private static COMPONENT_TAG_NAME: string = 'NG2-TREE';
31+
private static FOLDING_NODE_EXPANDED: string = 'node-expanded';
32+
private static FOLDING_NODE_COLLAPSED: string = 'node-collapsed';
33+
private static FOLDING_NODE_LEAF: string = 'node-leaf';
2234

23-
@Input()
24-
private tree: any;
35+
@Input()
36+
private tree: any;
2537

26-
private switchFolding($event: any, tree: any): void {
27-
this.handleFoldingType($event.target.parentNode, tree);
38+
private treeService: Ng2TreeService;
39+
private isMenuVisible: boolean = false;
40+
private edit: boolean = false;
41+
42+
constructor(treeService: Ng2TreeService) {
43+
this.treeService = treeService;
44+
}
45+
46+
private switchFolding($event: any, tree: any): void {
47+
console.log('fold');
48+
this.handleFoldingType($event.target.parentNode.parentNode, tree);
49+
}
50+
51+
private foldingType(node: any): any {
52+
if (node.foldingType) {
53+
return node.foldingType;
2854
}
2955

30-
private foldingType(node: any): any {
31-
if (node.foldingType) {
32-
return node.foldingType;
33-
}
56+
if (node.children) {
57+
node.foldingType = Ng2Tree.FOLDING_NODE_EXPANDED;
58+
} else {
59+
node.foldingType = Ng2Tree.FOLDING_NODE_LEAF;
60+
}
3461

35-
if (node.children) {
36-
node.foldingType = Ng2Tree.FOLDING_NODE_EXPANDED;
37-
} else {
38-
node.foldingType = Ng2Tree.FOLDING_NODE_LEAF;
39-
}
62+
return node.foldingType;
63+
}
4064

41-
return node.foldingType;
65+
private nextFoldingType(node: any): string {
66+
if (node.foldingType === Ng2Tree.FOLDING_NODE_EXPANDED) {
67+
return Ng2Tree.FOLDING_NODE_COLLAPSED;
4268
}
4369

44-
private nextFoldingType(node: any): string {
45-
if (node.foldingType === Ng2Tree.FOLDING_NODE_EXPANDED) {
46-
return Ng2Tree.FOLDING_NODE_COLLAPSED;
47-
}
70+
return Ng2Tree.FOLDING_NODE_EXPANDED;
71+
}
4872

49-
return Ng2Tree.FOLDING_NODE_EXPANDED;
73+
private handleFoldingType(parent: any, node: any) {
74+
if (node.foldingType === Ng2Tree.FOLDING_NODE_LEAF) {
75+
return;
5076
}
5177

52-
private handleFoldingType(parent: any, node: any) {
53-
if (node.foldingType === Ng2Tree.FOLDING_NODE_LEAF) {
54-
return;
55-
}
78+
let display = 'block';
79+
if (node.foldingType === Ng2Tree.FOLDING_NODE_EXPANDED) {
80+
display = 'none';
81+
}
5682

57-
let display = 'block';
58-
if (node.foldingType === Ng2Tree.FOLDING_NODE_EXPANDED) {
59-
display = 'none';
60-
}
83+
node.foldingType = this.nextFoldingType(node);
84+
for (let element of parent.childNodes) {
85+
if (element.nodeName === Ng2Tree.COMPONENT_TAG_NAME) {
86+
element.style.display = display;
87+
}
88+
}
89+
}
6190

62-
node.foldingType = this.nextFoldingType(node);
63-
for (let element of parent.childNodes) {
64-
if (element.nodeName === Ng2Tree.COMPONENT_TAG_NAME) {
65-
element.style.display = display;
66-
}
67-
}
91+
private rename($event: any, node: any) {
92+
if ($event.which === 1) {
93+
this.edit = true;
94+
this.isMenuVisible = false;
95+
}
96+
}
97+
98+
private showMenu($event: any): void {
99+
if ($event.which === 3) {
100+
this.isMenuVisible = !this.isMenuVisible;
101+
this.treeService.emitMenuEvent({sender: this, action: 'close'})
68102
}
103+
$event.preventDefault();
104+
}
105+
106+
private cancelActions($event: any): void {
107+
108+
}
109+
110+
private applyNewValue($event: any, node: any): void {
111+
node.value = $event.value;
112+
this.edit = false;
113+
}
114+
115+
ngOnInit(): void {
116+
this.treeService.menuEventStream()
117+
.subscribe(menuEvent => {
118+
if (menuEvent.sender !== this && menuEvent.action === 'close') {
119+
this.isMenuVisible = false;
120+
}
121+
})
122+
}
69123
}

components/ng2-tree.service.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import {Injectable, EventEmitter} from 'angular2/core';
2+
import {Observable} from 'rxjs/Observable';
3+
4+
@Injectable()
5+
export class Ng2TreeService {
6+
private menuEvents$:EventEmitter<any> = new EventEmitter();
7+
private cancelEvents$:EventEmitter<any> = new EventEmitter();
8+
9+
menuEventStream(): Observable<any> {
10+
return this.menuEvents$;
11+
}
12+
13+
emitMenuEvent(event: any): void {
14+
this.menuEvents$.emit(event);
15+
}
16+
17+
cancelEventStream(): Observable<any> {
18+
return this.cancelEvents$;
19+
}
20+
21+
emitCancelEvent(event: any): void {
22+
this.cancelEvents$.emit(event);
23+
}
24+
}

demo/app.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import {bootstrap} from 'angular2/platform/browser';
44
import {Component} from 'angular2/core';
55
import {Ng2Tree} from '../ng2-tree';
6+
import {Ng2TreeService} from '../components/ng2-tree.service';
67

78
require('./styles.styl');
89

@@ -63,4 +64,4 @@ class App {
6364
};
6465
}
6566

66-
bootstrap(App);
67+
bootstrap(App, [Ng2TreeService]);

demo/colors.styl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
folding-color = orange
2-
node-value-color = green
2+
node-value-color = green
3+
node-menu-active-item = orange
4+
node-menu-background = #e1e1e1

demo/menu.styl

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
.node-menu
2+
position relative
3+
width 100px
4+
5+
.node-menu-content
6+
width 100%
7+
padding-left 0
8+
position absolute
9+
background-color node-menu-background
10+
box-shadow 2px 4px 8px
11+
12+
li
13+
&:hover
14+
opacity unset
15+
cursor pointer
16+
background-color node-menu-active-item

demo/styles.styl

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,43 @@
11
@import colors
2+
@import menu
23

34
node-expanded-symbol = '[-]'
45
node-collapsed-symbol = '[+]'
56
leaf-node-symbol = '\25cf' //black circle
67

8+
li
9+
padding 0
10+
margin 0
11+
list-style none
12+
713
.tree
8-
font-family: monospace
14+
font-family monospace
915

1016
li
11-
padding: 3px 0
12-
list-style: none
17+
padding 3px 0
18+
list-style none
1319

1420
.folding
15-
cursor: pointer
16-
padding: 0 5px 0 5px
17-
font-weight: bold
18-
color: folding-color
21+
cursor pointer
22+
padding 0 5px 0 5px
23+
font-weight bold
24+
color folding-color
1925

2026
&.node-expanded
2127
&:after
22-
content: node-expanded-symbol
28+
content node-expanded-symbol
2329

2430
&.node-collapsed
2531
&:after
26-
content: node-collapsed-symbol
32+
content node-collapsed-symbol
2733

2834

2935
&.node-leaf
30-
cursor: default
36+
cursor default
3137

3238
&:after
33-
content: leaf-node-symbol
39+
content leaf-node-symbol
3440

3541
.node-value
36-
font-weight: bold
37-
color: node-value-color
42+
font-weight bold
43+
color node-value-color

0 commit comments

Comments
 (0)