Skip to content

Instantly share code, notes, and snippets.

@keithshep
Created July 20, 2017 19:16
Show Gist options
  • Save keithshep/2b92696dad8cdc2d80bf429150ee7d6c to your computer and use it in GitHub Desktop.
Save keithshep/2b92696dad8cdc2d80bf429150ee7d6c to your computer and use it in GitHub Desktop.
Tree-Table implemented with Angular
import {
Component,
ContentChildren,
Input,
QueryList,
TemplateRef,
} from '@angular/core';
@Component({
selector: 'pxa-tree-table',
template: `
<table class="table">
<tr *ngFor="let rowData of flattenedData; trackBy: trackByData">
<td *ngFor="let n of range(rowData.depth + 1); last as isLastNodeExpandCol" class="node-expand-col">
<i
*ngIf="isLastNodeExpandCol && showExpandFor(rowData)"
(click)="rowData.data.collapsed = false;"
class="fa fa-plus-square-o"
aria-hidden="true"></i>
<i
*ngIf="isLastNodeExpandCol && showCollapseFor(rowData)"
(click)="rowData.data.collapsed = true;"
class="fa fa-minus-square-o"
aria-hidden="true"></i>
<!-- this last hidden icon is just to ensure consistent spacing -->
<i
*ngIf="isLastNodeExpandCol && !showExpandFor(rowData) && !showCollapseFor(rowData)"
class="fa fa-minus-square-o"
aria-hidden="true"
style="visibility: hidden !important;"></i>
</td>
<td
*ngFor="let colTemp of cellTemplates(rowData.data); first as isFirstDataCol; index as i;"
[attr.colspan]="colspanOf(rowData, i)">
<ng-container *ngTemplateOutlet="colTemp; context: rowData.data"></ng-container>
</td>
</tr>
</table>
`,
styles: [`
.node-expand-col {
width: 1px;
}
`],
})
export class TreeTableComponent {
@Input() trees: TreeTableData[];
@ContentChildren('column') columnTemplates: QueryList<TemplateRef<any>>;
colspanOf(currFD: FlattenedData, colIndex: number) {
let colspans = currFD.data.colspans;
let colspan = colspans ? colspans[colIndex] : 1;
if(colIndex === 0) {
colspan += this.treeDepth - currFD.depth - 1;
}
return colspan;
}
get treeDepth() {
// TODO: since tree depth is so expensive and is used a lot
// we should allow the user to provide this
// as an input if they want
if(this.trees) {
let depth = 0;
for(let tree of this.trees) {
let currDepth = this._treeDepthRecursive(tree);
if(currDepth > depth) {
depth = currDepth;
}
}
return depth;
} else {
return 0;
}
}
cellTemplates(ttd: TreeTableData) {
if(ttd.cellTemplates) {
return ttd.cellTemplates;
} else {
return this.columnTemplates;
}
}
private _treeDepthRecursive(ttd: TreeTableData) {
let depth = 1;
if(ttd.children) {
for(let child of ttd.children) {
let currDepth = 1 + this._treeDepthRecursive(child);
if(currDepth > depth) {
depth = currDepth;
}
}
}
return depth;
}
get flattenedData(): FlattenedData[] {
let flattenedDataArr = [];
if(this.trees) {
for(let tree of this.trees) {
generateFlattenedData(flattenedDataArr, 0, tree);
}
}
return flattenedDataArr;
}
range(x) {
let vals = [];
for(let i = 0; i < x; i++) {
vals.push(i);
}
return vals;
}
trackByData(index: number, item: FlattenedData) {
return item.data;
}
showExpandFor(flattenedData: FlattenedData): boolean {
let data = flattenedData.data;
return data.children && data.children.length && data.collapsed;
}
showCollapseFor(flattenedData: FlattenedData): boolean {
let data = flattenedData.data;
return data.children && data.children.length && !data.collapsed;
}
}
// NOTE: this will be cleaner once I can use ES6 generators
// https://kangax.github.io/compat-table/es6/
function generateFlattenedData(flattenedDataArr: FlattenedData[], depth: number, data: TreeTableData): void {
flattenedDataArr.push({depth, data});
if(data.children && !data.collapsed) {
for(const child of data.children) {
generateFlattenedData(flattenedDataArr, depth + 1, child);
}
}
}
export class TreeTableData {
row: any;
collapsed?: boolean;
children?: TreeTableData[];
cellTemplates?: QueryList<TemplateRef<any>>;
colspans?: number[];
}
class FlattenedData {
depth: number;
data: TreeTableData;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment