Skip to content

Commit 8a7ea34

Browse files
committed
feat(tree): add tree demo pages (#8749)
1 parent 0dc7d57 commit 8a7ea34

File tree

10 files changed

+357
-6
lines changed

10 files changed

+357
-6
lines changed

src/demo-app/demo-app/demo-app.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ export class DemoApp {
8686
{name: 'Tabs', route: '/tabs'},
8787
{name: 'Toolbar', route: '/toolbar'},
8888
{name: 'Tooltip', route: '/tooltip'},
89+
{name: 'Tree', route: '/tree'},
8990
{name: 'Typography', route: '/typography'}
9091
];
9192

src/demo-app/demo-app/demo-module.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ import {LayoutModule} from '@angular/cdk/layout';
5454
import {FoggyTabContent, RainyTabContent, SunnyTabContent, TabsDemo} from '../tabs/tabs-demo';
5555
import {ToolbarDemo} from '../toolbar/toolbar-demo';
5656
import {TooltipDemo} from '../tooltip/tooltip-demo';
57+
import {TreeDemo} from '../tree/tree-demo';
58+
import {JsonDatabase} from '../tree/json-database';
5759
import {TypographyDemo} from '../typography/typography-demo';
5860
import {DemoApp, Home} from './demo-app';
5961
import {DEMO_APP_ROUTES} from './routes';
@@ -119,10 +121,13 @@ import {TableDemoModule} from '../table/table-demo-module';
119121
TabsDemo,
120122
ToolbarDemo,
121123
TooltipDemo,
124+
TreeDemo,
122125
TypographyDemo,
123126
],
124127
providers: [
125128
{provide: OverlayContainer, useClass: FullscreenOverlayContainer},
129+
PeopleDatabase,
130+
JsonDatabase
126131
],
127132
entryComponents: [
128133
ContentElementDialog,

src/demo-app/demo-app/routes.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import {TABS_DEMO_ROUTES} from '../tabs/routes';
4646
import {TabsDemo} from '../tabs/tabs-demo';
4747
import {ToolbarDemo} from '../toolbar/toolbar-demo';
4848
import {TooltipDemo} from '../tooltip/tooltip-demo';
49+
import {TreeDemo} from '../tree/tree-demo';
4950
import {TypographyDemo} from '../typography/typography-demo';
5051
import {DemoApp, Home} from './demo-app';
5152
import {TableDemoPage} from '../table/table-demo-page';
@@ -90,6 +91,7 @@ export const DEMO_APP_ROUTES: Routes = [
9091
{path: 'tabs', component: TabsDemo, children: TABS_DEMO_ROUTES},
9192
{path: 'toolbar', component: ToolbarDemo},
9293
{path: 'tooltip', component: TooltipDemo},
94+
{path: 'tree', component: TreeDemo},
9395
{path: 'typography', component: TypographyDemo},
9496
{path: 'expansion', component: ExpansionDemo},
9597
{path: 'stepper', component: StepperDemo},

src/demo-app/tree/flat-data-source.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {CollectionViewer, DataSource} from '@angular/cdk/collections';
10+
import {FlatTreeControl, TreeControl} from '@angular/cdk/tree';
11+
import {Observable} from 'rxjs/Observable';
12+
import {merge} from 'rxjs/observable/merge';
13+
import {map} from 'rxjs/operators/map';
14+
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
15+
import {JsonNode, JsonDatabase} from './json-database';
16+
17+
/** Flat node with expandable and level information */
18+
export class JsonFlatNode {
19+
key: string;
20+
value: any;
21+
level: number;
22+
expandable: boolean;
23+
}
24+
25+
function _flattenNode(node: JsonNode, level: number,
26+
resultNodes: JsonFlatNode[], parentMap: boolean[]) {
27+
let flatNode: JsonFlatNode = new JsonFlatNode();
28+
flatNode.key = node.key;
29+
flatNode.value = node.value;
30+
flatNode.level = level;
31+
flatNode.expandable = !!node.children;
32+
resultNodes.push(flatNode);
33+
34+
if (flatNode.expandable) {
35+
node.children.forEach((child, index) => {
36+
let childParentMap: boolean[] = parentMap.slice();
37+
childParentMap.push(index != node.children.length - 1);
38+
_flattenNode(child, level + 1, resultNodes, childParentMap);
39+
});
40+
}
41+
return resultNodes;
42+
}
43+
44+
/** Tree flattener to transfrom JsonNode to JsonFlatNode */
45+
export function flattenNodes(structuredData: JsonNode[]): JsonFlatNode[] {
46+
let resultNodes: JsonFlatNode[] = [];
47+
structuredData.forEach(node => _flattenNode(node, 0, resultNodes, []));
48+
return resultNodes;
49+
}
50+
51+
export function expandFlattenedNodes(nodes: JsonFlatNode[],
52+
treeControl: TreeControl<JsonFlatNode>): JsonFlatNode[] {
53+
let results: JsonFlatNode[] = [];
54+
let currentExpand: boolean[] = [];
55+
currentExpand[0] = true;
56+
57+
nodes.forEach((node) => {
58+
let expand = true;
59+
for (let i = 0; i <= node.level; i++) {
60+
expand = expand && currentExpand[i];
61+
}
62+
if (expand) {
63+
results.push(node);
64+
}
65+
if (node.expandable) {
66+
currentExpand[node.level + 1] = treeControl.isExpanded(node);
67+
}
68+
});
69+
return results;
70+
}
71+
72+
/** Flat data source */
73+
export class FlatDataSource implements DataSource<any> {
74+
_flattenedData = new BehaviorSubject<any>([]);
75+
get flattenedData() { return this._flattenedData.value; }
76+
77+
_expandedData = new BehaviorSubject<any>([]);
78+
get expandedData() { return this._expandedData.value; }
79+
80+
constructor(database: JsonDatabase, private treeControl: FlatTreeControl<JsonFlatNode>) {
81+
database.dataChange.subscribe((tree) => {
82+
this._flattenedData.next(flattenNodes(tree));
83+
this.treeControl.dataNodes = this.flattenedData;
84+
});
85+
}
86+
87+
connect(collectionViewer: CollectionViewer): Observable<JsonFlatNode[]> {
88+
return merge([
89+
collectionViewer.viewChange,
90+
this.treeControl.expansionModel.onChange,
91+
this._flattenedData])
92+
.pipe(map(() => {
93+
this._expandedData.next(
94+
expandFlattenedNodes(this.flattenedData, this.treeControl));
95+
return this.expandedData;
96+
}));
97+
}
98+
99+
disconnect() {
100+
}
101+
}
102+

src/demo-app/tree/json-database.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {Injectable} from '@angular/core';
10+
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
11+
12+
export class JsonNode {
13+
children: JsonNode[];
14+
key: string;
15+
value: any;
16+
}
17+
18+
const TREE_DATA = `{"Tina":
19+
{
20+
"Documents": {
21+
"angular": {
22+
"src": {
23+
"core": "ts",
24+
"compiler": "ts"
25+
}
26+
},
27+
"material2": {
28+
"src": {
29+
"button": "ts",
30+
"checkbox": "ts",
31+
"input": "ts"
32+
}
33+
}
34+
},
35+
"Downloads": {
36+
"Tutorial": "html",
37+
"November": "pdf",
38+
"October": "pdf"
39+
},
40+
"Pictures": {
41+
"Sun": "png",
42+
"Woods": "jpg",
43+
"Photo Booth Library": {
44+
"Contents": "dir",
45+
"Pictures": "dir"
46+
}
47+
},
48+
"Applications": {
49+
"Chrome": "app",
50+
"Calendar": "app",
51+
"Webstorm": "app"
52+
}
53+
}}
54+
`;
55+
56+
@Injectable()
57+
export class JsonDatabase {
58+
dataChange: BehaviorSubject<JsonNode[]> = new BehaviorSubject<JsonNode[]>([]);
59+
60+
get data(): JsonNode[] { return this.dataChange.value; }
61+
62+
constructor() {
63+
this.initialize();
64+
}
65+
66+
initialize() {
67+
const dataObject = JSON.parse(TREE_DATA);
68+
const data = this.buildJsonTree(dataObject, 0);
69+
this.dataChange.next(data);
70+
}
71+
72+
buildJsonTree(value: any, level: number) {
73+
let data: any[] = [];
74+
for (let k in value) {
75+
let v = value[k];
76+
let node = new JsonNode();
77+
node.key = `${k}`;
78+
if (v === null || v === undefined) {
79+
// no action
80+
} else if (typeof v === 'object') {
81+
node.children = this.buildJsonTree(v, level + 1);
82+
} else {
83+
node.value = v;
84+
}
85+
data.push(node);
86+
}
87+
return data;
88+
}
89+
90+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {CollectionViewer, DataSource} from '@angular/cdk/collections';
10+
import {Observable} from 'rxjs/Observable';
11+
import {merge} from 'rxjs/observable/merge';
12+
import {map} from 'rxjs/operators/map';
13+
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
14+
15+
import {JsonNode, JsonDatabase} from './json-database';
16+
17+
export class JsonNestedDataSource implements DataSource<any> {
18+
_renderedData = new BehaviorSubject<JsonNode[]>([]);
19+
get renderedData(): JsonNode[] { return this._renderedData.value; }
20+
21+
constructor(private database: JsonDatabase) {}
22+
23+
connect(collectionViewer: CollectionViewer): Observable<JsonNode[]> {
24+
return merge([collectionViewer.viewChange, this.database.dataChange])
25+
.pipe(map(() => {
26+
this._renderedData.next(this.database.data);
27+
return this.renderedData;
28+
}));
29+
}
30+
31+
disconnect() { }
32+
}
33+

src/demo-app/tree/tree-demo.html

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<div class="demo-tree-container">
2+
<mat-card>
3+
<mat-card-header>Flattened tree</mat-card-header>
4+
5+
<mat-tree [dataSource]="dataSource" [treeControl]="treeControl">
6+
<mat-tree-node *matTreeNodeDef="let node" matTreeNodeTrigger matTreeNodePadding>
7+
{{node.key}} : {{node.value}}
8+
</mat-tree-node>
9+
10+
<mat-tree-node *matTreeNodeDef="let node;when: hasChild" matTreeNodePadding>
11+
<mat-icon matTreeNodeTrigger>
12+
{{treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right'}}
13+
</mat-icon>
14+
{{node.key}} : {{node.value}}
15+
</mat-tree-node>
16+
</mat-tree>
17+
18+
</mat-card>
19+
20+
21+
22+
<mat-card>
23+
<mat-card-header>Nested tree</mat-card-header>
24+
25+
<mat-tree [dataSource]="nestedDataSource" [treeControl]="nestedTreeControl">
26+
<mat-tree-node *matTreeNodeDef="let node" role="treeitem" matTreeNodeTrigger>
27+
<li>
28+
<div>{{node.key}}: {{node.value}}</div>
29+
</li>
30+
</mat-tree-node>
31+
32+
<mat-nested-tree-node *matTreeNodeDef="let node; when: hasNestedChild" role="group">
33+
<li>
34+
<div class="mat-tree-node">
35+
<mat-icon matTreeNodeTrigger>
36+
{{treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right'}}
37+
</mat-icon>
38+
{{node.key}}
39+
</div>
40+
<ul [class]="nestedTreeControl.isExpanded(node) ? '' : 'tree-demo-invisible'">
41+
<ng-container matTreeNodeOutlet></ng-container>
42+
</ul>
43+
</li>
44+
</mat-nested-tree-node>
45+
</mat-tree>
46+
</mat-card>
47+
</div>

src/demo-app/tree/tree-demo.scss

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
.demo-tree-container {
2+
.tree-demo-invisible {
3+
display: none;
4+
}
5+
6+
ul, li {
7+
-webkit-margin-before: 0px;
8+
-webkit-margin-after: 0px;
9+
list-style-type: none;
10+
}
11+
12+
.mat-card {
13+
margin: 16px;
14+
}
15+
}

src/demo-app/tree/tree-demo.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {Component} from '@angular/core';
10+
import {FlatTreeControl, NestedTreeControl} from '@angular/cdk/tree';
11+
import {of as ofObservable} from 'rxjs/observable/of';
12+
13+
import {JsonNode, JsonDatabase} from './json-database';
14+
import {FlatDataSource, JsonFlatNode} from './flat-data-source';
15+
import {JsonNestedDataSource} from './nested-data-source';
16+
17+
18+
@Component({
19+
moduleId: module.id,
20+
selector: 'tree-demo',
21+
templateUrl: 'tree-demo.html',
22+
styleUrls: ['tree-demo.css'],
23+
})
24+
export class TreeDemo {
25+
// Flat tree control
26+
treeControl: FlatTreeControl<JsonFlatNode>;
27+
28+
// Nested tree control
29+
nestedTreeControl: NestedTreeControl<JsonNode>;
30+
31+
// Flat tree data source
32+
dataSource: FlatDataSource;
33+
34+
// Nested tree data source
35+
nestedDataSource: JsonNestedDataSource;
36+
37+
constructor(database: JsonDatabase) {
38+
// For flat tree
39+
this.treeControl = new FlatTreeControl<JsonFlatNode>(this.getLevel, this.isExpandable);
40+
this.dataSource = new FlatDataSource(database, this.treeControl);
41+
42+
// For nested tree
43+
this.nestedTreeControl = new NestedTreeControl<JsonNode>(this.getChildren);
44+
this.nestedDataSource = new JsonNestedDataSource(database);
45+
}
46+
47+
getLevel = (node: JsonFlatNode) => { return node.level };
48+
49+
isExpandable = (node: JsonFlatNode) => { return node.expandable; }
50+
51+
getChildren = (node: JsonNode) => { return ofObservable(node.children); }
52+
53+
hasChild = (_: number, _nodeData: JsonFlatNode) => { return _nodeData.expandable; }
54+
55+
hasNestedChild = (_: number, nodeData: JsonNode) => {return !(nodeData.value); }
56+
}

0 commit comments

Comments
 (0)