Skip to content

Commit e70eb48

Browse files
tinayuangaojosephperrott
authored andcommitted
docs(tree): add more tree examples (#10386)
1 parent ddf22ed commit e70eb48

22 files changed

+863
-179
lines changed

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

Lines changed: 41 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -12,37 +12,35 @@ import {BehaviorSubject} from 'rxjs';
1212
* Node for to-do item
1313
*/
1414
export class TodoItemNode {
15-
children: TodoItemNode[];
16-
item: string;
17-
}
18-
19-
/** Flat to-do item node with expandable and level information */
20-
export class TodoItemFlatNode {
21-
item: string;
22-
level: number;
23-
expandable: boolean;
15+
children: BehaviorSubject<TodoItemNode[]>;
16+
constructor(public item: string, children?: TodoItemNode[], public parent?: TodoItemNode) {
17+
this.children = new BehaviorSubject(children === undefined ? [] : children);
18+
}
2419
}
2520

2621
/**
2722
* The Json object for to-do list data.
2823
*/
29-
const TREE_DATA = {
30-
'Reminders': [
31-
'Cook dinner',
32-
'Read the Material Design spec',
33-
'Upgrade Application to Angular'
34-
],
35-
'Groceries': {
36-
'Organic eggs': null,
37-
'Protein Powder': null,
38-
'Almond Meal flour': null,
39-
'Fruits': {
40-
'Apple': null,
41-
'Orange': null,
42-
'Berries': ['Blueberry', 'Raspberry']
43-
}
44-
}
45-
};
24+
const TREE_DATA = [
25+
new TodoItemNode('Reminders', [
26+
new TodoItemNode('Cook dinner'),
27+
new TodoItemNode('Read the Material Design spec'),
28+
new TodoItemNode('Upgrade Application to Angular')
29+
]),
30+
new TodoItemNode('Groceries', [
31+
new TodoItemNode('Organic eggs'),
32+
new TodoItemNode('Protein Powder'),
33+
new TodoItemNode('Almond Meal flour'),
34+
new TodoItemNode('Fruits', [
35+
new TodoItemNode('Apple'),
36+
new TodoItemNode('Orange'),
37+
new TodoItemNode('Berries', [
38+
new TodoItemNode('Blueberry'),
39+
new TodoItemNode('Raspberry')
40+
])
41+
])
42+
])
43+
];
4644

4745
/**
4846
* Checklist database, it can build a tree structured Json object.
@@ -51,50 +49,28 @@ const TREE_DATA = {
5149
*/
5250
@Injectable()
5351
export class ChecklistDatabase {
54-
dataChange: BehaviorSubject<TodoItemNode[]> = new BehaviorSubject<TodoItemNode[]>([]);
55-
56-
get data(): TodoItemNode[] { return this.dataChange.value; }
57-
58-
constructor() {
59-
this.initialize();
60-
}
61-
62-
initialize() {
63-
// Build the tree nodes from Json object. The result is a list of `TodoItemNode` with nested
64-
// file node as children.
65-
const data = this.buildFileTree(TREE_DATA, 0);
52+
dataChange: BehaviorSubject<TodoItemNode[]> = new BehaviorSubject<TodoItemNode[]>(TREE_DATA);
6653

67-
// Notify the change.
68-
this.dataChange.next(data);
69-
}
70-
71-
/**
72-
* Build the file structure tree. The `value` is the Json object, or a sub-tree of a Json object.
73-
* The return value is the list of `TodoItemNode`.
74-
*/
75-
buildFileTree(value: any, level: number) {
76-
let data: any[] = [];
77-
for (let k in value) {
78-
let v = value[k];
79-
let node = new TodoItemNode();
80-
node.item = `${k}`;
81-
if (v === null || v === undefined) {
82-
// no action
83-
} else if (typeof v === 'object') {
84-
node.children = this.buildFileTree(v, level + 1);
85-
} else {
86-
node.item = v;
87-
}
88-
data.push(node);
89-
}
90-
return data;
54+
get data(): TodoItemNode[] {
55+
return this.dataChange.value;
9156
}
9257

9358
/** Add an item to to-do list */
9459
insertItem(parent: TodoItemNode, name: string) {
95-
const child = <TodoItemNode>{item: name};
96-
if (parent.children) {
97-
parent.children.push(child);
60+
const child = new TodoItemNode(name, [], parent);
61+
let children = parent.children.value;
62+
children.push(child);
63+
parent.children.next(children);
64+
this.dataChange.next(this.data);
65+
}
66+
67+
updateItem(node: TodoItemNode, name: string) {
68+
let newNode = new TodoItemNode(name, node.children.value, node.parent);
69+
if (node.parent) {
70+
let children = node.parent.children.value;
71+
let index = children.indexOf(node);
72+
children.splice(index, 1, newNode);
73+
node.parent.children.next(children);
9874
this.dataChange.next(this.data);
9975
}
10076
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<mat-tree [dataSource]="dataSource" [treeControl]="treeControl">
2+
<mat-nested-tree-node *matTreeNodeDef="let node">
3+
<div class="mat-tree-node">
4+
<button mat-icon-button matTreeNodeToggle
5+
[attr.aria-label]="'toggle ' + node.filename"
6+
[disabled]="!node.children.value.length">
7+
<mat-icon *ngIf="node.children.value.length">
8+
{{treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right'}}
9+
</mat-icon>
10+
</button>
11+
<mat-checkbox *ngIf="!hasNoContent(node)"
12+
[checked]="descendantsAllSelected(node)"
13+
[indeterminate]="descendantsPartiallySelected(node)"
14+
(change)="todoItemSelectionToggle(node)">
15+
{{node.item}}
16+
</mat-checkbox>
17+
<mat-form-field *ngIf="hasNoContent(node)">
18+
<input matInput #itemValue placeholder="New item..."
19+
(blur)="saveNode(node, itemValue.value)">
20+
</mat-form-field>
21+
<button mat-icon-button (click)="addNewItem(node)"><mat-icon>add</mat-icon></button>
22+
</div>
23+
<div class="demo-tree-node-nested"
24+
[class.demo-tree-node-invisible]="!treeControl.isExpanded(node)">
25+
<ng-container matTreeNodeOutlet></ng-container>
26+
</div>
27+
</mat-nested-tree-node>
28+
</mat-tree>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
.demo-tree-node-invisible {
2+
display: none;
3+
}
4+
.demo-tree-node-nested {
5+
padding-left: 40px;
6+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
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+
import {ChangeDetectorRef, ChangeDetectionStrategy, Component} from '@angular/core';
9+
import {SelectionModel} from '@angular/cdk/collections';
10+
import {NestedTreeControl} from '@angular/cdk/tree';
11+
import {Observable} from 'rxjs';
12+
import {ChecklistDatabase, TodoItemNode} from './checklist-database';
13+
14+
/**
15+
* Checklist demo with nested tree
16+
*/
17+
@Component({
18+
moduleId: module.id,
19+
selector: 'checklist-nested-tree-demo',
20+
templateUrl: 'checklist-nested-tree-demo.html',
21+
styleUrls: ['checklist-tree-demo.css'],
22+
providers: [ChecklistDatabase],
23+
changeDetection: ChangeDetectionStrategy.OnPush,
24+
})
25+
export class ChecklistNestedTreeDemo {
26+
treeControl: NestedTreeControl<TodoItemNode>;
27+
28+
dataSource: TodoItemNode[];
29+
30+
/** The selection for checklist */
31+
checklistSelection = new SelectionModel<TodoItemNode>(true /* multiple */);
32+
33+
constructor(private database: ChecklistDatabase, private changeDetectorRef: ChangeDetectorRef) {
34+
this.treeControl = new NestedTreeControl<TodoItemNode>(this.getChildren);
35+
this.dataSource = database.data;
36+
}
37+
38+
getChildren = (node: TodoItemNode): Observable<TodoItemNode[]> => node.children;
39+
40+
hasNoContent = (_nodeData: TodoItemNode) => { return _nodeData.item === ''; };
41+
42+
/** Whether all the descendants of the node are selected */
43+
descendantsAllSelected(node: TodoItemNode): boolean {
44+
const descendants = this.treeControl.getDescendants(node);
45+
if (!descendants.length) {
46+
return this.checklistSelection.isSelected(node);
47+
}
48+
const selected = this.checklistSelection.isSelected(node);
49+
const allSelected = descendants.every(child => this.checklistSelection.isSelected(child));
50+
if (!selected && allSelected) {
51+
this.checklistSelection.select(node);
52+
this.changeDetectorRef.markForCheck();
53+
}
54+
return allSelected;
55+
}
56+
57+
/** Whether part of the descendants are selected */
58+
descendantsPartiallySelected(node: TodoItemNode): boolean {
59+
const descendants = this.treeControl.getDescendants(node);
60+
if (!descendants.length) {
61+
return false;
62+
}
63+
const result = descendants.some(child => this.checklistSelection.isSelected(child));
64+
return result && !this.descendantsAllSelected(node);
65+
}
66+
67+
/** Toggle the to-do item selection. Select/deselect all the descendants node */
68+
todoItemSelectionToggle(node: TodoItemNode): void {
69+
this.checklistSelection.toggle(node);
70+
const descendants = this.treeControl.getDescendants(node);
71+
this.checklistSelection.isSelected(node)
72+
? this.checklistSelection.select(...descendants, node)
73+
: this.checklistSelection.deselect(...descendants, node);
74+
this.changeDetectorRef.markForCheck();
75+
}
76+
77+
/** Select the category so we can insert the new item. */
78+
addNewItem(node: TodoItemNode) {
79+
this.database.insertItem(node, '');
80+
this.treeControl.expand(node);
81+
}
82+
83+
/** Save the node to database */
84+
saveNode(node: TodoItemNode, itemValue: string) {
85+
this.database.updateItem(node, itemValue);
86+
}
87+
}
Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,27 @@
11
<mat-tree [dataSource]="dataSource" [treeControl]="treeControl">
2-
<mat-tree-node *matTreeNodeDef="let node" matTreeNodeToggle matTreeNodePadding>
3-
<button mat-icon-button disabled></button>
4-
<mat-checkbox class="checklist-leaf-node"
5-
[checked]="checklistSelection.isSelected(node)"
6-
(change)="checklistSelection.toggle(node);">{{node.item}}</mat-checkbox>
7-
</mat-tree-node>
8-
<mat-tree-node *matTreeNodeDef="let node; when: hasChild" matTreeNodePadding>
9-
<button mat-icon-button matTreeNodeToggle
10-
[attr.aria-label]="'toggle ' + node.filename">
11-
<mat-icon>
12-
{{treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right'}}
13-
</mat-icon>
14-
</button>
15-
<mat-checkbox [checked]="descendantsAllSelected(node)"
16-
[indeterminate]="descendantsPartiallySelected(node)"
17-
(change)="todoItemSelectionToggle(node)">{{node.item}}</mat-checkbox>
18-
<button mat-icon-button (click)="setParent(node)"><mat-icon>add</mat-icon></button>
2+
<mat-tree-node *matTreeNodeDef="let node" matTreeNodePadding>
3+
<div class="mat-tree-node">
4+
<button mat-icon-button matTreeNodeToggle
5+
[attr.aria-label]="'toggle ' + node.filename"
6+
[disabled]="!node.children.value.length">
7+
<mat-icon *ngIf="node.children.value.length">
8+
{{treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right'}}
9+
</mat-icon>
10+
</button>
11+
<mat-checkbox *ngIf="node.item !== ''"
12+
[checked]="descendantsAllSelected(node)"
13+
[indeterminate]="descendantsPartiallySelected(node)"
14+
(change)="todoItemSelectionToggle(node)">
15+
{{node.item}}
16+
</mat-checkbox>
17+
<mat-form-field *ngIf="node.item === ''">
18+
<input matInput #itemValue placeholder="New item..."
19+
(blur)="saveNode(node, itemValue.value)">
20+
</mat-form-field>
21+
<button mat-icon-button (click)="addNewItem(node)"><mat-icon>add</mat-icon></button>
22+
</div>
23+
<div class="demo-tree-node-nested" [class.tree-node-invisible]="!treeControl.isExpanded(node)">
24+
<ng-container matTreeNodeOutlet></ng-container>
25+
</div>
1926
</mat-tree-node>
2027
</mat-tree>
21-
22-
23-
Selected parent: {{selectedParent ? selectedParent.item : 'No selected'}}
24-
<br>
25-
<mat-form-field>
26-
<input matInput [(ngModel)]="newItemName" placeholder="Add an item"/>
27-
</mat-form-field>
28-
<button mat-button (click)="addNode()">Add node</button>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
.demo-tree-node-invisible {
2+
display: none;
3+
}
4+
.demo-tree-node-nested {
5+
padding-left: 40px;
6+
}

0 commit comments

Comments
 (0)