Skip to content

Commit

Permalink
feat(tree): add tree demo pages (#8749)
Browse files Browse the repository at this point in the history
  • Loading branch information
tinayuangao authored and jelbourn committed Feb 12, 2018
1 parent 0291a18 commit dc3256f
Show file tree
Hide file tree
Showing 10 changed files with 357 additions and 6 deletions.
1 change: 1 addition & 0 deletions src/demo-app/demo-app/demo-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export class DemoApp {
{name: 'Tabs', route: '/tabs'},
{name: 'Toolbar', route: '/toolbar'},
{name: 'Tooltip', route: '/tooltip'},
{name: 'Tree', route: '/tree'},
{name: 'Typography', route: '/typography'}
];

Expand Down
5 changes: 5 additions & 0 deletions src/demo-app/demo-app/demo-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ import {
} from '../tabs/tabs-demo';
import {ToolbarDemo} from '../toolbar/toolbar-demo';
import {TooltipDemo} from '../tooltip/tooltip-demo';
import {TreeDemo} from '../tree/tree-demo';
import {JsonDatabase} from '../tree/json-database';
import {TypographyDemo} from '../typography/typography-demo';
import {DemoApp, Home} from './demo-app';
import {DEMO_APP_ROUTES} from './routes';
Expand Down Expand Up @@ -124,11 +126,14 @@ import {TableDemoModule} from '../table/table-demo-module';
TabsDemo,
ToolbarDemo,
TooltipDemo,
TreeDemo,
TypographyDemo,
ExampleBottomSheet,
],
providers: [
{provide: OverlayContainer, useClass: FullscreenOverlayContainer},
PeopleDatabase,
JsonDatabase
],
entryComponents: [
ContentElementDialog,
Expand Down
2 changes: 2 additions & 0 deletions src/demo-app/demo-app/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import {TABS_DEMO_ROUTES} from '../tabs/routes';
import {TabsDemo} from '../tabs/tabs-demo';
import {ToolbarDemo} from '../toolbar/toolbar-demo';
import {TooltipDemo} from '../tooltip/tooltip-demo';
import {TreeDemo} from '../tree/tree-demo';
import {TypographyDemo} from '../typography/typography-demo';
import {DemoApp, Home} from './demo-app';
import {TableDemoPage} from '../table/table-demo-page';
Expand Down Expand Up @@ -92,6 +93,7 @@ export const DEMO_APP_ROUTES: Routes = [
{path: 'tabs', component: TabsDemo, children: TABS_DEMO_ROUTES},
{path: 'toolbar', component: ToolbarDemo},
{path: 'tooltip', component: TooltipDemo},
{path: 'tree', component: TreeDemo},
{path: 'typography', component: TypographyDemo},
{path: 'expansion', component: ExpansionDemo},
{path: 'stepper', component: StepperDemo},
Expand Down
102 changes: 102 additions & 0 deletions src/demo-app/tree/flat-data-source.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {CollectionViewer, DataSource} from '@angular/cdk/collections';
import {FlatTreeControl, TreeControl} from '@angular/cdk/tree';
import {Observable} from 'rxjs/Observable';
import {merge} from 'rxjs/observable/merge';
import {map} from 'rxjs/operators/map';
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
import {JsonNode, JsonDatabase} from './json-database';

/** Flat node with expandable and level information */
export class JsonFlatNode {
key: string;
value: any;
level: number;
expandable: boolean;
}

function _flattenNode(node: JsonNode, level: number,
resultNodes: JsonFlatNode[], parentMap: boolean[]) {
let flatNode: JsonFlatNode = new JsonFlatNode();
flatNode.key = node.key;
flatNode.value = node.value;
flatNode.level = level;
flatNode.expandable = !!node.children;
resultNodes.push(flatNode);

if (flatNode.expandable) {
node.children.forEach((child, index) => {
let childParentMap: boolean[] = parentMap.slice();
childParentMap.push(index != node.children.length - 1);
_flattenNode(child, level + 1, resultNodes, childParentMap);
});
}
return resultNodes;
}

/** Tree flattener to transfrom JsonNode to JsonFlatNode */
export function flattenNodes(structuredData: JsonNode[]): JsonFlatNode[] {
let resultNodes: JsonFlatNode[] = [];
structuredData.forEach(node => _flattenNode(node, 0, resultNodes, []));
return resultNodes;
}

export function expandFlattenedNodes(nodes: JsonFlatNode[],
treeControl: TreeControl<JsonFlatNode>): JsonFlatNode[] {
let results: JsonFlatNode[] = [];
let currentExpand: boolean[] = [];
currentExpand[0] = true;

nodes.forEach((node) => {
let expand = true;
for (let i = 0; i <= node.level; i++) {
expand = expand && currentExpand[i];
}
if (expand) {
results.push(node);
}
if (node.expandable) {
currentExpand[node.level + 1] = treeControl.isExpanded(node);
}
});
return results;
}

/** Flat data source */
export class FlatDataSource implements DataSource<any> {
_flattenedData = new BehaviorSubject<any>([]);
get flattenedData() { return this._flattenedData.value; }

_expandedData = new BehaviorSubject<any>([]);
get expandedData() { return this._expandedData.value; }

constructor(database: JsonDatabase, private treeControl: FlatTreeControl<JsonFlatNode>) {
database.dataChange.subscribe((tree) => {
this._flattenedData.next(flattenNodes(tree));
this.treeControl.dataNodes = this.flattenedData;
});
}

connect(collectionViewer: CollectionViewer): Observable<JsonFlatNode[]> {
return merge([
collectionViewer.viewChange,
this.treeControl.expansionModel.onChange,
this._flattenedData])
.pipe(map(() => {
this._expandedData.next(
expandFlattenedNodes(this.flattenedData, this.treeControl));
return this.expandedData;
}));
}

disconnect() {
}
}

90 changes: 90 additions & 0 deletions src/demo-app/tree/json-database.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {Injectable} from '@angular/core';
import {BehaviorSubject} from 'rxjs/BehaviorSubject';

export class JsonNode {
children: JsonNode[];
key: string;
value: any;
}

const TREE_DATA = `{"Tina":
{
"Documents": {
"angular": {
"src": {
"core": "ts",
"compiler": "ts"
}
},
"material2": {
"src": {
"button": "ts",
"checkbox": "ts",
"input": "ts"
}
}
},
"Downloads": {
"Tutorial": "html",
"November": "pdf",
"October": "pdf"
},
"Pictures": {
"Sun": "png",
"Woods": "jpg",
"Photo Booth Library": {
"Contents": "dir",
"Pictures": "dir"
}
},
"Applications": {
"Chrome": "app",
"Calendar": "app",
"Webstorm": "app"
}
}}
`;

@Injectable()
export class JsonDatabase {
dataChange: BehaviorSubject<JsonNode[]> = new BehaviorSubject<JsonNode[]>([]);

get data(): JsonNode[] { return this.dataChange.value; }

constructor() {
this.initialize();
}

initialize() {
const dataObject = JSON.parse(TREE_DATA);
const data = this.buildJsonTree(dataObject, 0);
this.dataChange.next(data);
}

buildJsonTree(value: any, level: number) {
let data: any[] = [];
for (let k in value) {
let v = value[k];
let node = new JsonNode();
node.key = `${k}`;
if (v === null || v === undefined) {
// no action
} else if (typeof v === 'object') {
node.children = this.buildJsonTree(v, level + 1);
} else {
node.value = v;
}
data.push(node);
}
return data;
}

}
33 changes: 33 additions & 0 deletions src/demo-app/tree/nested-data-source.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {CollectionViewer, DataSource} from '@angular/cdk/collections';
import {Observable} from 'rxjs/Observable';
import {merge} from 'rxjs/observable/merge';
import {map} from 'rxjs/operators/map';
import {BehaviorSubject} from 'rxjs/BehaviorSubject';

import {JsonNode, JsonDatabase} from './json-database';

export class JsonNestedDataSource implements DataSource<any> {
_renderedData = new BehaviorSubject<JsonNode[]>([]);
get renderedData(): JsonNode[] { return this._renderedData.value; }

constructor(private database: JsonDatabase) {}

connect(collectionViewer: CollectionViewer): Observable<JsonNode[]> {
return merge([collectionViewer.viewChange, this.database.dataChange])
.pipe(map(() => {
this._renderedData.next(this.database.data);
return this.renderedData;
}));
}

disconnect() { }
}

47 changes: 47 additions & 0 deletions src/demo-app/tree/tree-demo.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<div class="demo-tree-container">
<mat-card>
<mat-card-header>Flattened tree</mat-card-header>

<mat-tree [dataSource]="dataSource" [treeControl]="treeControl">
<mat-tree-node *matTreeNodeDef="let node" matTreeNodeTrigger matTreeNodePadding>
{{node.key}} : {{node.value}}
</mat-tree-node>

<mat-tree-node *matTreeNodeDef="let node;when: hasChild" matTreeNodePadding>
<mat-icon matTreeNodeTrigger>
{{treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right'}}
</mat-icon>
{{node.key}} : {{node.value}}
</mat-tree-node>
</mat-tree>

</mat-card>



<mat-card>
<mat-card-header>Nested tree</mat-card-header>

<mat-tree [dataSource]="nestedDataSource" [treeControl]="nestedTreeControl">
<mat-tree-node *matTreeNodeDef="let node" role="treeitem" matTreeNodeTrigger>
<li>
<div>{{node.key}}: {{node.value}}</div>
</li>
</mat-tree-node>

<mat-nested-tree-node *matTreeNodeDef="let node; when: hasNestedChild" role="group">
<li>
<div class="mat-tree-node">
<mat-icon matTreeNodeTrigger>
{{treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right'}}
</mat-icon>
{{node.key}}
</div>
<ul [class]="nestedTreeControl.isExpanded(node) ? '' : 'tree-demo-invisible'">
<ng-container matTreeNodeOutlet></ng-container>
</ul>
</li>
</mat-nested-tree-node>
</mat-tree>
</mat-card>
</div>
15 changes: 15 additions & 0 deletions src/demo-app/tree/tree-demo.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.demo-tree-container {
.tree-demo-invisible {
display: none;
}

ul, li {
-webkit-margin-before: 0px;
-webkit-margin-after: 0px;
list-style-type: none;
}

.mat-card {
margin: 16px;
}
}
56 changes: 56 additions & 0 deletions src/demo-app/tree/tree-demo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {Component} from '@angular/core';
import {FlatTreeControl, NestedTreeControl} from '@angular/cdk/tree';
import {of as ofObservable} from 'rxjs/observable/of';

import {JsonNode, JsonDatabase} from './json-database';
import {FlatDataSource, JsonFlatNode} from './flat-data-source';
import {JsonNestedDataSource} from './nested-data-source';


@Component({
moduleId: module.id,
selector: 'tree-demo',
templateUrl: 'tree-demo.html',
styleUrls: ['tree-demo.css'],
})
export class TreeDemo {
// Flat tree control
treeControl: FlatTreeControl<JsonFlatNode>;

// Nested tree control
nestedTreeControl: NestedTreeControl<JsonNode>;

// Flat tree data source
dataSource: FlatDataSource;

// Nested tree data source
nestedDataSource: JsonNestedDataSource;

constructor(database: JsonDatabase) {
// For flat tree
this.treeControl = new FlatTreeControl<JsonFlatNode>(this.getLevel, this.isExpandable);
this.dataSource = new FlatDataSource(database, this.treeControl);

// For nested tree
this.nestedTreeControl = new NestedTreeControl<JsonNode>(this.getChildren);
this.nestedDataSource = new JsonNestedDataSource(database);
}

getLevel = (node: JsonFlatNode) => { return node.level };

isExpandable = (node: JsonFlatNode) => { return node.expandable; }

getChildren = (node: JsonNode) => { return ofObservable(node.children); }

hasChild = (_: number, _nodeData: JsonFlatNode) => { return _nodeData.expandable; }

hasNestedChild = (_: number, nodeData: JsonNode) => {return !(nodeData.value); }
}
Loading

0 comments on commit dc3256f

Please sign in to comment.