Skip to content

Commit

Permalink
feat(selection): add ability to allow and forbid a node selection (cl…
Browse files Browse the repository at this point in the history
…oses #220) (#221)
  • Loading branch information
Georgii Rychko authored Feb 18, 2018
1 parent 63d85d9 commit 12852c9
Show file tree
Hide file tree
Showing 9 changed files with 199 additions and 14 deletions.
5 changes: 5 additions & 0 deletions src/demo/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ declare const alertify: any;
<div class="tree-controlls">
<p class="notice">Tree API exposed via TreeController</p>
<button button (click)="handleActionOnFFS(13, 'select')">Select 'boot' node</button>
<button button (click)="handleActionOnFFS(13, 'allowSelection')">Allow selection of the 'boot' node</button>
<button button (click)="handleActionOnFFS(13, 'forbidSelection')">Forbid selection of the 'boot' node</button>
<button button (click)="handleActionOnFFS(2, 'collapse')">Collapse 'bin' node</button>
<button button (click)="handleActionOnFFS(2, 'expand')">Expand 'bin' node</button>
<button button (click)="renameFFS(21)">Rename 'unicode.pf2' to 'unicode.pf'</button>
Expand Down Expand Up @@ -310,6 +312,9 @@ export class AppComponent implements OnInit {
{
value: 'boot',
id: 13,
settings: {
selectionAllowed: false
},
children: [
{
value: 'grub',
Expand Down
12 changes: 12 additions & 0 deletions src/tree-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,4 +128,16 @@ export class TreeController {
public isIndetermined(): boolean {
return get(this.component, 'checkboxElementRef.nativeElement.indeterminate');
}

public allowSelection() {
this.tree.selectionAllowed = true;
}

public forbidSelection() {
this.tree.selectionAllowed = false;
}

public isSelectionAllowed(): boolean {
return this.tree.selectionAllowed;
}
}
4 changes: 4 additions & 0 deletions src/tree-internal.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,10 @@ export class TreeInternalComponent implements OnInit, OnChanges, OnDestroy, Afte
}

public onNodeSelected(e: { button: number }): void {
if (!this.tree.selectionAllowed) {
return;
}

if (EventUtils.isLeftButtonClicked(e as MouseEvent)) {
this.isSelected = true;
this.treeService.fireNodeSelected(this.tree);
Expand Down
9 changes: 9 additions & 0 deletions src/tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,15 @@ export class Tree {
return this.hasLoadedChildern() ? this.children.filter(child => child.checked) : [];
}

public set selectionAllowed(selectionAllowed: boolean) {
this.node.settings = Object.assign({}, this.node.settings, { selectionAllowed });
}

public get selectionAllowed(): boolean {
const value = get(this.node.settings, 'selectionAllowed');
return isNil(value) ? true : !!value;
}

hasLoadedChildern() {
return !isEmpty(this.children);
}
Expand Down
14 changes: 10 additions & 4 deletions src/tree.types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { defaultsDeep, get } from './utils/fn.utils';
import { defaultsDeep, get, omit } from './utils/fn.utils';
import { NodeMenuItem } from './menu/node-menu.component';

export class FoldingType {
Expand Down Expand Up @@ -95,13 +95,19 @@ export class TreeModelSettings {

public checked?: boolean;

public static merge(sourceA: TreeModel, sourceB: TreeModel): TreeModelSettings {
return defaultsDeep({}, get(sourceA, 'settings'), get(sourceB, 'settings'), {
public selectionAllowed?: boolean;

public static readonly NOT_CASCADING_SETTINGS = ['selectionAllowed'];

public static merge(child: TreeModel, parent: TreeModel): TreeModelSettings {
const parentCascadingSettings = omit(get(parent, 'settings'), TreeModelSettings.NOT_CASCADING_SETTINGS);
return defaultsDeep({}, get(child, 'settings'), parentCascadingSettings, {
static: false,
leftMenu: false,
rightMenu: true,
isCollapsedOnInit: false,
checked: false
checked: false,
selectionAllowed: true
});
}
}
Expand Down
10 changes: 8 additions & 2 deletions src/utils/fn.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,15 @@ export function get(value: any, path: string, defaultValue?: any) {
return isNil(result) || result === value ? defaultValue : result;
}

export function omit(value: any, propToSkip: string): any {
export function omit(value: any, propsToSkip: string | string[]): any {
if (!value) {
return value;
}

const normalizedPropsToSkip = typeof propsToSkip === 'string' ? [propsToSkip] : propsToSkip;

return Object.keys(value).reduce((result, prop) => {
if (prop === propToSkip) {
if (includes(normalizedPropsToSkip, prop)) {
return result;
}
return Object.assign(result, { [prop]: value[prop] });
Expand Down
65 changes: 59 additions & 6 deletions test/data-provider/tree.data-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,68 @@ export class TreeDataProvider {
'default values': {
treeModelA: { value: '42' },
treeModelB: { value: '12' },
result: { static: false, leftMenu: false, rightMenu: true, isCollapsedOnInit: false, checked: false }
result: {
static: false,
leftMenu: false,
rightMenu: true,
isCollapsedOnInit: false,
checked: false,
selectionAllowed: true
}
},
'first settings source has higher priority': {
treeModelA: {
value: '42',
settings: { static: true, leftMenu: true, rightMenu: true, isCollapsedOnInit: true, checked: true }
settings: {
static: true,
leftMenu: true,
rightMenu: true,
isCollapsedOnInit: true,
checked: true,
selectionAllowed: false
}
},
treeModelB: {
value: '12',
settings: { static: false, leftMenu: false, rightMenu: false, isCollapsedOnInit: false, checked: false }
settings: {
static: false,
leftMenu: false,
rightMenu: false,
isCollapsedOnInit: false,
checked: false,
selectionAllowed: true
}
},
result: { static: true, leftMenu: true, rightMenu: true, isCollapsedOnInit: true, checked: true }
result: {
static: true,
leftMenu: true,
rightMenu: true,
isCollapsedOnInit: true,
checked: true,
selectionAllowed: false
}
},
'second settings source has priority if first settings source does not have the option': {
treeModelA: { value: '42' },
treeModelB: {
value: '12',
settings: { static: true, leftMenu: true, rightMenu: false, isCollapsedOnInit: true, checked: true }
settings: {
static: true,
leftMenu: true,
rightMenu: false,
isCollapsedOnInit: true,
checked: true,
selectionAllowed: false
}
},
result: { static: true, leftMenu: true, rightMenu: false, isCollapsedOnInit: true, checked: true }
result: {
static: true,
leftMenu: true,
rightMenu: false,
isCollapsedOnInit: true,
checked: true,
selectionAllowed: true
}
},
'first expanded property of cssClasses has higher priority': {
treeModelA: { value: '12', settings: { cssClasses: { expanded: 'arrow-down-o' } } },
Expand All @@ -36,6 +78,7 @@ export class TreeDataProvider {
leftMenu: false,
rightMenu: true,
checked: false,
selectionAllowed: true,
cssClasses: { expanded: 'arrow-down-o', collapsed: 'arrow-right', empty: 'arrow-gray', leaf: 'dot' }
}
},
Expand All @@ -51,6 +94,7 @@ export class TreeDataProvider {
leftMenu: false,
rightMenu: true,
checked: false,
selectionAllowed: true,
cssClasses: { expanded: 'arrow-down', collapsed: 'arrow-right-o', empty: 'arrow-gray', leaf: 'dot' }
}
},
Expand All @@ -66,6 +110,7 @@ export class TreeDataProvider {
leftMenu: false,
rightMenu: true,
checked: false,
selectionAllowed: true,
cssClasses: { expanded: 'arrow-down', collapsed: 'arrow-right', empty: 'arrow-gray-o', leaf: 'dot' }
}
},
Expand All @@ -81,6 +126,7 @@ export class TreeDataProvider {
leftMenu: false,
rightMenu: true,
checked: false,
selectionAllowed: true,
cssClasses: { expanded: 'arrow-down', collapsed: 'arrow-right', empty: 'arrow-gray', leaf: 'dot-o' }
}
},
Expand All @@ -101,6 +147,7 @@ export class TreeDataProvider {
leftMenu: false,
rightMenu: true,
checked: false,
selectionAllowed: true,
cssClasses: { expanded: 'arrow-down-o', collapsed: 'arrow-right-o', empty: 'arrow-gray-o', leaf: 'dot-o' }
}
},
Expand All @@ -118,6 +165,7 @@ export class TreeDataProvider {
leftMenu: true,
rightMenu: false,
checked: false,
selectionAllowed: true,
cssClasses: { expanded: 'arrow-down-o', collapsed: 'arrow-right-o', empty: 'arrow-gray-o', leaf: 'dot-o' }
}
},
Expand All @@ -139,6 +187,7 @@ export class TreeDataProvider {
leftMenu: false,
rightMenu: true,
checked: false,
selectionAllowed: true,
templates: {
node: '<i class="folder-o"></i>',
leaf: '<i class="file"></i>',
Expand All @@ -164,6 +213,7 @@ export class TreeDataProvider {
leftMenu: false,
rightMenu: true,
checked: false,
selectionAllowed: true,
templates: {
node: '<i class="folder"></i>',
leaf: '<i class="file-o"></i>',
Expand All @@ -189,6 +239,7 @@ export class TreeDataProvider {
leftMenu: false,
rightMenu: true,
checked: false,
selectionAllowed: true,
templates: {
node: '<i class="folder"></i>',
leaf: '<i class="file"></i>',
Expand Down Expand Up @@ -223,6 +274,7 @@ export class TreeDataProvider {
leftMenu: false,
rightMenu: true,
checked: false,
selectionAllowed: true,
templates: {
node: '<i class="folder-o"></i>',
leaf: '<i class="file-o"></i>',
Expand All @@ -248,6 +300,7 @@ export class TreeDataProvider {
leftMenu: true,
rightMenu: false,
checked: false,
selectionAllowed: true,
templates: {
node: '<i class="folder-o"></i>',
leaf: '<i class="file-o"></i>',
Expand Down
26 changes: 26 additions & 0 deletions test/tree-controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,32 @@ describe('TreeController', () => {
expect(controller.isChecked()).toBe(false);
});

it('forbids selection', () => {
const controller = treeService.getController(lordInternalTreeInstance.tree.id);
expect(controller.isSelectionAllowed()).toBe(true);

controller.forbidSelection();

fixture.detectChanges();

expect(controller.isSelectionAllowed()).toBe(false);
});

it('allows selection', () => {
const controller = treeService.getController(lordInternalTreeInstance.tree.id);
expect(controller.isSelectionAllowed()).toBe(true);

controller.forbidSelection();
fixture.detectChanges();

expect(controller.isSelectionAllowed()).toBe(false);

controller.allowSelection();
fixture.detectChanges();

expect(controller.isSelectionAllowed()).toBe(true);
});

it('checks all the children down the branch', () => {
const tree = lordInternalTreeInstance.tree;
const controller = treeService.getController(tree.id);
Expand Down
68 changes: 66 additions & 2 deletions test/tree.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1047,13 +1047,21 @@ describe('Tree', () => {
static: false,
leftMenu: false,
rightMenu: true,
checked: true
checked: true,
selectionAllowed: true
},
children: [
{
value: 'child#1',
emitLoadNextLevel: false,
settings: { isCollapsedOnInit: true, static: false, leftMenu: false, rightMenu: true, checked: true }
settings: {
isCollapsedOnInit: true,
static: false,
leftMenu: false,
rightMenu: true,
checked: true,
selectionAllowed: true
}
}
]
};
Expand All @@ -1063,6 +1071,62 @@ describe('Tree', () => {
expect(tree.toTreeModel()).toEqual(model);
});

it('has selection allowed by default', () => {
const model: TreeModel = {
id: 6,
value: 'root'
};

const tree: Tree = new Tree(model);

expect(tree.selectionAllowed).toBe(true);
});

it('can forbid selection', () => {
const model: TreeModel = {
id: 6,
value: 'root'
};

const tree: Tree = new Tree(model);
tree.selectionAllowed = false;

expect(tree.selectionAllowed).toBe(false);
});

it('can allow selection', () => {
const model: TreeModel = {
id: 6,
value: 'root',
settings: {
selectionAllowed: false
}
};

const tree: Tree = new Tree(model);

expect(tree.selectionAllowed).toBe(false);

tree.selectionAllowed = true;
expect(tree.selectionAllowed).toBe(true);
});

it('does not cascade selectionAllowed setting', () => {
const model: TreeModel = {
id: 6,
value: 'root',
settings: {
selectionAllowed: false
},
children: [{ value: 'foo' }]
};

const tree: Tree = new Tree(model);

expect(tree.selectionAllowed).toBe(false);
expect(tree.children[0].selectionAllowed).toBe(true);
});

it('has an access to menu items', () => {
const model: TreeModel = {
id: 42,
Expand Down

0 comments on commit 12852c9

Please sign in to comment.