import { NestedTreeControl } from '@angular/cdk/tree'; import { Component, Input, OnInit, HostListener, Output, EventEmitter } from '@angular/core'; import { MatTreeNestedDataSource } from '@angular/material/tree'; import { SortPipe } from '../../plugins/sorting.pipe'; import { FormControl } from '@angular/forms'; import { tap, debounceTime } from 'rxjs/operators'; import { LatinisePipe } from 'ngx-pipes'; /** Flat node with expandable and level information */ interface ExampleFlatNode { expandable: boolean; item: string; parent_id: string; level: number; } /** * @title Tree with flat nodes */ @Component({ selector: 'app-maarch-flat-tree', templateUrl: 'maarch-flat-tree.component.html', styleUrls: ['maarch-flat-tree.component.scss'], providers: [SortPipe], }) export class MaarchFlatTreeComponent implements OnInit { @Input() rawData: any = []; @Output() afterSelectNode = new EventEmitter<any>(); @Output() afterDeselectNode = new EventEmitter<any>(); holdShift: boolean = false; treeControl = new NestedTreeControl<any>(node => node.children); dataSource = new MatTreeNestedDataSource<any>(); searchMode: boolean = false; searchTerm: FormControl = new FormControl(''); lastSelectedNodeIds: any[] = []; @HostListener('document:keydown.Shift', ['$event']) onKeydownHandler(event: KeyboardEvent) { this.holdShift = true; } @HostListener('document:keyup.Shift', ['$event']) onKeyupHandler(event: KeyboardEvent) { this.holdShift = false; } constructor( private sortPipe: SortPipe, private latinisePipe: LatinisePipe, ) { } ngOnInit(): void { // SAMPLE /* this.rawData = [ { id: '46', text: 'bonjour', parent_id: null, icon: 'fa fa-building', state: { selected: true, } }, { id: '42', text: 'coucou', parent_id: '46', icon: 'fa fa-building', state: { selected: true, } }, { id: '41', text: 'coucou', parent_id: '42', icon: 'fa fa-building', state: { selected: true, } }, { id: '1', text: 'Compétences fonctionnelles', parent_id: null, icon: 'fa fa-building', state: { selected: true, } }, { id: '232', text: 'Compétences technique', parent_id: null, icon: 'fa fa-building', state: { selected: true, } } ]; */ if (this.rawData.length > 0) { this.initData(); } } initData(data: any = this.rawData) { this.rawData = data.map((item: any) => { return { ...item, parent_id : item.parent_id === '#' ? null : item.parent_id, state: { selected: item.state.selected, opened: item.state.opened, disabled: item.state.disabled, } }; }); this.rawData = this.sortPipe.transform(this.rawData, 'parent_id'); const nestedData = this.flatToNestedObject(this.rawData); this.dataSource.data = nestedData; this.treeControl.dataNodes = nestedData; this.searchTerm.valueChanges .pipe( debounceTime(300), // filter(value => value.length > 2), tap((filterValue: any) => { filterValue = filterValue.trim(); // Remove whitespace filterValue = filterValue.toLowerCase(); // MatTableDataSource defaults to lowercase matches this.searchNode(this.dataSource.data, filterValue); }), ).subscribe(); } getData(id: any) { return this.rawData.filter((elem: any) => elem.id === id)[0]; } getIteration(it: number) { return Array(it).fill(0).map((x, i) => i); } flatToNestedObject(data: any) { const nested = data.reduce((initial: any, value: any, index: any, original: any) => { if (value.parent_id === '') { if (initial.left.length) { this.checkLeftOvers(initial.left, value); } delete value.parent_id; value.root = true; initial.nested.push(value); initial.nested = this.sortPipe.transform(initial.nested, 'text'); initial.nested = initial.nested.map((info: any, indexPar: number) => { return { ...info, last: initial.nested.length - 1 === indexPar, }; }); } else { const parentFound = this.findParent(initial.nested, value); if (parentFound) { this.checkLeftOvers(initial.left, value); } else { initial.left.push(value); } } return index < original.length - 1 ? initial : initial.nested; }, { nested: [], left: [] }); return nested; } checkLeftOvers(leftOvers: any, possibleParent: any) { for (let i = 0; i < leftOvers.length; i++) { if (leftOvers[i].parent_id === possibleParent.id) { // delete leftOvers[i].parent_id; possibleParent.children ? possibleParent.children.push(leftOvers[i]) : possibleParent.children = [leftOvers[i]]; possibleParent.count = possibleParent.children.length; const addedObj = leftOvers.splice(i, 1); this.checkLeftOvers(leftOvers, addedObj[0]); } } } findParent(possibleParents: any, possibleChild: any): any { let found = false; for (let i = 0; i < possibleParents.length; i++) { if (possibleParents[i].id === possibleChild.parent_id) { found = true; // delete possibleChild.parent_id; if (possibleParents[i].children) { possibleParents[i].children.push(possibleChild); } else { possibleParents[i].children = [possibleChild]; } possibleParents[i].count = possibleParents[i].children.length; possibleParents[i].children = this.sortPipe.transform(possibleParents[i].children, 'text'); possibleParents[i].children = possibleParents[i].children.map((info: any, index: number) => { return { ...info, last: possibleParents[i].children.length - 1 === index, }; }); return true; } else if (possibleParents[i].children) { found = this.findParent(possibleParents[i].children, possibleChild); } } return found; } hasChild = (_: number, node: any) => !!node.children && node.children.length > 0; selectNode(node: any) { if (!node.state.disabled) { if (this.searchMode) { this.searchMode = false; this.searchTerm.setValue(''); } this.lastSelectedNodeIds = []; if (this.holdShift) { this.toggleNode( this.dataSource.data, { selected: !node.state.selected, opened: true }, [node.id] ); } else { node.state.selected = !node.state.selected; this.lastSelectedNodeIds = [node]; } if (node.state.selected) { this.afterSelectNode.emit(this.lastSelectedNodeIds); } else { this.afterDeselectNode.emit(this.lastSelectedNodeIds); } } } toggleNode(data, state, nodeIds) { // traverse throuh each node if (Array.isArray(data)) { // if data is an array data.forEach((d) => { if (nodeIds.indexOf(d.id) > -1 || (this.holdShift && nodeIds.indexOf(d.parent_id) > -1)) { Object.keys(state).forEach(key => { if (d.state.disabled && key === 'opened') { d.state[key] = state[key]; } else if (!d.state.disabled) { d.state[key] = state[key]; if (key === 'selected') { this.lastSelectedNodeIds.push(d); } } }); } if (this.holdShift && nodeIds.indexOf(d.parent_id) > -1) { nodeIds.push(d.id); } this.toggleNode(d, state, nodeIds); }); // call the function on each item } else if (data instanceof Object) { // otherwise, if data is an object (data.children || []).forEach((f) => { if (nodeIds.indexOf(f.id) > -1 || (this.holdShift && nodeIds.indexOf(f.parent_id) > -1)) { Object.keys(state).forEach(key => { if (f.state.disabled && key === 'opened') { f.state[key] = state[key]; } else if (!f.state.disabled) { f.state[key] = state[key]; if (key === 'selected') { this.lastSelectedNodeIds.push(f); } } }); } if (this.holdShift && nodeIds.indexOf(f.parent_id) > -1) { nodeIds.push(f.id); } this.toggleNode(f, state, nodeIds); }); // and call function on each child } } searchNode(data, term) { this.searchMode = term !== ''; // traverse throuh each node if (Array.isArray(data)) { // if data is an array data.forEach((d) => { d.state.opened = true; if (this.latinisePipe.transform(d.text.toLowerCase()).indexOf(this.latinisePipe.transform(term)) > -1) { d.state.search = true; } else if (term === '') { delete d.state.search; } else { d.state.search = false; } this.searchNode(d, term); }); // call the function on each item } else if (data instanceof Object) { // otherwise, if data is an object (data.children || []).forEach((f) => { f.state.opened = true; if (this.latinisePipe.transform(f.text.toLowerCase()).indexOf(this.latinisePipe.transform(term)) > -1) { f.state.search = true; } else if (term === '') { delete f.state.search; } else { f.state.search = false; } this.searchNode(f, term); }); // and call function on each child } } getSelectedNodes() { return this.rawData.filter((data: any) => data.state.selected); } }