<!DOCTYPE html>
<html>
<head>
<base href="." />
<title>Angular Demo</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" />
<script src="https://unpkg.com/core-js@^2.4.1/client/shim.js"></script>
<script src="https://unpkg.com/zone.js@0.8.10/dist/zone.js"></script>
<script src="https://unpkg.com/zone.js@0.8.10/dist/long-stack-trace-zone.js"></script>
<script src="https://unpkg.com/reflect-metadata@^0.1.8/Reflect.js"></script>
<script src="https://unpkg.com/systemjs@^0.19.40/dist/system.js"></script>
<script src="config.js"></script>
<script>
System.import('app').catch(console.error.bind(console));
</script>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-12">
<app-root>Loading...</app-root>
</div>
</div>
</div>
</body>
</html>
var ver = {
ng: '5.2.6'
};
System.config({
//use typescript for compilation
transpiler: 'typescript',
//typescript compiler options
typescriptOptions: {
emitDecoratorMetadata: true
},
meta: {
'typescript': {
"exports": "ts"
}
},
paths: {
'npm:': 'https://unpkg.com/'
},
map: {
'app': './src',
'@angular/core': 'npm:@angular/core@' + ver.ng + '/bundles/core.umd.js',
'@angular/common': 'npm:@angular/common@' + ver.ng + '/bundles/common.umd.js',
'@angular/common/http': 'npm:@angular/common@' + ver.ng + '/bundles/common-http.umd.js',
'@angular/compiler': 'npm:@angular/compiler@' + ver.ng + '/bundles/compiler.umd.js',
'@angular/platform-browser': 'npm:@angular/platform-browser@' + ver.ng + '/bundles/platform-browser.umd.js',
'@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic@' + ver.ng + '/bundles/platform-browser-dynamic.umd.js',
'@angular/http': 'npm:@angular/http@' + ver.ng + '/bundles/http.umd.js',
'@angular/router': 'npm:@angular/router@' + ver.ng + '/bundles/router.umd.js',
'@angular/forms': 'npm:@angular/forms@' + ver.ng + '/bundles/forms.umd.js',
'rxjs': 'npm:rxjs@^5.5.2',
'rxjs/operators': 'npm:rxjs@^5.5.2/operators/index.js',
'tslib': 'npm:tslib/tslib.js',
'typescript': 'npm:typescript@2.4.2/lib/typescript.js',
'@ng-bootstrap/ng-bootstrap': 'npm:@ng-bootstrap/ng-bootstrap@1.0.0/bundles/ng-bootstrap.js'
},
packages: {
app: {
main: './main.ts',
defaultExtension: 'ts'
},
rxjs: {
defaultExtension: 'js'
}
}
});
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {AppModule} from './app/app.module.ts';
platformBrowserDynamic().bootstrapModule(AppModule);
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { AppComponent } from './app.component';
import { TreeViewComponent } from './tree-view.component';
@NgModule({
declarations: [
AppComponent,
TreeViewComponent
],
imports: [
BrowserModule,
FormsModule,
NgbModule.forRoot()
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<app-tree-view [data]="treeData" [collapseAll]="collapseAll" [selectAll]="selectAll" (onClick)="click($event)" (onChange)="onChange($event)">
</app-tree-view>
<div class="btn-toolbar" role="toolbar" aria-label="Toolbar with button groups">
<div class="btn-group mr-2" role="group" aria-label="First group">
<button type="button" class="btn btn-sm btn-secondary" (click)="collapseAll = true">Collapse All</button>
<button type="button" class="btn btn-sm btn-secondary" (click)="collapseAll = false">Expand All</button>
</div>
<div class="btn-group" role="group" aria-label="Second group">
<button type="button" class="btn btn-sm btn-secondary" (click)="selectAll = true">Check All</button>
<button type="button" class="btn btn-sm btn-secondary" (click)="selectAll = false">Uncheck All</button>
</div>
</div>
`,
styles: [`
`]
})
export class AppComponent {
public treeData = [
{'ID': 1, 'NAME': 'ROOT_1'}, {'ID': 2, 'NAME': 'ROOT_2'},
{'ID': 3, 'NAME': 'ROOT_3', 'PARENT_ID': 2},
{'ID': 4, 'NAME': 'ROOT_4', 'PARENT_ID': 3},
{'ID': 5, 'NAME': 'ROOT_5', 'PARENT_ID': 4},
{'ID': 6, 'NAME': 'ROOT_6', 'PARENT_ID': 7},
{'ID': 7, 'NAME': 'ROOT_7'},
{'ID': 8, 'NAME': 'ROOT_8', 'PARENT_ID': 7},
{'ID': 9, 'NAME': 'ROOT_9', 'PARENT_ID': 7},
{'ID': 10, 'NAME': 'ROOT_10', 'PARENT_ID': 7},
{'ID': 11, 'NAME': 'ROOT_11', 'PARENT_ID': 7},
{'ID': 12, 'NAME': 'ROOT_12', 'PARENT_ID': 7},
{'ID': 13, 'NAME': 'ROOT_13'},
{'ID': 14, 'NAME': 'ROOT_14'},
{'ID': 15, 'NAME': 'ROOT_15', 'PARENT_ID': 4},
{'ID': 16, 'NAME': 'ROOT_16', 'PARENT_ID': 4},
{'ID': 17, 'NAME': 'ROOT_17', 'PARENT_ID': 16},
{'ID': 18, 'NAME': 'ROOT_18', 'PARENT_ID': 16},
{'ID': 19, 'NAME': 'ROOT_19', 'PARENT_ID': 18},
{'ID': 20, 'NAME': 'ROOT_20', 'PARENT_ID': 2},
{'ID': 21, 'NAME': 'ROOT_21', 'PARENT_ID': 2},
{'ID': 22, 'NAME': 'ROOT_22', 'PARENT_ID': 3},
{'ID': 23, 'NAME': 'ROOT_23', 'PARENT_ID': 3},
{'ID': 24, 'NAME': 'ROOT_24', 'PARENT_ID': 3},
{'ID': 25, 'NAME': 'ROOT_25', 'PARENT_ID': 4},
{'ID': 26, 'NAME': 'ROOT_26', 'PARENT_ID': 18}
];
public collapseAll: boolean;
public selectAll: boolean;
click(node: any){
console.log(node);
}
onChange(data){
console.log(data);
}
}
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
/**
* The NgbTreeview a simple way to create tree view in html.
*/
@Component({
selector: 'app-tree-view',
template: `
<ul class="treeview" *ngIf="nodes.length">
<li *ngFor="let node of nodes">
<span *ngIf="node[childrenAttr].length" [ngClass]="{'node-opened': !node[collapseAttr]}">></span>
<span *ngIf="!node[childrenAttr].length">●</span>
<input type="checkbox"
[(ngModel)]="node[selectAttr]"
[indeterminate]="node[inDeterminateAttr]"
(change)="onModelChange(node)" />
<span [ngClass]="{'parent': node[childrenAttr].length}"
(click)="click(node)">
{{node.NAME}}
</span>
<app-tree-view *ngIf="node[childrenAttr].length"
[data]="node[childrenAttr]"
[prepareData]="false"
[ngbCollapse]="node[collapseAttr]"
(onChange)="change($event)"
>
</app-tree-view>
</li>
</ul>
`,
styles: [`
.treeview {
list-style-type: none;
}
.treeview .parent {
font-weight: bold;
cursor: pointer;
}
.treeview span {
display: inline-block;
}
.treeview .node-opened {
transform: rotate(90deg);
}
`]
})
export class TreeViewComponent implements OnInit {
private _collapseAll: boolean;
private _selectAll: boolean;
public nodes: any[] = [];
public collapseAttr: string = 'isCollapsed';
public selectAttr: string = 'isSelected';
public inDeterminateAttr: string = 'isIndeterminate';
/**
* Providen data for tree.
*/
@Input('data') data: any[];
/**
* A flag indicating data is flatten in array and prepare is required.(Default
* is true).
*/
@Input('prepareData') prepareData: boolean = true;
/**
* Name of ID property in input data.
*/
@Input('idAttr') idAttr: string = 'ID';
/**
* Name of parent property in input data.
*/
@Input('parentAttr') parentAttr: string = 'PARENT_ID';
/**
* Name of children list property in input data.
*/
@Input('childrenAttr') childrenAttr: string = 'CHILDREN';
/**
* Collapse or expand all parent nodes.
*/
@Input('collapseAll')
set collapseAll(value: boolean) {
this._collapseAll = value;
this._recursiveEdit(
this.nodes, this.childrenAttr, this.collapseAttr, value);
}
/**
* Select or deselect all nodes.
*/
@Input('selectAll')
set selectAll(value: boolean) {
this._selectAll = value;
this._recursiveEdit(this.nodes, this.childrenAttr, this.selectAttr, value);
this._recursiveEdit(
this.nodes, this.childrenAttr, this.inDeterminateAttr, false);
}
/**
* When change a node model this event will be emit.
*/
@Output() onChange = new EventEmitter<any>();
/**
* On click node.
*/
@Output() onClick = new EventEmitter<any>();
constructor() {}
ngOnInit() {
// Clone input data for protect.
const cloned = this.data.map(x => Object.assign([], x));
// If data is flat, prepare data with recursive function.
this.nodes = this.prepareData ? this._getPreparedData(cloned) : this.data;
}
onModelChange(node) {
if (node[this.childrenAttr].length) {
this._recursiveEdit(
[node], this.childrenAttr, this.selectAttr, node[this.selectAttr]);
}
this.onChange.emit(node);
}
click(node: any) {
if (node[this.childrenAttr].length) {
node[this.collapseAttr] = !node[this.collapseAttr]
}
this.onClick.emit(node);
}
change(value: any) {
const parent = this.nodes.filter(
(item) => {return item.ID === value[this.parentAttr]})[0];
if (parent) {
let hasDifferent = false, duplicate = {},
isIndeterminate = value[this.inDeterminateAttr] || false;
parent[this.childrenAttr].forEach((item) => {
duplicate[item[this.selectAttr]] =
(duplicate[item[this.selectAttr]] || 0) + 1;
if (item[this.inDeterminateAttr]) {
isIndeterminate = true;
}
});
if (Object.keys(duplicate).length === 1 && !isIndeterminate) {
parent[this.inDeterminateAttr] = false;
parent[this.selectAttr] = JSON.parse(Object.keys(duplicate)[0]);
this.onChange.emit(parent);
} else {
parent[this.inDeterminateAttr] = true;
this.onChange.emit(parent);
}
}
}
private _recursiveEdit(list, childrenAttr, attr, value) {
if (Array.isArray(list)) {
for (let i = 0, len = list.length; i < len; i++) {
list[i][attr] = value;
if (list[i][childrenAttr].length) {
this._recursiveEdit(list[i][childrenAttr], childrenAttr, attr, value);
}
}
}
}
private _getPreparedData(list) {
let tree = [], lookup = {};
for (let i = 0, len = list.length; i < len; i++) {
lookup[list[i][this.idAttr]] = list[i];
list[i][this.childrenAttr] = [];
list[i][this.collapseAttr] = true;
list[i][this.selectAttr] = false;
list[i][this.inDeterminateAttr] = false;
}
for (let i = 0, len = list.length; i < len; i++) {
if (list[i][this.parentAttr]) {
lookup[list[i][this.parentAttr]][this.childrenAttr].push(list[i]);
} else {
tree.push(list[i]);
}
}
return tree;
};
}