Skip to content

Commit 12852c9

Browse files
author
Georgii Rychko
authored
feat(selection): add ability to allow and forbid a node selection (closes #220) (#221)
1 parent 63d85d9 commit 12852c9

File tree

9 files changed

+199
-14
lines changed

9 files changed

+199
-14
lines changed

src/demo/app/app.component.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ declare const alertify: any;
6666
<div class="tree-controlls">
6767
<p class="notice">Tree API exposed via TreeController</p>
6868
<button button (click)="handleActionOnFFS(13, 'select')">Select 'boot' node</button>
69+
<button button (click)="handleActionOnFFS(13, 'allowSelection')">Allow selection of the 'boot' node</button>
70+
<button button (click)="handleActionOnFFS(13, 'forbidSelection')">Forbid selection of the 'boot' node</button>
6971
<button button (click)="handleActionOnFFS(2, 'collapse')">Collapse 'bin' node</button>
7072
<button button (click)="handleActionOnFFS(2, 'expand')">Expand 'bin' node</button>
7173
<button button (click)="renameFFS(21)">Rename 'unicode.pf2' to 'unicode.pf'</button>
@@ -310,6 +312,9 @@ export class AppComponent implements OnInit {
310312
{
311313
value: 'boot',
312314
id: 13,
315+
settings: {
316+
selectionAllowed: false
317+
},
313318
children: [
314319
{
315320
value: 'grub',

src/tree-controller.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,4 +128,16 @@ export class TreeController {
128128
public isIndetermined(): boolean {
129129
return get(this.component, 'checkboxElementRef.nativeElement.indeterminate');
130130
}
131+
132+
public allowSelection() {
133+
this.tree.selectionAllowed = true;
134+
}
135+
136+
public forbidSelection() {
137+
this.tree.selectionAllowed = false;
138+
}
139+
140+
public isSelectionAllowed(): boolean {
141+
return this.tree.selectionAllowed;
142+
}
131143
}

src/tree-internal.component.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,10 @@ export class TreeInternalComponent implements OnInit, OnChanges, OnDestroy, Afte
183183
}
184184

185185
public onNodeSelected(e: { button: number }): void {
186+
if (!this.tree.selectionAllowed) {
187+
return;
188+
}
189+
186190
if (EventUtils.isLeftButtonClicked(e as MouseEvent)) {
187191
this.isSelected = true;
188192
this.treeService.fireNodeSelected(this.tree);

src/tree.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,15 @@ export class Tree {
240240
return this.hasLoadedChildern() ? this.children.filter(child => child.checked) : [];
241241
}
242242

243+
public set selectionAllowed(selectionAllowed: boolean) {
244+
this.node.settings = Object.assign({}, this.node.settings, { selectionAllowed });
245+
}
246+
247+
public get selectionAllowed(): boolean {
248+
const value = get(this.node.settings, 'selectionAllowed');
249+
return isNil(value) ? true : !!value;
250+
}
251+
243252
hasLoadedChildern() {
244253
return !isEmpty(this.children);
245254
}

src/tree.types.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { defaultsDeep, get } from './utils/fn.utils';
1+
import { defaultsDeep, get, omit } from './utils/fn.utils';
22
import { NodeMenuItem } from './menu/node-menu.component';
33

44
export class FoldingType {
@@ -95,13 +95,19 @@ export class TreeModelSettings {
9595

9696
public checked?: boolean;
9797

98-
public static merge(sourceA: TreeModel, sourceB: TreeModel): TreeModelSettings {
99-
return defaultsDeep({}, get(sourceA, 'settings'), get(sourceB, 'settings'), {
98+
public selectionAllowed?: boolean;
99+
100+
public static readonly NOT_CASCADING_SETTINGS = ['selectionAllowed'];
101+
102+
public static merge(child: TreeModel, parent: TreeModel): TreeModelSettings {
103+
const parentCascadingSettings = omit(get(parent, 'settings'), TreeModelSettings.NOT_CASCADING_SETTINGS);
104+
return defaultsDeep({}, get(child, 'settings'), parentCascadingSettings, {
100105
static: false,
101106
leftMenu: false,
102107
rightMenu: true,
103108
isCollapsedOnInit: false,
104-
checked: false
109+
checked: false,
110+
selectionAllowed: true
105111
});
106112
}
107113
}

src/utils/fn.utils.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,15 @@ export function get(value: any, path: string, defaultValue?: any) {
3636
return isNil(result) || result === value ? defaultValue : result;
3737
}
3838

39-
export function omit(value: any, propToSkip: string): any {
39+
export function omit(value: any, propsToSkip: string | string[]): any {
40+
if (!value) {
41+
return value;
42+
}
43+
44+
const normalizedPropsToSkip = typeof propsToSkip === 'string' ? [propsToSkip] : propsToSkip;
45+
4046
return Object.keys(value).reduce((result, prop) => {
41-
if (prop === propToSkip) {
47+
if (includes(normalizedPropsToSkip, prop)) {
4248
return result;
4349
}
4450
return Object.assign(result, { [prop]: value[prop] });

test/data-provider/tree.data-provider.ts

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,68 @@ export class TreeDataProvider {
33
'default values': {
44
treeModelA: { value: '42' },
55
treeModelB: { value: '12' },
6-
result: { static: false, leftMenu: false, rightMenu: true, isCollapsedOnInit: false, checked: false }
6+
result: {
7+
static: false,
8+
leftMenu: false,
9+
rightMenu: true,
10+
isCollapsedOnInit: false,
11+
checked: false,
12+
selectionAllowed: true
13+
}
714
},
815
'first settings source has higher priority': {
916
treeModelA: {
1017
value: '42',
11-
settings: { static: true, leftMenu: true, rightMenu: true, isCollapsedOnInit: true, checked: true }
18+
settings: {
19+
static: true,
20+
leftMenu: true,
21+
rightMenu: true,
22+
isCollapsedOnInit: true,
23+
checked: true,
24+
selectionAllowed: false
25+
}
1226
},
1327
treeModelB: {
1428
value: '12',
15-
settings: { static: false, leftMenu: false, rightMenu: false, isCollapsedOnInit: false, checked: false }
29+
settings: {
30+
static: false,
31+
leftMenu: false,
32+
rightMenu: false,
33+
isCollapsedOnInit: false,
34+
checked: false,
35+
selectionAllowed: true
36+
}
1637
},
17-
result: { static: true, leftMenu: true, rightMenu: true, isCollapsedOnInit: true, checked: true }
38+
result: {
39+
static: true,
40+
leftMenu: true,
41+
rightMenu: true,
42+
isCollapsedOnInit: true,
43+
checked: true,
44+
selectionAllowed: false
45+
}
1846
},
1947
'second settings source has priority if first settings source does not have the option': {
2048
treeModelA: { value: '42' },
2149
treeModelB: {
2250
value: '12',
23-
settings: { static: true, leftMenu: true, rightMenu: false, isCollapsedOnInit: true, checked: true }
51+
settings: {
52+
static: true,
53+
leftMenu: true,
54+
rightMenu: false,
55+
isCollapsedOnInit: true,
56+
checked: true,
57+
selectionAllowed: false
58+
}
2459
},
25-
result: { static: true, leftMenu: true, rightMenu: false, isCollapsedOnInit: true, checked: true }
60+
result: {
61+
static: true,
62+
leftMenu: true,
63+
rightMenu: false,
64+
isCollapsedOnInit: true,
65+
checked: true,
66+
selectionAllowed: true
67+
}
2668
},
2769
'first expanded property of cssClasses has higher priority': {
2870
treeModelA: { value: '12', settings: { cssClasses: { expanded: 'arrow-down-o' } } },
@@ -36,6 +78,7 @@ export class TreeDataProvider {
3678
leftMenu: false,
3779
rightMenu: true,
3880
checked: false,
81+
selectionAllowed: true,
3982
cssClasses: { expanded: 'arrow-down-o', collapsed: 'arrow-right', empty: 'arrow-gray', leaf: 'dot' }
4083
}
4184
},
@@ -51,6 +94,7 @@ export class TreeDataProvider {
5194
leftMenu: false,
5295
rightMenu: true,
5396
checked: false,
97+
selectionAllowed: true,
5498
cssClasses: { expanded: 'arrow-down', collapsed: 'arrow-right-o', empty: 'arrow-gray', leaf: 'dot' }
5599
}
56100
},
@@ -66,6 +110,7 @@ export class TreeDataProvider {
66110
leftMenu: false,
67111
rightMenu: true,
68112
checked: false,
113+
selectionAllowed: true,
69114
cssClasses: { expanded: 'arrow-down', collapsed: 'arrow-right', empty: 'arrow-gray-o', leaf: 'dot' }
70115
}
71116
},
@@ -81,6 +126,7 @@ export class TreeDataProvider {
81126
leftMenu: false,
82127
rightMenu: true,
83128
checked: false,
129+
selectionAllowed: true,
84130
cssClasses: { expanded: 'arrow-down', collapsed: 'arrow-right', empty: 'arrow-gray', leaf: 'dot-o' }
85131
}
86132
},
@@ -101,6 +147,7 @@ export class TreeDataProvider {
101147
leftMenu: false,
102148
rightMenu: true,
103149
checked: false,
150+
selectionAllowed: true,
104151
cssClasses: { expanded: 'arrow-down-o', collapsed: 'arrow-right-o', empty: 'arrow-gray-o', leaf: 'dot-o' }
105152
}
106153
},
@@ -118,6 +165,7 @@ export class TreeDataProvider {
118165
leftMenu: true,
119166
rightMenu: false,
120167
checked: false,
168+
selectionAllowed: true,
121169
cssClasses: { expanded: 'arrow-down-o', collapsed: 'arrow-right-o', empty: 'arrow-gray-o', leaf: 'dot-o' }
122170
}
123171
},
@@ -139,6 +187,7 @@ export class TreeDataProvider {
139187
leftMenu: false,
140188
rightMenu: true,
141189
checked: false,
190+
selectionAllowed: true,
142191
templates: {
143192
node: '<i class="folder-o"></i>',
144193
leaf: '<i class="file"></i>',
@@ -164,6 +213,7 @@ export class TreeDataProvider {
164213
leftMenu: false,
165214
rightMenu: true,
166215
checked: false,
216+
selectionAllowed: true,
167217
templates: {
168218
node: '<i class="folder"></i>',
169219
leaf: '<i class="file-o"></i>',
@@ -189,6 +239,7 @@ export class TreeDataProvider {
189239
leftMenu: false,
190240
rightMenu: true,
191241
checked: false,
242+
selectionAllowed: true,
192243
templates: {
193244
node: '<i class="folder"></i>',
194245
leaf: '<i class="file"></i>',
@@ -223,6 +274,7 @@ export class TreeDataProvider {
223274
leftMenu: false,
224275
rightMenu: true,
225276
checked: false,
277+
selectionAllowed: true,
226278
templates: {
227279
node: '<i class="folder-o"></i>',
228280
leaf: '<i class="file-o"></i>',
@@ -248,6 +300,7 @@ export class TreeDataProvider {
248300
leftMenu: true,
249301
rightMenu: false,
250302
checked: false,
303+
selectionAllowed: true,
251304
templates: {
252305
node: '<i class="folder-o"></i>',
253306
leaf: '<i class="file-o"></i>',

test/tree-controller.spec.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,32 @@ describe('TreeController', () => {
121121
expect(controller.isChecked()).toBe(false);
122122
});
123123

124+
it('forbids selection', () => {
125+
const controller = treeService.getController(lordInternalTreeInstance.tree.id);
126+
expect(controller.isSelectionAllowed()).toBe(true);
127+
128+
controller.forbidSelection();
129+
130+
fixture.detectChanges();
131+
132+
expect(controller.isSelectionAllowed()).toBe(false);
133+
});
134+
135+
it('allows selection', () => {
136+
const controller = treeService.getController(lordInternalTreeInstance.tree.id);
137+
expect(controller.isSelectionAllowed()).toBe(true);
138+
139+
controller.forbidSelection();
140+
fixture.detectChanges();
141+
142+
expect(controller.isSelectionAllowed()).toBe(false);
143+
144+
controller.allowSelection();
145+
fixture.detectChanges();
146+
147+
expect(controller.isSelectionAllowed()).toBe(true);
148+
});
149+
124150
it('checks all the children down the branch', () => {
125151
const tree = lordInternalTreeInstance.tree;
126152
const controller = treeService.getController(tree.id);

test/tree.spec.ts

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1047,13 +1047,21 @@ describe('Tree', () => {
10471047
static: false,
10481048
leftMenu: false,
10491049
rightMenu: true,
1050-
checked: true
1050+
checked: true,
1051+
selectionAllowed: true
10511052
},
10521053
children: [
10531054
{
10541055
value: 'child#1',
10551056
emitLoadNextLevel: false,
1056-
settings: { isCollapsedOnInit: true, static: false, leftMenu: false, rightMenu: true, checked: true }
1057+
settings: {
1058+
isCollapsedOnInit: true,
1059+
static: false,
1060+
leftMenu: false,
1061+
rightMenu: true,
1062+
checked: true,
1063+
selectionAllowed: true
1064+
}
10571065
}
10581066
]
10591067
};
@@ -1063,6 +1071,62 @@ describe('Tree', () => {
10631071
expect(tree.toTreeModel()).toEqual(model);
10641072
});
10651073

1074+
it('has selection allowed by default', () => {
1075+
const model: TreeModel = {
1076+
id: 6,
1077+
value: 'root'
1078+
};
1079+
1080+
const tree: Tree = new Tree(model);
1081+
1082+
expect(tree.selectionAllowed).toBe(true);
1083+
});
1084+
1085+
it('can forbid selection', () => {
1086+
const model: TreeModel = {
1087+
id: 6,
1088+
value: 'root'
1089+
};
1090+
1091+
const tree: Tree = new Tree(model);
1092+
tree.selectionAllowed = false;
1093+
1094+
expect(tree.selectionAllowed).toBe(false);
1095+
});
1096+
1097+
it('can allow selection', () => {
1098+
const model: TreeModel = {
1099+
id: 6,
1100+
value: 'root',
1101+
settings: {
1102+
selectionAllowed: false
1103+
}
1104+
};
1105+
1106+
const tree: Tree = new Tree(model);
1107+
1108+
expect(tree.selectionAllowed).toBe(false);
1109+
1110+
tree.selectionAllowed = true;
1111+
expect(tree.selectionAllowed).toBe(true);
1112+
});
1113+
1114+
it('does not cascade selectionAllowed setting', () => {
1115+
const model: TreeModel = {
1116+
id: 6,
1117+
value: 'root',
1118+
settings: {
1119+
selectionAllowed: false
1120+
},
1121+
children: [{ value: 'foo' }]
1122+
};
1123+
1124+
const tree: Tree = new Tree(model);
1125+
1126+
expect(tree.selectionAllowed).toBe(false);
1127+
expect(tree.children[0].selectionAllowed).toBe(true);
1128+
});
1129+
10661130
it('has an access to menu items', () => {
10671131
const model: TreeModel = {
10681132
id: 42,

0 commit comments

Comments
 (0)