@neoprospecta/angular-data-box
Version:
Data table with REST implementation.
603 lines (536 loc) • 31.7 kB
text/typescript
import { Component, Input, Output, EventEmitter, OnChanges } from '@angular/core';
import { FormControl } from '@angular/forms';
import { BehaviorSubject } from 'rxjs';
import { DataBoxColumn, DataBoxAction } from './';
import { FilterPipe } from '@neoprospecta/angular-custom-pipes';
({
selector: 'data-box',
styles: [".data-box-wrapper md-icon[fontSet=fa]{font-size:19px;height:auto}.data-box-wrapper md-input-container{width:100%}.data-box-wrapper .table-actions-wrapper{text-align:right}.data-box-wrapper .table-actions-wrapper span{display:inline-block;padding:10px 0px}.data-box-wrapper .text-rigth{text-align:right}.data-box-wrapper table{border-collapse:collapse;width:100%}.data-box-wrapper table .array-ul{list-style:none;margin:0;padding:4px}.data-box-wrapper table .array-ul li{line-height:12px;font-size:10px}.data-box-wrapper table a{text-decoration:none}.data-box-wrapper table .nowrap{white-space:nowrap}.data-box-wrapper table .fit-text{width:1px}.data-box-wrapper table thead tr{height:50px}.data-box-wrapper table td,.data-box-wrapper table th{padding:0px 6px}.data-box-wrapper table tbody md-icon .icon-text{padding:0px 4px 0px 3px;border-radius:3px;font-size:13px;line-height:17px;margin-left:-7px;margin-top:-7px;position:absolute}.data-box-wrapper table tbody a{font-weight:800}.data-box-wrapper table tbody tr{font-size:12px;line-height:20px}.data-box-wrapper table tbody .option-selected{display:grid} "],
template: "<div class=\"data-box-wrapper\"> <!-- TITLE section --> <div *ngIf=\"title\"> <div [ngSwitch]=\"titleSize\"> <h1 *ngSwitchCase=\"'h1'\"> {{title}} <button *ngIf=\"insert\" md-icon-button (click)=\"addItem()\"> <md-icon fontSet=\"fa\" fontIcon=\"fa-plus\" title=\"Adicionar\" color=\"primary\"></md-icon> </button> </h1> <h2 *ngSwitchCase=\"'h2'\"> {{title}} <button *ngIf=\"insert\" md-icon-button (click)=\"addItem()\"> <md-icon fontSet=\"fa\" fontIcon=\"fa-plus\" title=\"Adicionar\" color=\"primary\"></md-icon> </button> </h2> <h3 *ngSwitchCase=\"'h3'\"> {{title}} <button *ngIf=\"insert\" md-icon-button (click)=\"addItem()\"> <md-icon fontSet=\"fa\" fontIcon=\"fa-plus\" title=\"Adicionar\" color=\"primary\"></md-icon> </button> </h3> <h4 *ngSwitchCase=\"'h4'\"> {{title}} <button *ngIf=\"insert\" md-icon-button (click)=\"addItem()\"> <md-icon fontSet=\"fa\" fontIcon=\"fa-plus\" title=\"Adicionar\" color=\"primary\"></md-icon> </button> </h4> <h5 *ngSwitchCase=\"'h5'\"> {{title}} <button *ngIf=\"insert\" md-icon-button (click)=\"addItem()\"> <md-icon fontSet=\"fa\" fontIcon=\"fa-plus\" title=\"Adicionar\" color=\"primary\"></md-icon> </button> </h5> <h6 *ngSwitchCase=\"'h6'\"> {{title}} <button *ngIf=\"insert\" md-icon-button (click)=\"addItem()\"> <md-icon fontSet=\"fa\" fontIcon=\"fa-plus\" title=\"Adicionar\" color=\"primary\"></md-icon> </button> </h6> <p *ngSwitchCase=\"undefined\"> {{title}} <button *ngIf=\"insert\" md-icon-button (click)=\"addItem()\"> <md-icon fontSet=\"fa\" fontIcon=\"fa-plus\" title=\"Adicionar\" color=\"primary\"></md-icon> </button> </p> </div> </div> <!-- end TITLE section --> <!-- LOAD section --> <div *ngIf=\"noDataProvided\"> <p>Dados não fornecidos.</p> </div> <div *ngIf=\"loading\"> <p>Carregando dados...</p> </div> <div *ngIf=\"saving\"> <p>Salvando dados...</p> </div> <!-- end LOAD section --> <!-- TABLE section--> <div *ngIf=\"!noDataProvided && !loading\"> <!-- TIPS section --> <div class=\"text-rigth\" *ngIf=\"includeTips\"> <button md-icon-button (click)=\"showTips = !showTips\" title=\"Ajuda\"> <md-icon fontSet=\"fa\" fontIcon=\"fa-question\" color=\"primary\"></md-icon> </button> <div *ngIf=\"showTips\"> <hr> Como usar a tabela <ul> <li>shift + click: seleciona multiplas linhas.</li> <li>ctrl + click: seleciona multiplas linhas, uma por clique.</li> <li>shift + ctrl + click: copia um valor pra multiplas linhas.</li> <li>alt + ctrl + click: copia um valor pra multiplas linhas e adiciona +1 se o último conteúdo for numérico (dia 1, dia 2, dia 3...)</li> <li>Nenhuma alteração é salva automaticamente. Para salvar as alterações é preciso clicar em \"Salvar alterações\"</li> </ul> <hr> </div> </div> <!-- end TIPS section --> <div class=\"table-actions-wrapper\"> <span *ngIf=\"dataChanged.length\"> <button md-raised-button color=\"primary\" (click)=\"saveChanges()\">Salvar</button> <button md-raised-button color=\"warn\" (click)=\"discardChanges()\">Descartar</button> </span> <span *ngIf=\"hasMultiSelection()\"> <button md-raised-button color=\"accent\" (click)=\"copyToMultipleSelection()\">Copiar</button> <button md-raised-button color=\"accent\" (click)=\"copyToMultipleSelection(true)\">Iterar</button> </span> </div> <div *ngIf=\"searchable\"> <md-input-container> <input mdInput type=\"text\" [formControl]=\"searchStringControl\" placeholder=\"Filtre pelo nome, código...\" /> <span *ngIf=\"searchStringControl.value\" mdSuffix (click)=\"searchStringControl.setValue('')\"> <md-icon fontSet=\"fa\" fontIcon=\"fa-close\"></md-icon> </span> </md-input-container> </div> <div *ngIf=\"includeCounter\"> <small> Mostrando {{filteredData.length}} de {{tableData.length}} resultados <a href=\"javascript:void(0)\" *ngIf=\"filteredData.length < tableData.length\" (click)=\"searchStringControl.setValue('')\">(Mostrar tudo)</a> </small> </div> <!-- PAGINATOR --> <np-paginator *ngIf=\"onPagination && currentPage && amountPerPage\" (pageChanged)=\"pageChanged($event)\" [totalItems]=\"totalItems || filteredData.length\" [currentPage]=\"currentPage\" [amountPerPage]=\"amountPerPage\" > </np-paginator> <!-- END PAGINATOR section--> <table [ngClass]=\"{'stripped': stripped, 'hover': hover, 'condensed': condensed}\"> <thead> <tr class=\"nowrap\"> <th class=\"fit-text\" *ngIf=\"selectable\"> <md-checkbox color=\"accent\" (change)=\"markAllObjects($event)\" [checked]=\"isAllObjectsMarked()\"></md-checkbox> </th> <th *ngFor=\"let column of columns\" align=\"{{column.align || left}}\" [ngClass]=\"{'fit-text': column.fitText}\"> <span [attr.title]=\"getAttributeValue(undefined, column.tooltip)\"> <span [ngSwitch]=\"column.sort\"> <span *ngSwitchCase=\"true\" (click)=\"sortBy(column.attr)\"> {{column.header}} <a href=\"javascript:void(0)\" *ngIf=\"sortAttr == column.attr\"><md-icon fontSet=\"fa\" fontIcon=\"fa-sort-{{sortDirection ? 'up' : 'down'}}\" md-input-icon></md-icon></a> <a href=\"javascript:void(0)\" *ngIf=\"!(sortAttr == column.attr)\"><md-icon fontSet=\"fa\" fontIcon=\"fa-sort\" md-input-icon></md-icon></a> </span> <span *ngSwitchDefault=\"\">{{column.header}}</span> </span> </span> </th> <th class=\"fit-text\" *ngIf=\"actions && actions.length\">Ações</th> </tr> </thead> <tbody> <tr *ngFor=\"let object of filteredData | orderBy:sortAttr:sortDirection; let objectIndex = index; trackBy:trackBy\"> <td class=\"fit-text\" *ngIf=\"selectable\"> <md-checkbox color=\"accent\" (change)=\"markObject($event, object)\" (click)=\"markObjectByRange($event, object)\" [checked]=\"isObjectMarked(object)\"></md-checkbox> </td> <td *ngFor=\"let column of columns\" align=\"{{column.align || left}}\" [ngClass]=\"{'fit-text': column.fitText, 'highlight-cell': isCellSelected(object, column.attr), 'nowrap': column.actions.length > 0}\" (click)=\"selectCell($event, object, column.attr, column.selectable)\" [attr.title]=\"getAttributeValue(object, column.tooltip)\"> <div [ngSwitch]=\"column.editable\"> <span *ngSwitchCase=\"false\"> <div [ngSwitch]=\"column.type\"> <span *ngSwitchCase=\"'text'\">{{getAttributeValue(object, column.attr)}}</span> <span *ngSwitchCase=\"'text-icon'\"> <md-icon color=\"{{getAttributeValue(object, column.colorIcon)}}\" fontSet=\"fa\" fontIcon=\"{{getAttributeValue(object, column.fontIcon)}}\"></md-icon> {{getAttributeValue(object, column.attr)}} </span> <span *ngSwitchCase=\"'number'\">{{getAttributeValue(object, column.attr)}}</span> <span *ngSwitchCase=\"'option'\">{{getOptionLabel(getAttributeValue(object, column.attr), column.options)}}</span> <span *ngSwitchCase=\"'currency'\">{{getAttributeValue(object, column.attr) | currency:column.currencyLocation:true}}</span> <span *ngSwitchCase=\"'date' || 'date-time'\"> <div *ngIf=\"getAttributeValue(object, column.attr) != column.attr\"> {{getAttributeValue(object, column.attr) | date: column.dateFormat}} </div> </span> <span *ngSwitchCase=\"'boolean'\"> <span [ngSwitch]=\"getAttributeValue(object, column.attr)\"> <md-icon *ngSwitchCase=\"true\" color=\"primary\" fontSet=\"fa\" fontIcon=\"fa-check\" md-list-icon></md-icon> </span> </span> <ul *ngSwitchCase=\"'array'\" class=\"array-ul\"> <li *ngFor=\"let item of getAttributeValue(object, column.attr)\">{{getAttributeValue(item, column.arrayAttr, object)}}</li> </ul> <span *ngSwitchCase=\"'actions'\"> <ng-container *ngFor=\"let action of column.actions\"> <span *ngIf=\"action.action === 'menu'\"> <button *ngIf=\"action.icon\" md-icon-button [mdMenuTriggerFor]=\"btnAction\" [attr.title]=\"getAttributeValue(undefined, action.tooltip)\"> <md-icon *ngIf=\"action.icon\" fontSet=\"fa\" fontIcon=\"{{action.icon}}\" color=\"{{action.color}}\"></md-icon> </button> <button *ngIf=\"!action.icon\" md-raised-button [mdMenuTriggerFor]=\"btnAction\" [attr.title]=\"getAttributeValue(undefined, action.tooltip)\" color=\"{{action.color || 'default'}}\"> <span>{{geActionTitle(object, action.title)}}</span> </button> <md-menu #btnAction=\"mdMenu\" xPosition=\"before\" yPosition=\"below\"> <button *ngFor=\"let option of action.options\" (click)=\"callAction(option.function, object)\" md-menu-item> {{option.name}} </button> </md-menu> </span> <span *ngIf=\"action.action !== 'menu'\"> <button *ngIf=\"action.icon\" md-icon-button [attr.title]=\"getAttributeValue(undefined, action.tooltip)\" (click)=\"callAction(action.action, object)\"> <md-icon *ngIf=\"action.icon\" fontSet=\"fa\" fontIcon=\"{{action.icon}}\" color=\"{{action.color}}\"></md-icon> </button> <button *ngIf=\"!action.icon\" md-raised-button [attr.title]=\"getAttributeValue(undefined, action.tooltip)\" (click)=\"callAction(action.action, object)\" color=\"{{action.color || 'default'}}\"> <span>{{geActionTitle(object, action.title)}}</span> </button> </span> </ng-container> </span> </div> </span> <div *ngSwitchCase=\"true\"> <div [ngSwitch]=\"column.type\"> <span *ngSwitchCase=\"'text'\"> <md-input-container> <input *ngIf=\"!column.attr2\" mdInput type=\"text\" [(ngModel)]=\"object[column.attr]\" (change)=\"markChanged(object)\" name=\"{{column.attr}}{{objectIndex}}\" /> <input *ngIf=\"column.attr2\" mdInput type=\"text\" [(ngModel)]=\"object[column.attr][column.attr2]\" (change)=\"markChanged(object)\" name=\"{{column.attr}}{{column.attr2}}{{objectIndex}}\" /> </md-input-container> </span> <span *ngSwitchCase=\"'text-icon'\"> <md-input-container> <input mdInput type=\"text\" [(ngModel)]=\"object[column.attr]\" (change)=\"markChanged(object)\" name=\"{{column.attr}}{{objectIndex}}\" /> </md-input-container> </span> <span *ngSwitchCase=\"'option'\"> <span *ngIf=\"column.unique\" class=\"option-selected\"> {{object[column.attr]}} </span> <md-select [disabled]=\"disableOptionCell(object, column.disable)\" [(ngModel)]=\"object[column.attr]\" (change)=\"markChanged(object)\" name=\"{{column.attr}}{{objectIndex}}\"> <md-option [value]=\"undefined\" *ngIf=\"column.options.length > 0\" >-</md-option> <md-option [value]=\"undefined\" *ngIf=\"column.options.length === 0\" >Sem opções para selecionar</md-option> <md-option *ngFor=\"let option of column.options\" [value]=\"option.id\"> {{option.label}} </md-option> </md-select> </span> <span *ngSwitchCase=\"'number'\"> <md-input-container> <input mdInput *ngIf=\"!column.attr2\" type=\"number\" max=\"{{column.max}}\" min=\"{{column.min}}\" [(ngModel)]=\"object[column.attr]\" (change)=\"markChanged(object)\" name=\"{{column.attr}}{{objectIndex}}\" /> <input mdInput *ngIf=\"column.attr2\" type=\"number\" max=\"{{column.max}}\" min=\"{{column.min}}\" [(ngModel)]=\"object[column.attr][column.attr2]\" (change)=\"markChanged(object)\" name=\"{{column.attr}}{{column.attr2}}{{objectIndex}}\" /> </md-input-container> </span> <span *ngSwitchCase=\"'boolean'\"> <md-checkbox color=\"primary\" name=\"{{column.attr}}{{objectIndex}}\" [(ngModel)]=\"object[column.attr]\" (change)=\"markChanged(object)\"></md-checkbox> </span> <span *ngSwitchCase=\"'date-time'\"> <np-datetime-picker *ngIf=\"!column.attr2\" [(ngModel)]=\"object[column.attr]\" (change)=\"markChanged(object)\" name=\"{{column.attr}}{{objectIndex}}\" close-on-select=\"false\" date-format=\"{{column.dateFormat}}\" ></np-datetime-picker> <np-datetime-picker *ngIf=\"column.attr2\" [(ngModel)]=\"object[column.attr]\" (change)=\"markChanged(object)\" name=\"{{column.attr}}{{column.attr2}}{{objectIndex}}\" close-on-select=\"false\" date-format=\"{{column.dateFormat}}\" ></np-datetime-picker> </span> <span *ngSwitchCase=\"'date'\"> <np-datetime-picker *ngIf=\"!column.attr2\" [(ngModel)]=\"object[column.attr]\" (change)=\"markChanged(object)\" name=\"{{column.attr}}{{objectIndex}}\" close-on-select=\"true\" date-format=\"{{column.dateFormat}}\" date-only=\"true\" ></np-datetime-picker> <np-datetime-picker *ngIf=\"column.attr2\" [(ngModel)]=\"object[column.attr][column.attr2]\" (change)=\"markChanged(object)\" name=\"{{column.attr}}{{column.attr2}}{{objectIndex}}\" close-on-select=\"true\" date-format=\"{{column.dateFormat}}\" date-only=\"true\" ></np-datetime-picker> </span> </div> </div> </div> </td> <td class=\"nowrap\" *ngIf=\"actions && actions.length\"> <ng-container *ngFor=\"let action of actions\"> <button *ngIf=\"showActionButton(object, action)\" md-icon-button title=\"{{action.title}}\" (click)=\"callAction(action.action, object)\"> <md-icon *ngIf=\"action.icon\" fontSet=\"fa\" fontIcon=\"{{action.icon}}\" color=\"{{action.color}}\"></md-icon> <span *ngIf=\"!action.icon\">{{action.title}}</span> </button> </ng-container> </td> </tr> </tbody> </table> <div class=\"table-actions-wrapper\"> <span *ngIf=\"dataChanged.length\"> <button md-raised-button color=\"primary\" (click)=\"saveChanges()\">Salvar</button> <button md-raised-button color=\"warn\" (click)=\"discardChanges()\">Descartar</button> </span> <span *ngIf=\"hasMultiSelection()\"> <button md-raised-button color=\"accent\" (click)=\"copyToMultipleSelection()\">Copiar</button> <button md-raised-button color=\"accent\" (click)=\"copyToMultipleSelection(true)\">Iterar</button> </span> </div> <!-- TIPS section --> <div class=\"text-rigth\" *ngIf=\"includeTips\"> <button md-icon-button (click)=\"showTips = !showTips\" title=\"Ajuda\"> <md-icon fontSet=\"fa\" fontIcon=\"fa-question\" color=\"primary\"></md-icon> </button> <div class=\"about-data-box\" *ngIf=\"showTips\"> <hr> Como usar a tabela <ul> <li>shift + click: seleciona multiplas linhas.</li> <li>ctrl + click: seleciona multiplas linhas, uma por clique.</li> <li>shift + ctrl + click: copia um valor pra multiplas linhas.</li> <li>alt + ctrl + click: copia um valor pra multiplas linhas e adiciona +1 se o último conteúdo for numérico (dia 1, dia 2, dia 3...)</li> <li>Nenhuma alteração é salva automaticamente. Para salvar as alterações é preciso clicar em \"Salvar alterações\"</li> </ul> <hr> </div> </div> <!-- end TIPS section --> </div> </div> "
})
export class DataBoxComponent implements OnChanges{
/*
** Input config
** Used to configure the component
*/
() columns: DataBoxColumn[];
() actions: Array<any>;
() data: Array<any>; // The array of objects to be shown in the table.
() dataChange = new EventEmitter<any>() // Emits an event so subscribers with the changed data;
() title: string; // The table title
('title-size') titleSize: number; // The size of the table title h1, h2, h3, h4, h5, h6 or p
('max-rows') maxRows: number; // The max number of rows in a page. If not defined, pagination wont be done
() stripped: boolean = true; // Stripped table
() condensed: boolean = false; // Turn the lines compact
() hover: boolean = true; // Hover the line when the mouse is over
() searchable: boolean = true; // Defines if the table should be searchable or not
() selectable: boolean = true; // Defines if the table should be searchable or not
('tips') includeTips: boolean = true; // Defines if the tips button and div should be included
('counter') includeCounter: boolean = true; // Defines if the counter should be included
('auto-save') autoSave: boolean = false; // Defines if the changes should be automaticaly saved
('http-service') httpService: any; // The service used to send and get data from backend
() model: any; // The models to be used to add new items
('track-by') trackByAttr: string; // Defines the field to be used in track by function in for loop
() insert: boolean | Function = false; // Defines if the insert button should be displayed or not
('insert-position') insertPosition: 'start' | 'end' = 'start'; // Defines where a new item should be placed (start or end of a list)
// paginator
() totalItems: number;
() currentPage: number;
() amountPerPage: number;
/*
** Events config
** Used to emmit events
*/
() multipleSelection: EventEmitter<any> = new EventEmitter<any>();
() change: EventEmitter<any>;
('on-pagination') onPagination: EventEmitter<any>;
/*
** Local variables
** Used to control the component
*/
tableData: Array<any>;
filteredData: Array<any>;
selectedCells: Array<any>;
markedObjects: Array<any>;
searchStringControl: FormControl;
searchString: string;
markedObject: any;
selectedCell: any;
dataChanged: Array<any>;
noDataProvided: boolean;
saving: boolean;
loading: boolean;
sortAttr: string;
sortDirection: boolean;
/*
** Class Methods
** Private and public methods used by the component
*/
constructor(
) {
this.actions = new Array<any>();
this.columns = new Array<any>();
this.tableData = new Array<any>();
this.filteredData = new Array<any>();
this.selectedCells = new Array<any>();
this.markedObjects = new Array<any>();
this.searchStringControl = new FormControl();
this.dataChanged = new Array<any>();
this.noDataProvided = false;
this.saving = false;
this.loading = false;
this.change = new EventEmitter<any>();
this.onPagination = new EventEmitter<any>();
}
ngOnChanges(changes: any){
if(changes.data || changes.httpService){
this.loadData();
this.filterData();
}
if(changes.trackByAttr){
this.trackBy = (index: number, obj: any) => {
return obj[this.trackByAttr];
}
}
}
ngOnInit() {
this.ensureBooleanIputs();
this.initSearch();
}
pageChanged(event: any){
this.onPagination.emit({event: event.event});
}
trackBy(index: number, obj: any) {
return this.trackByAttr ? obj[this.trackByAttr] : index;
}
isCellSelected(object: any, column: string){
if(this.selectedCell){
return this.isCellAndColumnEqual(this.selectedCell, object, column) || this.isCellInSelectedCells(object, column);
} else {
return false;
}
}
hasMultiSelection(){
return this.selectedCells.length > 1 && this.selectedCells[0].column != '';
}
selectCell(event: any, object: any, column: string, editable = false){
if(editable){
if((event.shiftKey || event.ctrlKey) && this.selectedCell){
this.preventDefaultSelectionBehaviour(event);
if(event.shiftKey && event.ctrlKey){
let selectedObjects = this.getObjectsByRange(object, this.selectedCell.object);
this.copyDataFromSelectedCellToTheOthers(selectedObjects);
} else if(event.altKey && event.ctrlKey){
let selectedObjects = this.getObjectsByRange(object, this.selectedCell.object);
this.copyAndIterateDataFromSelectedCellToTheOthers(selectedObjects);
} else if(event.shiftKey){
let selectedObjects = this.getObjectsByRange(object, this.selectedCell.object);
this.selectCellsWithShiftKey(selectedObjects, this.selectedCell.column);
} else if(event.ctrlKey) {
this.selectCellsWithCtrlKey(object, this.selectedCell.column);
}
} else {
this.selectCellWithoutKey(object, column);
}
}
}
copyToMultipleSelection(iterates = false){
let selectedObjects = this.selectedCells.map(selectedCell => selectedCell.object);
if(iterates){
this.copyAndIterateDataFromSelectedCellToTheOthers(selectedObjects);
} else {
this.copyDataFromSelectedCellToTheOthers(selectedObjects);
}
}
saveChanges(){
let qtd = this.dataChanged.length;
if(qtd){
this.saving = true;
let done = 0;
this.dataChanged.forEach((object: any) => {
this.httpService.save(object).then((object: any) => {
done++;
if(done == qtd){
this.saving = false;
this.copyTableDataToData();
this.dataChange.emit(this.data);
this.dataChanged = [];
}
});
});
}
}
discardChanges(){
this.resetTableData();
}
markAllObjects = (checked: boolean) => {
this.filteredData.forEach(object => {
this.markObject(checked, object);
});
}
isAllObjectsMarked = () => {
let allMarked = true;
this.filteredData.forEach(object => {
allMarked = allMarked && (this.markedObjects.indexOf(object) >= 0);
});
return allMarked;
}
markObject(event: any, object: any){
this.markedObject = object;
if(event.checked){
this.addToMarkedObjects(object);
} else {
this.removeFromMarkedObjects(object);
}
}
markObjectByRange(event: any, object: any){
if(event.shiftKey && this.markedObject){
this.preventDefaultSelectionBehaviour(event);
let checked = this.isObjectMarked(this.markedObject);
let markedObjects = this.getObjectsByRange(object, this.markedObject);
markedObjects.forEach(object => {
if(checked){
this.addToMarkedObjects(object);
} else {
this.removeFromMarkedObjects(object);
}
});
}
}
isObjectMarked(object: any){
if(this.markedObjects){
let markedObject = this.markedObjects.find(markedObject => markedObject == object);
return markedObject ? true : false;
} else {
return false;
}
}
clearSearch(){
}
getOptionLabel(option: any, options: any[]){
let label = options.find((opt: any) => opt.id == option).label;
return label ? label : option;
}
geActionTitle = (obj: any, attr: any, parent?: any) => {
if(typeof attr === 'string'){
return attr;
} else {
return attr(obj, parent);
}
}
getAttributeValue = (obj: any, attr: any, parent?: any) => {
if(typeof attr === 'string'){
return obj !== undefined && obj[attr] !== undefined ? obj[attr] : attr;
} else {
return attr(obj, parent);
}
}
showActionButton(obj: any, action: DataBoxAction){
if(action.hide){
if(typeof action.hide === 'boolean'){
return !action.hide;
} else {
return !action.hide(obj);
}
} else {
return true;
}
}
disableOptionCell(obj: any, disable: any){
if(disable) {
if(typeof disable === 'boolean'){
return disable;
} else {
return disable(obj);
}
} else {
return false;
}
}
// SORT
sortBy(attr: any){
this.sortAttr = attr;
this.sortDirection = this.sortDirection != undefined ? !this.sortDirection : true;
}
callAction = (action: any, obj: any) => {
if(typeof action === 'string'){
action = this.getInternalAction(action);
}
let cbk = action(obj);
if(cbk){
cbk.subscribe((obj: any) => {
if(obj) this.markChanged(obj);
});
}
}
addItem(){
if(typeof this.insert === 'function'){
let cbk = this.insert();
if(cbk){
cbk.subscribe((newObject: any) => {
if(newObject){
this.addItemToTheList(newObject);
if(this.httpService){
this.markChanged(newObject);
}
}
});
}
} else {
let newObject = this.model ? new this.model() : {};
this.addItemToTheList(newObject);
this.markChanged(newObject);
}
}
private addItemToTheList = (newObject: any) => {
if(this.insertPosition == 'end'){
this.tableData.push(newObject);
} else {
this.tableData.unshift(newObject);
}
}
deleteItem(object: any){
if(this.httpService){
this.httpService.delete(object).then(
(success: any) => {
this.removeObjectFromAllLists(object);
}, (error: any) => {
console.log('Error when deleting the object', error);
}
);
} else {
this.removeObjectFromAllLists(object);
}
}
markChanged = (obj: any | Array<any>) => {
this.change.next(obj);
if(this.httpService){
let oldObj = JSON.stringify(this.getFromData(obj));
let newObj = JSON.stringify(obj);
if( newObj != oldObj){
if(typeof obj.forEach === 'function'){
obj.forEach((obj: any) => this.markChanged(obj));
} else {
let exist = false;
if(this.trackByAttr){
exist = this.dataChanged.find(_obj => _obj[this.trackByAttr] == obj[this.trackByAttr]);
} else {
exist = this.dataChanged.find(_obj => _obj == obj);
}
if(!exist){
this.dataChanged.push(obj);
}
}
} else {
this.removeFromChangedObjects(obj);
}
} else {
this.data = this.tableData;
}
}
private getInternalAction(actionName: string){
switch (actionName) {
case "delete":
return (item: any) => this.deleteItem(item);
case "add":
return (item: any) => this.addItem();
default:
return () => {};
}
}
private removeObjectFromAllLists(object: any){
this.markChanged(object);
this.removeFromChangedObjects(object);
this.removeFromMarkedObjects(object);
this.removeFromSelectedCells(object);
this.removeFromTableData(object);
}
private removeFromTableData(object: any){
let index = this.tableData.indexOf(object);
this.tableData.splice(index, 1);
}
private addToMarkedObjects(object: any){
if(!this.isObjectMarked(object)){
this.markedObjects.push(object);
}
}
private removeFromChangedObjects(object: any){
let index = this.dataChanged.indexOf(object);
this.dataChanged.splice(index, 1);
}
private removeFromMarkedObjects(object: any){
let index = this.markedObjects.indexOf(object);
this.markedObjects.splice(index, 1);
}
private ensureBooleanIputs(){
this.autoSave = typeof this.autoSave == 'string' ? this.autoSave === 'true' : false;
}
private isCellAndColumnEqual(selectedCell: any, object: any, column: string){
return (selectedCell.object == object && selectedCell.column == column);
}
private isCellInSelectedCells(object: any, column: string){
if(this.selectedCells){
let selectedCell = this.selectedCells.find(selectedCell => this.isCellAndColumnEqual(selectedCell, object, column));
return selectedCell ? true : false;
} else {
return false;
}
}
private isObjectInSelectedCells(object: any){
if(this.selectedCells){
let selectedCell = this.selectedCells.find(selectedCell => selectedCell.object == object);
return selectedCell ? true : false;
} else {
return false;
}
}
private selectCellWithoutKey(object: any, column: string){
if(this.isCellSelected(object, column) && this.selectedCells.length == 1){
this.selectedCell = undefined;
this.selectedCells = [];
} else {
this.selectedCell = {object: object, column: column};
this.selectedCells = [{object: object, column: column}];
}
}
private selectCellsWithShiftKey(selectedObjects: Array<any>, column: string){
selectedObjects.forEach((selectedObject) => {
this.addToSelectedCells(selectedObject, column);
});
}
private addToSelectedCells(object: any, column: string){
if(!this.isCellInSelectedCells(object, column)){
this.selectedCells.push({object: object, column: column});
}
}
private selectCellsWithCtrlKey(object: any, column: string){
if(this.isCellInSelectedCells(object, column)){
this.removeFromSelectedCells(object, column);
} else {
this.addToSelectedCells(object, column);
}
}
private getFromSelected(object: any, column: string){
return this.selectedCells.find(selectedCell => this.isCellAndColumnEqual(selectedCell, object, column));
}
private removeFromSelectedCells(object: any, column?: string){
if(column){
let index = this.selectedCells.indexOf(this.getFromSelected(object, column));
this.selectedCells.splice(index, 1);
} else {
let items = this.selectedCells.filter((item: any) => item.object == object);
items.forEach((item: any) => {
this.removeFromSelectedCells(object, item.column);
});
}
}
private getObjectsByRange(firstObject: any, lastObject: any){
let lastIndex = this.filteredData.indexOf(lastObject);
let startIndex = this.filteredData.indexOf(firstObject);
if(lastIndex < startIndex){
return this.filteredData.filter((object, index) => {
return index >= lastIndex && index <= startIndex;
});
} else {
return this.filteredData.filter((object, index) => {
return index <= lastIndex && index >= startIndex;
});
}
}
private getInFiltered(selectedObjects: Array<any>){
return selectedObjects.filter(selectedObject => this.filteredData.indexOf(selectedObject) >= 0);
}
private copyAndIterateDataFromSelectedCellToTheOthers(selectedObjects: Array<any>){
let attr = this.selectedCell.column;
let value = this.selectedCell.object[attr];
let numericParts = value.match(/\d+$/);
if(numericParts){
let lastIndex = this.filteredData.indexOf(selectedObjects[1]);
let startIndex = this.filteredData.indexOf(this.selectedCell.object);
let lastValuePart = parseInt(numericParts[0]);
let selectedInFiltered = this.getInFiltered(selectedObjects);
if(startIndex < lastIndex){
selectedInFiltered.forEach(object => {
object[attr] = value.replace(/\d+$([^\d]*)$/, lastValuePart+'$1');
lastValuePart++;
});
} else {
selectedInFiltered.reverse().forEach(object => {
object[attr] = value.replace(/\d+$([^\d]*)$/, lastValuePart+'$1');
lastValuePart--;
});
}
if(this.autoSave){
this.saveChanges();
} else {
this.markChanged(selectedInFiltered);
}
}
}
private copyDataFromSelectedCellToTheOthers(selectedObjects: Array<any>){
let attr = this.selectedCell.column;
let value = this.selectedCell.object[attr];
let selectedInFiltered = this.getInFiltered(selectedObjects);
selectedInFiltered.forEach(object => {
object[attr] = value;
});
if(this.autoSave){
this.saveChanges();
} else {
this.markChanged(selectedInFiltered);
}
}
private loadData(){
if(this.httpService){
this.loading = true;
this.httpService.filter().subscribe((res: any) => {
if(res){
this.data = res;
this.resetTableData();
this.loading = false;
}
});
} else if(this.data) {
this.tableData = this.data;
} else {
this.noDataProvided = true;
}
}
private resetTableData(){
this.tableData = JSON.parse(JSON.stringify(this.data));
this.filterData();
this.dataChanged = [];
this.markedObject = [];
this.selectedCells = [];
}
private copyTableDataToData(){
this.data = JSON.parse(JSON.stringify(this.tableData));
}
private initSearch(){
this.searchStringControl.valueChanges
.debounceTime(300)
.distinctUntilChanged()
.subscribe(newValue => {
this.searchString = newValue;
this.filterData();
});
}
private getFromData(obj: any){
if(this.trackByAttr){
return this.data.find(_obj => _obj[this.trackByAttr] == obj[this.trackByAttr]);
} else {
return this.data.find(_obj => _obj == obj);
}
}
private filterData(){
if(this.searchString){
this.filteredData = new FilterPipe().transform(this.tableData, this.searchString);
} else {
this.filteredData = this.tableData;
}
}
private preventDefaultSelectionBehaviour(event: any){
event.preventDefault();
window.getSelection().removeAllRanges();
}
}