diff --git a/src/demo-app/demo-app/demo-module.ts b/src/demo-app/demo-app/demo-module.ts index c17384b4506d..043ee2004770 100644 --- a/src/demo-app/demo-app/demo-module.ts +++ b/src/demo-app/demo-app/demo-module.ts @@ -58,7 +58,6 @@ import { 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'; @@ -134,7 +133,6 @@ import {BadgeDemo} from '../badge/badge-demo'; ], providers: [ {provide: OverlayContainer, useClass: FullscreenOverlayContainer}, - JsonDatabase ], entryComponents: [ ContentElementDialog, diff --git a/src/demo-app/tree/file-database.ts b/src/demo-app/tree/file-database.ts new file mode 100644 index 000000000000..2c39a2164f8d --- /dev/null +++ b/src/demo-app/tree/file-database.ts @@ -0,0 +1,119 @@ +/** + * @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'; + +/** + * File node data with nested structure. + * Each node has a filename, and a type or a list of children. + */ +export class FileNode { + children: FileNode[]; + filename: string; + type: any; +} + +/** Flat node with expandable and level information */ +export class FileFlatNode { + filename: string; + type: any; + level: number; + expandable: boolean; +} + +/** + * The file structure tree data in string. The data could be parsed into a Json object + */ +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" + } +}}`; + +/** + * File database, it can build a tree structured Json object from string. + * Each node in Json object represents a file or a directory. For a file, it has filename and type. + * For a directory, it has filename and children (a list of files or directories). + * The input will be a json object string, and the output is a list of `FileNode` with nested + * structure. + */ +@Injectable() +export class FileDatabase { + dataChange: BehaviorSubject = new BehaviorSubject([]); + + get data(): FileNode[] { return this.dataChange.value; } + + constructor() { + this.initialize(); + } + + initialize() { + // Parse the string to json object. + const dataObject = JSON.parse(TREE_DATA); + + // Build the tree nodes from Json object. The result is a list of `FileNode` with nested + // file node as children. + const data = this.buildFileTree(dataObject, 0); + + // Notify the change. + this.dataChange.next(data); + } + + /** + * Build the file structure tree. The `value` is the Json object, or a sub-tree of a Json object. + * The return value is the list of `FileNode`. + */ + buildFileTree(value: any, level: number) { + let data: any[] = []; + for (let k in value) { + let v = value[k]; + let node = new FileNode(); + node.filename = `${k}`; + if (v === null || v === undefined) { + // no action + } else if (typeof v === 'object') { + node.children = this.buildFileTree(v, level + 1); + } else { + node.type = v; + } + data.push(node); + } + return data; + } +} diff --git a/src/demo-app/tree/flat-data-source.ts b/src/demo-app/tree/flat-data-source.ts deleted file mode 100644 index a0545c6f63be..000000000000 --- a/src/demo-app/tree/flat-data-source.ts +++ /dev/null @@ -1,102 +0,0 @@ -/** - * @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[] { - 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 { - _flattenedData = new BehaviorSubject([]); - get flattenedData() { return this._flattenedData.value; } - - _expandedData = new BehaviorSubject([]); - get expandedData() { return this._expandedData.value; } - - constructor(database: JsonDatabase, private treeControl: FlatTreeControl) { - database.dataChange.subscribe((tree) => { - this._flattenedData.next(flattenNodes(tree)); - this.treeControl.dataNodes = this.flattenedData; - }); - } - - connect(collectionViewer: CollectionViewer): Observable { - return merge([ - collectionViewer.viewChange, - this.treeControl.expansionModel.onChange, - this._flattenedData]) - .pipe(map(() => { - this._expandedData.next( - expandFlattenedNodes(this.flattenedData, this.treeControl)); - return this.expandedData; - })); - } - - disconnect() { - } -} - diff --git a/src/demo-app/tree/json-database.ts b/src/demo-app/tree/json-database.ts deleted file mode 100644 index f7da18b3e282..000000000000 --- a/src/demo-app/tree/json-database.ts +++ /dev/null @@ -1,88 +0,0 @@ -/** - * @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 = new BehaviorSubject([]); - - 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; - } -} diff --git a/src/demo-app/tree/nested-data-source.ts b/src/demo-app/tree/nested-data-source.ts deleted file mode 100644 index b593d19429bc..000000000000 --- a/src/demo-app/tree/nested-data-source.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * @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 { - _renderedData = new BehaviorSubject([]); - get renderedData(): JsonNode[] { return this._renderedData.value; } - - constructor(private database: JsonDatabase) {} - - connect(collectionViewer: CollectionViewer): Observable { - return merge([collectionViewer.viewChange, this.database.dataChange]) - .pipe(map(() => { - this._renderedData.next(this.database.data); - return this.renderedData; - })); - } - - disconnect() { } -} - diff --git a/src/demo-app/tree/tree-demo.html b/src/demo-app/tree/tree-demo.html index 1dc48f60ad12..8aef0ce6d395 100644 --- a/src/demo-app/tree/tree-demo.html +++ b/src/demo-app/tree/tree-demo.html @@ -4,17 +4,17 @@ - {{node.key}} : {{node.value}} + {{node.filename}} : {{node.type}} - {{node.key}} : {{node.value}} + {{node.filename}} : {{node.type}} @@ -28,7 +28,7 @@
  • -
    {{node.key}}: {{node.value}}
    +
    {{node.filename}}: {{node.type}}
  • @@ -36,14 +36,14 @@
  • - {{node.key}} + {{node.filename}}
    -
      +
      diff --git a/src/demo-app/tree/tree-demo.ts b/src/demo-app/tree/tree-demo.ts index aeea51efb82e..720e92862c12 100644 --- a/src/demo-app/tree/tree-demo.ts +++ b/src/demo-app/tree/tree-demo.ts @@ -14,41 +14,41 @@ import { MatTreeNestedDataSource } from '@angular/material/tree'; import {of as ofObservable} from 'rxjs/observable/of'; +import {Observable} from 'rxjs/Observable'; -import {JsonNode, JsonDatabase} from './json-database'; -import {JsonFlatNode} from './flat-data-source'; - +import {FileNode, FileFlatNode, FileDatabase} from './file-database'; @Component({ moduleId: module.id, selector: 'tree-demo', templateUrl: 'tree-demo.html', styleUrls: ['tree-demo.css'], + providers: [FileDatabase] }) export class TreeDemo { // Flat tree control - treeControl: FlatTreeControl; + treeControl: FlatTreeControl; // Nested tree control - nestedTreeControl: NestedTreeControl; + nestedTreeControl: NestedTreeControl; - treeFlattener: MatTreeFlattener; + treeFlattener: MatTreeFlattener; // Flat tree data source - dataSource: MatTreeFlatDataSource; + dataSource: MatTreeFlatDataSource; // Nested tree data source - nestedDataSource: MatTreeNestedDataSource; + nestedDataSource: MatTreeNestedDataSource; - constructor(database: JsonDatabase) { + constructor(database: FileDatabase) { this.treeFlattener = new MatTreeFlattener(this.transformer, this.getLevel, this.isExpandable, this.getChildren); // For flat tree - this.treeControl = new FlatTreeControl(this.getLevel, this.isExpandable); + this.treeControl = new FlatTreeControl(this.getLevel, this.isExpandable); this.dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener); // For nested tree - this.nestedTreeControl = new NestedTreeControl(this.getChildren); + this.nestedTreeControl = new NestedTreeControl(this.getChildren); this.nestedDataSource = new MatTreeNestedDataSource(); database.dataChange.subscribe(data => { @@ -57,22 +57,22 @@ export class TreeDemo { }); } - transformer = (node: JsonNode, level: number) => { - let flatNode = new JsonFlatNode(); - flatNode.key = node.key; - flatNode.value = node.value; + transformer = (node: FileNode, level: number) => { + let flatNode = new FileFlatNode(); + flatNode.filename = node.filename; + flatNode.type = node.type; flatNode.level = level; flatNode.expandable = !!node.children; return flatNode; } - getLevel = (node: JsonFlatNode) => { return node.level; }; + getLevel = (node: FileFlatNode) => { return node.level; }; - isExpandable = (node: JsonFlatNode) => { return node.expandable; }; + isExpandable = (node: FileFlatNode) => { return node.expandable; }; - getChildren = (node: JsonNode) => { return ofObservable(node.children); }; + getChildren = (node: FileNode): Observable => { return ofObservable(node.children); }; - hasChild = (_: number, _nodeData: JsonFlatNode) => { return _nodeData.expandable; }; + hasChild = (_: number, _nodeData: FileFlatNode) => { return _nodeData.expandable; }; - hasNestedChild = (_: number, nodeData: JsonNode) => {return !(nodeData.value); }; + hasNestedChild = (_: number, nodeData: FileNode) => {return !(nodeData.type); }; } diff --git a/src/lib/tree/tree.md b/src/lib/tree/tree.md index dcb918109378..e5e6b3e28a66 100644 --- a/src/lib/tree/tree.md +++ b/src/lib/tree/tree.md @@ -24,6 +24,8 @@ used to style the node such that it is indented to the appropriate level. ``` + + Flat trees are generally easier to style and inspect. They are also more friendly to scrolling variations, such as infinite or virtual scrolling @@ -43,6 +45,8 @@ outlet to keep all the children nodes. ``` + + Nested trees are easier to work with when hierarchical relationships are visually represented in ways that would be difficult to accomplish with flat nodes. diff --git a/src/material-examples/material-module.ts b/src/material-examples/material-module.ts index abdd7c97891c..099754511db8 100644 --- a/src/material-examples/material-module.ts +++ b/src/material-examples/material-module.ts @@ -1,6 +1,7 @@ import {NgModule} from '@angular/core'; import {CdkTableModule} from '@angular/cdk/table'; +import {CdkTreeModule} from '@angular/cdk/tree'; import { MatAutocompleteModule, MatButtonModule, MatButtonToggleModule, MatPaginatorModule, MatCardModule, MatCheckboxModule, MatChipsModule, MatDatepickerModule, @@ -8,12 +9,13 @@ import { MatListModule, MatMenuModule, MatProgressBarModule, MatProgressSpinnerModule, MatRadioModule, MatSelectModule, MatSidenavModule, MatSliderModule, MatSortModule, MatSlideToggleModule, MatSnackBarModule, MatTableModule, MatTabsModule, MatToolbarModule, - MatTooltipModule, MatFormFieldModule, MatExpansionModule, MatStepperModule + MatTooltipModule, MatFormFieldModule, MatExpansionModule, MatStepperModule, MatTreeModule } from '@angular/material'; @NgModule({ exports: [ CdkTableModule, + CdkTreeModule, MatAutocompleteModule, MatButtonModule, MatButtonToggleModule, @@ -45,6 +47,7 @@ import { MatTabsModule, MatToolbarModule, MatTooltipModule, + MatTreeModule ] }) export class ExampleMaterialModule {} diff --git a/src/material-examples/tree-flat-overview/tree-flat-overview-example.css b/src/material-examples/tree-flat-overview/tree-flat-overview-example.css new file mode 100644 index 000000000000..138500bdf17a --- /dev/null +++ b/src/material-examples/tree-flat-overview/tree-flat-overview-example.css @@ -0,0 +1,6 @@ +.example-tree ul, +.example-tree li { + -webkit-margin-before: 0; + -webkit-margin-after: 0; + list-style-type: none; +} diff --git a/src/material-examples/tree-flat-overview/tree-flat-overview-example.html b/src/material-examples/tree-flat-overview/tree-flat-overview-example.html new file mode 100644 index 000000000000..0a24659edaaa --- /dev/null +++ b/src/material-examples/tree-flat-overview/tree-flat-overview-example.html @@ -0,0 +1,15 @@ + + + {{node.filename}} : {{node.type}} + + + + + {{node.filename}} : {{node.type}} + + diff --git a/src/material-examples/tree-flat-overview/tree-flat-overview-example.ts b/src/material-examples/tree-flat-overview/tree-flat-overview-example.ts new file mode 100644 index 000000000000..86f86a8b2742 --- /dev/null +++ b/src/material-examples/tree-flat-overview/tree-flat-overview-example.ts @@ -0,0 +1,162 @@ +import {Component, Injectable} from '@angular/core'; +import {FlatTreeControl} from '@angular/cdk/tree'; +import {MatTreeFlattener, MatTreeFlatDataSource} from '@angular/material/tree'; +import {of} from 'rxjs/observable/of'; +import {Observable} from 'rxjs/Observable'; +import {BehaviorSubject} from 'rxjs/BehaviorSubject'; + +/** + * File node data with nested structure. + * Each node has a filename, and a type or a list of children. + */ +export class FileNode { + children: FileNode[]; + filename: string; + type: any; +} + +/** Flat node with expandable and level information */ +export class FileFlatNode { + filename: string; + type: any; + level: number; + expandable: boolean; +} + +/** + * The file structure tree data in string. The data could be parsed into a Json object + */ +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" + } +}}`; + +/** + * File database, it can build a tree structured Json object from string. + * Each node in Json object represents a file or a directory. For a file, it has filename and type. + * For a directory, it has filename and children (a list of files or directories). + * The input will be a json object string, and the output is a list of `FileNode` with nested + * structure. + */ +@Injectable() +export class FileDatabase { + dataChange: BehaviorSubject = new BehaviorSubject([]); + + get data(): FileNode[] { return this.dataChange.value; } + + constructor() { + this.initialize(); + } + + initialize() { + // Parse the string to json object. + const dataObject = JSON.parse(TREE_DATA); + + // Build the tree nodes from Json object. The result is a list of `FileNode` with nested + // file node as children. + const data = this.buildFileTree(dataObject, 0); + + // Notify the change. + this.dataChange.next(data); + } + + /** + * Build the file structure tree. The `value` is the Json object, or a sub-tree of a Json object. + * The return value is the list of `FileNode`. + */ + buildFileTree(value: any, level: number) { + let data: any[] = []; + for (let k in value) { + let v = value[k]; + let node = new FileNode(); + node.filename = `${k}`; + if (v === null || v === undefined) { + // no action + } else if (typeof v === 'object') { + node.children = this.buildFileTree(v, level + 1); + } else { + node.type = v; + } + data.push(node); + } + return data; + } +} + +/** + * @title Tree with flat nodes + */ +@Component({ + selector: 'tree-flat-overview-example', + templateUrl: 'tree-flat-overview-example.html', + styleUrls: ['tree-flat-overview-example.css'], + providers: [FileDatabase] +}) +export class TreeFlatOverviewExample { + + treeControl: FlatTreeControl; + + treeFlattener: MatTreeFlattener; + + dataSource: MatTreeFlatDataSource; + + constructor(database: FileDatabase) { + this.treeFlattener = new MatTreeFlattener(this.transformer, this._getLevel, + this._isExpandable, this._getChildren); + this.treeControl = new FlatTreeControl(this._getLevel, this._isExpandable); + this.dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener); + + database.dataChange.subscribe(data => { + this.dataSource.data = data; + }); + } + + transformer = (node: FileNode, level: number) => { + let flatNode = new FileFlatNode(); + flatNode.filename = node.filename; + flatNode.type = node.type; + flatNode.level = level; + flatNode.expandable = !!node.children; + return flatNode; + } + + private _getLevel = (node: FileFlatNode) => { return node.level; }; + + private _isExpandable = (node: FileFlatNode) => { return node.expandable; }; + + private _getChildren = (node: FileNode): Observable => { return of(node.children); }; + + hasChild = (_: number, _nodeData: FileFlatNode) => { return _nodeData.expandable; }; +} diff --git a/src/material-examples/tree-nested-overview/tree-nested-overview-example.css b/src/material-examples/tree-nested-overview/tree-nested-overview-example.css new file mode 100644 index 000000000000..1bcbfb831ce1 --- /dev/null +++ b/src/material-examples/tree-nested-overview/tree-nested-overview-example.css @@ -0,0 +1,10 @@ +.example-tree-invisible { + display: none; +} + +.example-tree ul, +.example-tree li { + -webkit-margin-before: 0; + -webkit-margin-after: 0; + list-style-type: none; +} diff --git a/src/material-examples/tree-nested-overview/tree-nested-overview-example.html b/src/material-examples/tree-nested-overview/tree-nested-overview-example.html new file mode 100644 index 000000000000..efe21202abaf --- /dev/null +++ b/src/material-examples/tree-nested-overview/tree-nested-overview-example.html @@ -0,0 +1,24 @@ + + +
    • +
      {{node.filename}}: {{node.type}}
      +
    • +
      + + +
    • +
      + + {{node.filename}} +
      +
        + +
      +
    • +
      +
      diff --git a/src/material-examples/tree-nested-overview/tree-nested-overview-example.ts b/src/material-examples/tree-nested-overview/tree-nested-overview-example.ts new file mode 100644 index 000000000000..6a80f1d4c720 --- /dev/null +++ b/src/material-examples/tree-nested-overview/tree-nested-overview-example.ts @@ -0,0 +1,132 @@ +import {Component, Injectable} from '@angular/core'; +import {NestedTreeControl} from '@angular/cdk/tree'; +import {MatTreeNestedDataSource} from '@angular/material/tree'; +import {of} from 'rxjs/observable/of'; +import {BehaviorSubject} from 'rxjs/BehaviorSubject'; + +/** + * Json node data with nested structure. Each node has a filename and a value or a list of children + */ +export class FileNode { + children: FileNode[]; + filename: string; + type: any; +} + +/** + * The Json tree data in string. The data could be parsed into Json object + */ +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" + } +}}`; + +/** + * File database, it can build a tree structured Json object from string. + * Each node in Json object represents a file or a directory. For a file, it has filename and type. + * For a directory, it has filename and children (a list of files or directories). + * The input will be a json object string, and the output is a list of `FileNode` with nested + * structure. + */ +@Injectable() +export class FileDatabase { + dataChange: BehaviorSubject = new BehaviorSubject([]); + + get data(): FileNode[] { return this.dataChange.value; } + + constructor() { + this.initialize(); + } + + initialize() { + // Parse the string to json object. + const dataObject = JSON.parse(TREE_DATA); + + // Build the tree nodes from Json object. The result is a list of `FileNode` with nested + // file node as children. + const data = this.buildFileTree(dataObject, 0); + + // Notify the change. + this.dataChange.next(data); + } + + /** + * Build the file structure tree. The `value` is the Json object, or a sub-tree of a Json object. + * The return value is the list of `FileNode`. + */ + buildFileTree(value: any, level: number) { + let data: any[] = []; + for (let k in value) { + let v = value[k]; + let node = new FileNode(); + node.filename = `${k}`; + if (v === null || v === undefined) { + // no action + } else if (typeof v === 'object') { + node.children = this.buildFileTree(v, level + 1); + } else { + node.type = v; + } + data.push(node); + } + return data; + } +} + +/** + * @title Tree with nested nodes + */ +@Component({ + selector: 'tree-nested-overview-example', + templateUrl: 'tree-nested-overview-example.html', + styleUrls: ['tree-nested-overview-example.css'], + providers: [FileDatabase] +}) +export class TreeNestedOverviewExample { + nestedTreeControl: NestedTreeControl; + + nestedDataSource: MatTreeNestedDataSource; + + constructor(database: FileDatabase) { + this.nestedTreeControl = new NestedTreeControl(this._getChildren); + this.nestedDataSource = new MatTreeNestedDataSource(); + + database.dataChange.subscribe(data => this.nestedDataSource.data = data); + } + + private _getChildren = (node: FileNode) => { return of(node.children); }; + + hasNestedChild = (_: number, nodeData: FileNode) => {return !(nodeData.type); }; +}