Skip to content

Commit

Permalink
feat(tree): filtration (#UIM-14) (#195)
Browse files Browse the repository at this point in the history
* added base functions

* added filtration with saving parents

* added highlight pipe

* highlight pipe moved in core

* node will expand (toggle state) if the filter is not empty

* fixed class name (McHighlightPipe)

* fixed build

* fix after review

* fixed tests for tree

* added test on filtration

* fixed error in test

* forbade expand and collapse when filter not empty
  • Loading branch information
lskramarov authored Aug 16, 2019
1 parent 8efd084 commit 7fe6423
Show file tree
Hide file tree
Showing 28 changed files with 939 additions and 1,018 deletions.
18 changes: 13 additions & 5 deletions packages/cdk/tree/control/base-tree-control.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
import { SelectionModel } from '@angular/cdk/collections';
import { Observable } from 'rxjs';
import { BehaviorSubject, Observable } from 'rxjs';

import { ITreeControl } from './tree-control';
import { TreeControl } from './tree-control';


/** Base tree control. It has basic toggle/expand/collapse operations on a single data node. */
// todo здесь явно ошибка проектирования, абстрактный класс реализует функционал
/* tslint:disable-next-line:naming-convention */
export abstract class BaseTreeControl<T> implements ITreeControl<T> {
export abstract class BaseTreeControl<T> implements TreeControl<T> {

/** Saved data node for `expandAll` action. */
dataNodes: T[];

/** A selection model with multi-selection to track expansion status. */
expansionModel: SelectionModel<T> = new SelectionModel<T>(true);

filterModel: SelectionModel<T> = new SelectionModel<T>(true);

filterValue = new BehaviorSubject<string>('');

/** Get depth of a given data node, return the level number. This is for flat tree node. */
getLevel: (dataNode: T) => number;

Expand All @@ -35,16 +37,22 @@ export abstract class BaseTreeControl<T> implements ITreeControl<T> {

/** Toggles one single data node's expanded/collapsed state. */
toggle(dataNode: T): void {
if (this.filterValue.value) { return; }

this.expansionModel.toggle(dataNode);
}

/** Expands one single data node. */
expand(dataNode: T): void {
if (this.filterValue.value) { return; }

this.expansionModel.select(dataNode);
}

/** Collapses one single data node. */
collapse(dataNode: T): void {
if (this.filterValue.value) { return; }

this.expansionModel.deselect(dataNode);
}

Expand Down
36 changes: 34 additions & 2 deletions packages/cdk/tree/control/flat-tree-control.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export class FlatTreeControl<T> extends BaseTreeControl<T> {
/**
* Gets a list of the data node's subtree of descendent data nodes.
*
* To make this working, the `dataNodes` of the ITreeControl must be flattened tree nodes
* To make this working, the `dataNodes` of the TreeControl must be flattened tree nodes
* with correct levels.
*/
getDescendants(dataNode: T): T[] {
Expand All @@ -38,10 +38,42 @@ export class FlatTreeControl<T> extends BaseTreeControl<T> {
/**
* Expands all data nodes in the tree.
*
* To make this working, the `dataNodes` variable of the ITreeControl must be set to all flattened
* To make this working, the `dataNodes` variable of the TreeControl must be set to all flattened
* data nodes of the tree.
*/
expandAll(): void {
this.expansionModel.select(...this.dataNodes);
}

getParents(node: any, result: T[]): T[] {
if (node.parent) {
result.unshift(node.parent);

return this.getParents(node.parent, result);
} else {
return result;
}
}

compareFunction(name: string, value: string): boolean {
return RegExp(value, 'gi').test(name);
}

filterNodes(value: string): void {
this.filterModel.clear();

// todo нет возможности управлять параметром имени 'node.name'
const filteredNodes = this.dataNodes.filter((node: any) => this.compareFunction(node.name, value));

const filteredNodesWithTheirParents = new Set();
filteredNodes.forEach((filteredNode) => {
this.getParents(filteredNode, []).forEach((node) => filteredNodesWithTheirParents.add(node));

filteredNodesWithTheirParents.add(filteredNode);
});

this.filterModel.select(...Array.from(filteredNodesWithTheirParents));

this.filterValue.next(value);
}
}
2 changes: 1 addition & 1 deletion packages/cdk/tree/control/nested-tree-control.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export class NestedTreeControl<T> extends BaseTreeControl<T> {
/**
* Expands all dataNodes in the tree.
*
* To make this working, the `dataNodes` variable of the ITreeControl must be set to all root level
* To make this working, the `dataNodes` variable of the TreeControl must be set to all root level
* data nodes of the tree.
*/
expandAll(): void {
Expand Down
9 changes: 6 additions & 3 deletions packages/cdk/tree/control/tree-control.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,20 @@ import { Observable } from 'rxjs';


/**
* Tree control interface. User can implement ITreeControl to expand/collapse dataNodes in the tree.
* The CDKTree will use this ITreeControl to expand/collapse a node.
* Tree control interface. User can implement TreeControl to expand/collapse dataNodes in the tree.
* The CDKTree will use this TreeControl to expand/collapse a node.
* User can also use it outside the `<cdk-tree>` to control the expansion status of the tree.
*/
export interface ITreeControl<T> {
// tslint:disable-next-line:naming-convention
export interface TreeControl<T> {
/** The saved tree nodes data for `expandAll` action. */
dataNodes: T[];

/** The expansion model */
expansionModel: SelectionModel<T>;

filterModel: SelectionModel<T>;

/** Get depth of a given data node, return the level number. This is for flat tree node. */
getLevel(dataNode: T): number;

Expand Down
34 changes: 17 additions & 17 deletions packages/cdk/tree/tree._spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { map } from 'rxjs/operators';
import { BaseTreeControl } from './control/base-tree-control';
import { FlatTreeControl } from './control/flat-tree-control';
import { NestedTreeControl } from './control/nested-tree-control';
import { ITreeControl } from './control/tree-control';
import { TreeControl } from './control/tree-control';
import { CdkTreeModule } from './index';
import { CdkTree } from './tree';
import { getTreeControlFunctionsMissingError } from './tree-errors';
Expand Down Expand Up @@ -897,7 +897,7 @@ class FakeDataSource extends DataSource<TestData> {
this._dataChange.next(data);
}

constructor(public treeControl: ITreeControl<TestData>) {
constructor(public treeControl: TreeControl<TestData>) {
super();
for (let i = 0; i < 3; i++) {
this.addData();
Expand Down Expand Up @@ -1044,7 +1044,7 @@ class SimpleCdkTreeApp {
getLevel = (node: TestData) => node.level;
isExpandable = (node: TestData) => node.children.length > 0;

treeControl: ITreeControl<TestData> = new FlatTreeControl(this.getLevel, this.isExpandable);
treeControl: TreeControl<TestData> = new FlatTreeControl(this.getLevel, this.isExpandable);

dataSource: FakeDataSource | null = new FakeDataSource(this.treeControl);

Expand All @@ -1065,7 +1065,7 @@ class SimpleCdkTreeApp {
class NestedCdkTreeApp {
getChildren = (node: TestData) => node.observableChildren;

treeControl: ITreeControl<TestData> = new NestedTreeControl(this.getChildren);
treeControl: TreeControl<TestData> = new NestedTreeControl(this.getChildren);

dataSource: FakeDataSource | null = new FakeDataSource(this.treeControl);

Expand All @@ -1091,7 +1091,7 @@ class WhenNodeNestedCdkTreeApp {

getChildren = (node: TestData) => node.observableChildren;

treeControl: ITreeControl<TestData> = new NestedTreeControl(this.getChildren);
treeControl: TreeControl<TestData> = new NestedTreeControl(this.getChildren);

dataSource: FakeDataSource | null = new FakeDataSource(this.treeControl);

Expand All @@ -1116,7 +1116,7 @@ class CdkTreeAppWithToggle {
getLevel = (node: TestData) => node.level;
isExpandable = (node: TestData) => node.children.length > 0;

treeControl: ITreeControl<TestData> = new FlatTreeControl(this.getLevel, this.isExpandable);
treeControl: TreeControl<TestData> = new FlatTreeControl(this.getLevel, this.isExpandable);
dataSource: FakeDataSource | null = new FakeDataSource(this.treeControl);

@ViewChild(CdkTree, {static: false}) tree: CdkTree<TestData>;
Expand All @@ -1140,7 +1140,7 @@ class NestedCdkTreeAppWithToggle {

getChildren = (node: TestData) => node.observableChildren;

treeControl: ITreeControl<TestData> = new NestedTreeControl(this.getChildren);
treeControl: TreeControl<TestData> = new NestedTreeControl(this.getChildren);
dataSource: FakeDataSource | null = new FakeDataSource(this.treeControl);

@ViewChild(CdkTree, {static: false}) tree: CdkTree<TestData>;
Expand All @@ -1167,7 +1167,7 @@ class WhenNodeCdkTreeApp {
getLevel = (node: TestData) => node.level;
isExpandable = (node: TestData) => node.children.length > 0;

treeControl: ITreeControl<TestData> = new FlatTreeControl(this.getLevel, this.isExpandable);
treeControl: TreeControl<TestData> = new FlatTreeControl(this.getLevel, this.isExpandable);

dataSource: FakeDataSource | null = new FakeDataSource(this.treeControl);

Expand All @@ -1189,7 +1189,7 @@ class ArrayDataSourceCdkTreeApp {
getLevel = (node: TestData) => node.level;
isExpandable = (node: TestData) => node.children.length > 0;

treeControl: ITreeControl<TestData> = new FlatTreeControl(this.getLevel, this.isExpandable);
treeControl: TreeControl<TestData> = new FlatTreeControl(this.getLevel, this.isExpandable);

dataSource: FakeDataSource = new FakeDataSource(this.treeControl);

Expand All @@ -1215,7 +1215,7 @@ class ObservableDataSourceCdkTreeApp {
getLevel = (node: TestData) => node.level;
isExpandable = (node: TestData) => node.children.length > 0;

treeControl: ITreeControl<TestData> = new FlatTreeControl(this.getLevel, this.isExpandable);
treeControl: TreeControl<TestData> = new FlatTreeControl(this.getLevel, this.isExpandable);

dataSource: FakeDataSource = new FakeDataSource(this.treeControl);

Expand All @@ -1240,7 +1240,7 @@ class ArrayDataSourceNestedCdkTreeApp {

getChildren = (node: TestData) => node.observableChildren;

treeControl: ITreeControl<TestData> = new NestedTreeControl(this.getChildren);
treeControl: TreeControl<TestData> = new NestedTreeControl(this.getChildren);

dataSource: FakeDataSource = new FakeDataSource(this.treeControl);

Expand All @@ -1265,7 +1265,7 @@ class ObservableDataSourceNestedCdkTreeApp {

getChildren = (node: TestData) => node.observableChildren;

treeControl: ITreeControl<TestData> = new NestedTreeControl(this.getChildren);
treeControl: TreeControl<TestData> = new NestedTreeControl(this.getChildren);

dataSource: FakeDataSource = new FakeDataSource(this.treeControl);

Expand All @@ -1291,7 +1291,7 @@ class NestedCdkErrorTreeApp {

isExpandable = (node: TestData) => node.children.length > 0;

treeControl: ITreeControl<TestData> = new FlatTreeControl(this.getLevel, this.isExpandable);
treeControl: TreeControl<TestData> = new FlatTreeControl(this.getLevel, this.isExpandable);

dataSource: FakeDataSource | null = new FakeDataSource(this.treeControl);

Expand Down Expand Up @@ -1324,7 +1324,7 @@ class FlatCdkErrorTreeApp {

isExpandable = (node: TestData) => node.children.length > 0;

treeControl: ITreeControl<TestData> = new FakeTreeControl();
treeControl: TreeControl<TestData> = new FakeTreeControl();

dataSource: FakeDataSource | null = new FakeDataSource(this.treeControl);

Expand All @@ -1347,7 +1347,7 @@ class DepthNestedCdkTreeApp {

getChildren = (node: TestData) => node.observableChildren;

treeControl: ITreeControl<TestData> = new NestedTreeControl(this.getChildren);
treeControl: TreeControl<TestData> = new NestedTreeControl(this.getChildren);

dataSource: FakeDataSource = new FakeDataSource(this.treeControl);

Expand Down Expand Up @@ -1384,7 +1384,7 @@ class CdkTreeAppWithTrackBy {
getLevel = (node: TestData) => node.level;
isExpandable = (node: TestData) => node.children.length > 0;

treeControl: ITreeControl<TestData> = new FlatTreeControl(this.getLevel, this.isExpandable);
treeControl: TreeControl<TestData> = new FlatTreeControl(this.getLevel, this.isExpandable);
dataSource: FakeDataSource = new FakeDataSource(this.treeControl);

@ViewChild(CdkTree, {static: false}) tree: CdkTree<TestData>;
Expand Down Expand Up @@ -1416,7 +1416,7 @@ class NestedCdkTreeAppWithTrackBy {

getChildren = (node: TestData) => node.observableChildren;

treeControl: ITreeControl<TestData> = new NestedTreeControl(this.getChildren);
treeControl: TreeControl<TestData> = new NestedTreeControl(this.getChildren);

dataSource: FakeDataSource = new FakeDataSource(this.treeControl);

Expand Down
4 changes: 2 additions & 2 deletions packages/cdk/tree/tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { IFocusableOption } from '@ptsecurity/cdk/a11y';
import { BehaviorSubject, Observable, of as observableOf, Subject, Subscription } from 'rxjs';
import { takeUntil } from 'rxjs/operators';

import { ITreeControl } from './control/tree-control';
import { TreeControl } from './control/tree-control';
import { CdkTreeNodeDef, CdkTreeNodeOutletContext } from './node';
import { CdkTreeNodeOutlet } from './outlet';
import {
Expand Down Expand Up @@ -53,7 +53,7 @@ import {
export class CdkTree<T> implements AfterContentChecked, CollectionViewer, OnDestroy, OnInit {

/** The tree controller */
@Input() treeControl: ITreeControl<T>;
@Input() treeControl: TreeControl<T>;

/**
* Tracking function that will be used to check the differences in data changes. Used similarly
Expand Down
23 changes: 10 additions & 13 deletions packages/mosaic-dev/all/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import { McToolTipModule } from '@ptsecurity/mosaic/tooltip';
import { McTreeFlatDataSource, McTreeFlattener, McTreeModule } from '@ptsecurity/mosaic/tree';
import { Observable, of as observableOf } from 'rxjs';

import { FileDatabase, FileFlatNode, FileNode } from '../tree/module';
import { buildFileTree, FileFlatNode, FileNode, DATA_OBJECT } from '../tree/module';

// Depending on whether rollup is used, moment needs to be imported differently.
// Since Moment.js doesn't have a default export, we normally need to import using the `* as`
Expand All @@ -57,8 +57,7 @@ const MAX_PERCENT: number = 100;
selector: 'app',
template: require('./template.html'),
styleUrls: ['./styles.scss'],
encapsulation: ViewEncapsulation.None,
providers: [FileDatabase]
encapsulation: ViewEncapsulation.None
})
export class DemoComponent {
checked: boolean[] = [true, true, false];
Expand Down Expand Up @@ -129,21 +128,19 @@ export class DemoComponent {
dataSource: McTreeFlatDataSource<FileNode, FileFlatNode>;
treeFlattener: McTreeFlattener<FileNode, FileFlatNode>;

constructor(private modalService: McModalService, database: FileDatabase) {
constructor(private modalService: McModalService) {
setInterval(() => {
this.percent = (this.percent + STEP) % (MAX_PERCENT + STEP);
}, INTERVAL);

this.treeFlattener = new McTreeFlattener(
this.transformer, this._getLevel, this._isExpandable, this._getChildren
this.transformer, this.getLevel, this.isExpandable, this.getChildren
);

this.treeControl = new FlatTreeControl<FileFlatNode>(this._getLevel, this._isExpandable);
this.treeControl = new FlatTreeControl<FileFlatNode>(this.getLevel, this.isExpandable);
this.dataSource = new McTreeFlatDataSource(this.treeControl, this.treeFlattener);

database.dataChange.subscribe((data) => {
this.dataSource.data = data;
});
this.dataSource.data = buildFileTree(DATA_OBJECT, 0);
}

showConfirm() {
Expand All @@ -170,7 +167,7 @@ export class DemoComponent {
return flatNode;
}

hasChild(_: number, _nodeData: FileFlatNode) { return _nodeData.expandable; }
hasChild(_: number, nodeData: FileFlatNode) { return nodeData.expandable; }

hasNestedChild(_: number, nodeData: FileNode) {
return !(nodeData.type);
Expand All @@ -180,11 +177,11 @@ export class DemoComponent {
clearInterval(this.intervalId);
}

private _getLevel(node: FileFlatNode) { return node.level; }
private getLevel(node: FileFlatNode) { return node.level; }

private _isExpandable(node: FileFlatNode) { return node.expandable; }
private isExpandable(node: FileFlatNode) { return node.expandable; }

private _getChildren = (node: FileNode): Observable<FileNode[]> => {
private getChildren = (node: FileNode): Observable<FileNode[]> => {
return observableOf(node.children);
}

Expand Down
Loading

0 comments on commit 7fe6423

Please sign in to comment.