Skip to content

Commit 7b0ae83

Browse files
committed
feat(tree): add tree demo pages
1 parent 8bf44e4 commit 7b0ae83

File tree

10 files changed

+353
-7
lines changed

10 files changed

+353
-7
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 & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ import {TableHeaderDemo} from '../table/table-header-demo';
5757
import {FoggyTabContent, RainyTabContent, SunnyTabContent, TabsDemo} from '../tabs/tabs-demo';
5858
import {ToolbarDemo} from '../toolbar/toolbar-demo';
5959
import {TooltipDemo} from '../tooltip/tooltip-demo';
60+
import {TreeDemo} from '../tree/tree-demo';
61+
import {JsonDatabase} from '../tree/json-database';
6062
import {TypographyDemo} from '../typography/typography-demo';
6163
import {DemoApp, Home} from './demo-app';
6264
import {DEMO_APP_ROUTES} from './routes';
@@ -122,11 +124,13 @@ import {DEMO_APP_ROUTES} from './routes';
122124
TabsDemo,
123125
ToolbarDemo,
124126
TooltipDemo,
127+
TreeDemo,
125128
TypographyDemo,
126129
],
127130
providers: [
128131
{provide: OverlayContainer, useClass: FullscreenOverlayContainer},
129-
PeopleDatabase
132+
PeopleDatabase,
133+
JsonDatabase
130134
],
131135
entryComponents: [
132136
ContentElementDialog,

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import {TABS_DEMO_ROUTES} from '../tabs/routes';
4747
import {TabsDemo} from '../tabs/tabs-demo';
4848
import {ToolbarDemo} from '../toolbar/toolbar-demo';
4949
import {TooltipDemo} from '../tooltip/tooltip-demo';
50+
import {TreeDemo} from '../tree/tree-demo';
5051
import {TypographyDemo} from '../typography/typography-demo';
5152
import {DemoApp, Home} from './demo-app';
5253

@@ -89,6 +90,7 @@ export const DEMO_APP_ROUTES: Routes = [
8990
{path: 'tabs', component: TabsDemo, children: TABS_DEMO_ROUTES},
9091
{path: 'toolbar', component: ToolbarDemo},
9192
{path: 'tooltip', component: TooltipDemo},
93+
{path: 'tree', component: TreeDemo},
9294
{path: 'typography', component: TypographyDemo},
9395
{path: 'expansion', component: ExpansionDemo},
9496
{path: 'stepper', component: StepperDemo},

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

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
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+
16+
import {JsonNode, JsonDatabase} from './json-database';
17+
18+
/** Flat node with expandable and level information */
19+
export class JsonFlatNode {
20+
key: string;
21+
value: any;
22+
level: number;
23+
expandable: boolean;
24+
}
25+
26+
/** Tree flattener to transfrom JsonNode to JsonFlatNode */
27+
export class TreeFlattener {
28+
29+
static flattenNodes( structuredData: JsonNode[]): JsonFlatNode[] {
30+
let resultNodes: JsonFlatNode[] = [];
31+
structuredData.forEach((node) => {
32+
this._flattenNode(node, 0, resultNodes, []);
33+
});
34+
return resultNodes;
35+
}
36+
37+
static _flattenNode(node: JsonNode, level: number,
38+
resultNodes: JsonFlatNode[], parentMap: boolean[]) {
39+
let flatNode: JsonFlatNode = new JsonFlatNode();
40+
flatNode.key = node.key;
41+
flatNode.value = node.value;
42+
flatNode.level = level;
43+
flatNode.expandable = !!node.children;
44+
resultNodes.push(flatNode);
45+
46+
if (flatNode.expandable) {
47+
node.children.forEach((child, index) => {
48+
let childParentMap: boolean[] = parentMap.slice();
49+
childParentMap.push(index != node.children.length - 1);
50+
this._flattenNode(child, level + 1, resultNodes, childParentMap);
51+
});
52+
}
53+
return flatNode;
54+
}
55+
56+
static expandFlattenedNodes(nodes: JsonFlatNode[],
57+
treeControl: TreeControl<JsonFlatNode>): JsonFlatNode[] {
58+
let results: JsonFlatNode[] = [];
59+
let currentExpand: boolean[] = [];
60+
currentExpand[0] = true;
61+
62+
nodes.forEach((node) => {
63+
let expand = true;
64+
for (let i = 0; i <= node.level; i++) {
65+
expand = expand && currentExpand[i];
66+
}
67+
if (expand) {
68+
results.push(node);
69+
}
70+
if (node.expandable) {
71+
currentExpand[node.level + 1] = treeControl.isExpanded(node);
72+
}
73+
});
74+
return results;
75+
}
76+
}
77+
78+
/** Flat data source */
79+
export class FlatDataSource implements DataSource<any> {
80+
_flattenedData = new BehaviorSubject<any>([]);
81+
get flattenedData() { return this._flattenedData.value; }
82+
83+
_expandedData = new BehaviorSubject<any>([]);
84+
get expandedData() { return this._expandedData.value; }
85+
86+
constructor(database: JsonDatabase, private treeControl: FlatTreeControl<JsonFlatNode>) {
87+
database.dataChange.subscribe((tree) => {
88+
this._flattenedData.next(TreeFlattener.flattenNodes(tree));
89+
this.treeControl.dataNodes = this.flattenedData;
90+
});
91+
}
92+
93+
connect(collectionViewer: CollectionViewer): Observable<JsonFlatNode[]> {
94+
return merge([
95+
collectionViewer.viewChange,
96+
this.treeControl.expansionModel.onChange,
97+
this._flattenedData])
98+
.pipe(map(() => {
99+
this._expandedData.next(
100+
TreeFlattener.expandFlattenedNodes(this.flattenedData, this.treeControl));
101+
return this.expandedData;
102+
}));
103+
}
104+
105+
disconnect() {
106+
}
107+
}
108+

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

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
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 = `{"widget": {
19+
"debug": "on",
20+
"window": {
21+
"title": "Sample Konfabulator Widget",
22+
"name": "main_window",
23+
"width": 500,
24+
"height": 500
25+
},
26+
"image": {
27+
"src": "Images/Sun.png",
28+
"name": "sun1",
29+
"hOffset": 250,
30+
"vOffset": 250,
31+
"alignment": "center"
32+
},
33+
"text": {
34+
"data": "Click Here",
35+
"size": 36,
36+
"style": "bold",
37+
"name": "text1",
38+
"hOffset": 250,
39+
"vOffset": 100,
40+
"alignment": "center",
41+
"onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;"
42+
}
43+
}}
44+
`;
45+
46+
@Injectable()
47+
export class JsonDatabase {
48+
dataChange: BehaviorSubject<JsonNode[]> = new BehaviorSubject<JsonNode[]>([]);
49+
50+
get data(): JsonNode[] { return this.dataChange.value; }
51+
52+
constructor() {
53+
this.initialize();
54+
}
55+
56+
initialize() {
57+
const dataObject = JSON.parse(TREE_DATA);
58+
const data = this.buildJsonTree(dataObject, 0);
59+
this.dataChange.next(data);
60+
}
61+
62+
buildJsonTree(value: any, level: number) {
63+
let data: any[] = [];
64+
for (let k in value) {
65+
let v = value[k];
66+
let node = new JsonNode();
67+
node.key = `${k}`;
68+
if (v === null || v === undefined) {
69+
// no action
70+
} else if (typeof v === 'object') {
71+
node.children = this.buildJsonTree(v, level + 1);
72+
} else {
73+
node.value = v;
74+
}
75+
data.push(node);
76+
}
77+
return data;
78+
}
79+
80+
}
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)