Skip to content

docs(tree): add more tree examples #10386

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 29, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 41 additions & 65 deletions src/demo-app/tree/checklist-tree-demo/checklist-database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,37 +12,35 @@ import {BehaviorSubject} from 'rxjs';
* Node for to-do item
*/
export class TodoItemNode {
children: TodoItemNode[];
item: string;
}

/** Flat to-do item node with expandable and level information */
export class TodoItemFlatNode {
item: string;
level: number;
expandable: boolean;
children: BehaviorSubject<TodoItemNode[]>;
constructor(public item: string, children?: TodoItemNode[], public parent?: TodoItemNode) {
this.children = new BehaviorSubject(children === undefined ? [] : children);
}
}

/**
* The Json object for to-do list data.
*/
const TREE_DATA = {
'Reminders': [
'Cook dinner',
'Read the Material Design spec',
'Upgrade Application to Angular'
],
'Groceries': {
'Organic eggs': null,
'Protein Powder': null,
'Almond Meal flour': null,
'Fruits': {
'Apple': null,
'Orange': null,
'Berries': ['Blueberry', 'Raspberry']
}
}
};
const TREE_DATA = [
new TodoItemNode('Reminders', [
new TodoItemNode('Cook dinner'),
new TodoItemNode('Read the Material Design spec'),
new TodoItemNode('Upgrade Application to Angular')
]),
new TodoItemNode('Groceries', [
new TodoItemNode('Organic eggs'),
new TodoItemNode('Protein Powder'),
new TodoItemNode('Almond Meal flour'),
new TodoItemNode('Fruits', [
new TodoItemNode('Apple'),
new TodoItemNode('Orange'),
new TodoItemNode('Berries', [
new TodoItemNode('Blueberry'),
new TodoItemNode('Raspberry')
])
])
])
];

/**
* Checklist database, it can build a tree structured Json object.
Expand All @@ -51,50 +49,28 @@ const TREE_DATA = {
*/
@Injectable()
export class ChecklistDatabase {
dataChange: BehaviorSubject<TodoItemNode[]> = new BehaviorSubject<TodoItemNode[]>([]);

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

constructor() {
this.initialize();
}

initialize() {
// Build the tree nodes from Json object. The result is a list of `TodoItemNode` with nested
// file node as children.
const data = this.buildFileTree(TREE_DATA, 0);
dataChange: BehaviorSubject<TodoItemNode[]> = new BehaviorSubject<TodoItemNode[]>(TREE_DATA);

// 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 `TodoItemNode`.
*/
buildFileTree(value: any, level: number) {
let data: any[] = [];
for (let k in value) {
let v = value[k];
let node = new TodoItemNode();
node.item = `${k}`;
if (v === null || v === undefined) {
// no action
} else if (typeof v === 'object') {
node.children = this.buildFileTree(v, level + 1);
} else {
node.item = v;
}
data.push(node);
}
return data;
get data(): TodoItemNode[] {
return this.dataChange.value;
}

/** Add an item to to-do list */
insertItem(parent: TodoItemNode, name: string) {
const child = <TodoItemNode>{item: name};
if (parent.children) {
parent.children.push(child);
const child = new TodoItemNode(name, [], parent);
let children = parent.children.value;
children.push(child);
parent.children.next(children);
this.dataChange.next(this.data);
}

updateItem(node: TodoItemNode, name: string) {
let newNode = new TodoItemNode(name, node.children.value, node.parent);
if (node.parent) {
let children = node.parent.children.value;
let index = children.indexOf(node);
children.splice(index, 1, newNode);
node.parent.children.next(children);
this.dataChange.next(this.data);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<mat-tree [dataSource]="dataSource" [treeControl]="treeControl">
<mat-nested-tree-node *matTreeNodeDef="let node">
<div class="mat-tree-node">
<button mat-icon-button matTreeNodeToggle
[attr.aria-label]="'toggle ' + node.filename"
[disabled]="!node.children.value.length">
<mat-icon *ngIf="node.children.value.length">
{{treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right'}}
</mat-icon>
</button>
<mat-checkbox *ngIf="!hasNoContent(node)"
[checked]="descendantsAllSelected(node)"
[indeterminate]="descendantsPartiallySelected(node)"
(change)="todoItemSelectionToggle(node)">
{{node.item}}
</mat-checkbox>
<mat-form-field *ngIf="hasNoContent(node)">
<input matInput #itemValue placeholder="New item..."
(blur)="saveNode(node, itemValue.value)">
</mat-form-field>
<button mat-icon-button (click)="addNewItem(node)"><mat-icon>add</mat-icon></button>
</div>
<div class="demo-tree-node-nested"
[class.demo-tree-node-invisible]="!treeControl.isExpanded(node)">
<ng-container matTreeNodeOutlet></ng-container>
</div>
</mat-nested-tree-node>
</mat-tree>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.demo-tree-node-invisible {
display: none;
}
.demo-tree-node-nested {
padding-left: 40px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/**
* @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 {ChangeDetectorRef, ChangeDetectionStrategy, Component} from '@angular/core';
import {SelectionModel} from '@angular/cdk/collections';
import {NestedTreeControl} from '@angular/cdk/tree';
import {Observable} from 'rxjs';
import {ChecklistDatabase, TodoItemNode} from './checklist-database';

/**
* Checklist demo with nested tree
*/
@Component({
moduleId: module.id,
selector: 'checklist-nested-tree-demo',
templateUrl: 'checklist-nested-tree-demo.html',
styleUrls: ['checklist-tree-demo.css'],
providers: [ChecklistDatabase],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ChecklistNestedTreeDemo {
treeControl: NestedTreeControl<TodoItemNode>;

dataSource: TodoItemNode[];

/** The selection for checklist */
checklistSelection = new SelectionModel<TodoItemNode>(true /* multiple */);

constructor(private database: ChecklistDatabase, private changeDetectorRef: ChangeDetectorRef) {
this.treeControl = new NestedTreeControl<TodoItemNode>(this.getChildren);
this.dataSource = database.data;
}

getChildren = (node: TodoItemNode): Observable<TodoItemNode[]> => node.children;

hasNoContent = (_nodeData: TodoItemNode) => { return _nodeData.item === ''; };

/** Whether all the descendants of the node are selected */
descendantsAllSelected(node: TodoItemNode): boolean {
const descendants = this.treeControl.getDescendants(node);
if (!descendants.length) {
return this.checklistSelection.isSelected(node);
}
const selected = this.checklistSelection.isSelected(node);
const allSelected = descendants.every(child => this.checklistSelection.isSelected(child));
if (!selected && allSelected) {
this.checklistSelection.select(node);
this.changeDetectorRef.markForCheck();
}
return allSelected;
}

/** Whether part of the descendants are selected */
descendantsPartiallySelected(node: TodoItemNode): boolean {
const descendants = this.treeControl.getDescendants(node);
if (!descendants.length) {
return false;
}
const result = descendants.some(child => this.checklistSelection.isSelected(child));
return result && !this.descendantsAllSelected(node);
}

/** Toggle the to-do item selection. Select/deselect all the descendants node */
todoItemSelectionToggle(node: TodoItemNode): void {
this.checklistSelection.toggle(node);
const descendants = this.treeControl.getDescendants(node);
this.checklistSelection.isSelected(node)
? this.checklistSelection.select(...descendants, node)
: this.checklistSelection.deselect(...descendants, node);
this.changeDetectorRef.markForCheck();
}

/** Select the category so we can insert the new item. */
addNewItem(node: TodoItemNode) {
this.database.insertItem(node, '');
this.treeControl.expand(node);
}

/** Save the node to database */
saveNode(node: TodoItemNode, itemValue: string) {
this.database.updateItem(node, itemValue);
}
}
49 changes: 24 additions & 25 deletions src/demo-app/tree/checklist-tree-demo/checklist-tree-demo.html
Original file line number Diff line number Diff line change
@@ -1,28 +1,27 @@
<mat-tree [dataSource]="dataSource" [treeControl]="treeControl">
<mat-tree-node *matTreeNodeDef="let node" matTreeNodeToggle matTreeNodePadding>
<button mat-icon-button disabled></button>
<mat-checkbox class="checklist-leaf-node"
[checked]="checklistSelection.isSelected(node)"
(change)="checklistSelection.toggle(node);">{{node.item}}</mat-checkbox>
</mat-tree-node>
<mat-tree-node *matTreeNodeDef="let node; when: hasChild" matTreeNodePadding>
<button mat-icon-button matTreeNodeToggle
[attr.aria-label]="'toggle ' + node.filename">
<mat-icon>
{{treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right'}}
</mat-icon>
</button>
<mat-checkbox [checked]="descendantsAllSelected(node)"
[indeterminate]="descendantsPartiallySelected(node)"
(change)="todoItemSelectionToggle(node)">{{node.item}}</mat-checkbox>
<button mat-icon-button (click)="setParent(node)"><mat-icon>add</mat-icon></button>
<mat-tree-node *matTreeNodeDef="let node" matTreeNodePadding>
<div class="mat-tree-node">
<button mat-icon-button matTreeNodeToggle
[attr.aria-label]="'toggle ' + node.filename"
[disabled]="!node.children.value.length">
<mat-icon *ngIf="node.children.value.length">
{{treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right'}}
</mat-icon>
</button>
<mat-checkbox *ngIf="node.item !== ''"
[checked]="descendantsAllSelected(node)"
[indeterminate]="descendantsPartiallySelected(node)"
(change)="todoItemSelectionToggle(node)">
{{node.item}}
</mat-checkbox>
<mat-form-field *ngIf="node.item === ''">
<input matInput #itemValue placeholder="New item..."
(blur)="saveNode(node, itemValue.value)">
</mat-form-field>
<button mat-icon-button (click)="addNewItem(node)"><mat-icon>add</mat-icon></button>
</div>
<div class="demo-tree-node-nested" [class.tree-node-invisible]="!treeControl.isExpanded(node)">
<ng-container matTreeNodeOutlet></ng-container>
</div>
</mat-tree-node>
</mat-tree>


Selected parent: {{selectedParent ? selectedParent.item : 'No selected'}}
<br>
<mat-form-field>
<input matInput [(ngModel)]="newItemName" placeholder="Add an item"/>
</mat-form-field>
<button mat-button (click)="addNode()">Add node</button>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.demo-tree-node-invisible {
display: none;
}
.demo-tree-node-nested {
padding-left: 40px;
}
Loading