nts-ng2-components
Version:
Paquete de componentes para Angular2 desarrollado por NITSNETS.
493 lines (465 loc) • 14.6 kB
text/typescript
import {
AfterContentInit,
Component,
ContentChild,
ContentChildren,
EventEmitter,
Input,
OnChanges,
Output,
QueryList,
TemplateRef,
} from '@angular/core';
import { deepClone, uuid } from '../../../utils';
import { ModalService } from './../../containers/modal/modal.service';
import { NtsDatagridColumnComponent } from './column/column.component';
import { NtsDatagridDeleteComponent } from './delete/delete.component';
import { NtsDatagridRowDirective } from './row/row-variables.directive';
import { NtsFilter } from './../../../models/filter';
import { Observable } from 'rxjs/Rx';
import { ViewContainerRef } from '@angular/core';
/**
*
* @prop {string} field The key of the filed to sort the dataset
* @prop {boolean} dir True if ascendent, false if descendent
* @export
* @interface NtsDataSort
*/
export interface INtsDataSort { field: string; dir: boolean; };
export interface INtsDataRowEvent { i: number; row: Object; };
export interface INtsDataCellEvent extends INtsDataRowEvent { j: number; column: Object; cell: any; };
/**
* Table of contents with specified columns and data. Automates sorting, selection and pagination of items
*
* @author Alvaro Yuste Torregrosa
*
* @export
* @class NtsDatagridComponent
* @emits dataChange
* @emits sortChange
* @emits cellClick
* @emits rowClick
* @implements {AfterContentInit}
* @implements {OnChanges}
* @license NTS-EULA-1
* @copyright NITSNETS
*/
export class NtsDatagridComponent implements AfterContentInit, OnChanges {
/** @lends NtsDatagridComponent */
/**
* Indicates that the datagrid must execute the operations (sort, pagination, deletion...)
* locally with the data provided. Else, the datagrid just emits events to delegate the
* responsability of the operations to its parent; then just waits for the new input of data.
* @type boolean
* @default true
*/
local = true;
localSort = null;
localPage = 0;
/**
* A key-value store for the items, in format: {id1: item1, id2: item3, ...}.
* @type {object}
*/
dataSource: { [key: string]: any } = {};
/**
* An sorted list that represents how the items are rendered
* @type {string[]}
*/
dataView: string[] = [];
/**
* True if any item has a random generated id
* @type {boolean}
* @default false
*/
randomIds = false;
/**
* The field with the unique ID to manage selection and deletion
*
* @type {string}
* @default 'id'
*/
idField = 'id';
/**
* The input where de items get into the component
* @type {Array<object>}
*/
data: Array<Object> = null;
/**
* The output where changes in the items emitted
* @event NtsDatagridComponent#dataChange
* @type {Array<object>}
*/
dataChange = new EventEmitter<Array<Object>>();
/***************** SELECTING *****************/
/**
* True if the items can be selected
* @type boolean
* @default false
*/
selectable = false;
/**
* A list of the selected items ID's
* @type {string[]}
*/
selected: string[] = [];
/**
* Fired when the selected items change
* @event
* @type {string[]}
*/
selectedChange = new EventEmitter<string[]>();
/***************** DELETING *****************/
/**
* True if the items can be deleted
* @type boolean
* @default false
*/
deletable = false;
/**
* Specifies how a deletion must be confirmed
*
* @type {('inline' | 'modal')}
*/
deleteConfirm: 'inline' | 'modal' = 'inline';
/**
* Fired when the some items are deleted
* @event
* @type {string[]}
*/
delete = new EventEmitter<string[]>();
/**
* True when the items selected are trying to be deleted
*/
deletingSelection = false;
/***************** PAGING *****************/
/**
* True if the items can be divided in pages
* @type boolean
* @default false
*/
pageable = false;
/**
* The number of items per page
* @type integer
* @default 10
*/
pageSize = 10;
/**
* The current page index
* @type integer
* @default 0
*/
page = 0;
/**
* Emits an event when the user changes the page
* @event NtsDatagridComponent#pageChange
* @type {number}
*/
pageChange = new EventEmitter<number>();
/**
* The number of items in total, to manage with pagination in case that this.local is false
* @see {@link this.local}
* @type integer
*/
totalItems: number;
/**
* Returns the last page index
*
* @readonly
*/
get lastPage() {
return Math.ceil((this.local ? this.dataView.length : this.totalItems) / this.pageSize) - 1;
}
/***************** SORTING *****************/
/**
* True if the items can be sorted.
* This can be overriden at column level.
* @type boolean
* @default true
*/
sortable = true;
/**
* The current sort key
* @prop field
* @prop dir
* @see {@link NtsDataSort}
* @type {NtsDataSort}
*/
sort: INtsDataSort = { field: null, dir: false };
/**
*
* @event NtsDatagridComponent#sortChange
* @type {INtsDataSort}
*/
sortChange: EventEmitter<INtsDataSort> = new EventEmitter<INtsDataSort>();
/***************** FILTERING *****************/
/**
*
*/
filterable = false;
filter: NtsFilter;
filterFn: Function;
/***************** VISUALIZATION *****************/
/**
* Specifies if the cells must highlight on hover.
* This can be overriden at column or cell level.
* @type boolean
* @default false
*/
highlightCell = false;
/**
* Specifies if the rows must highlight on hover.
* @type boolean
* @default true
*/
highlightRow = true;
/**
* Specifies if the cells must show column labels.
* This can be overriden at column or cell level.
* @type boolean
* @default false
*/
showLabels = false;
/**
* Emits an event when a cell is clicked
*
* @event NtsDatagridComponent#cellClick
* @type {INtsDataCellEvent}
*/
cellClick: EventEmitter<INtsDataCellEvent> = new EventEmitter<INtsDataCellEvent>();
/**
* Emits an event when a row is clicked
* @event NtsDatagridComponent#rowClick
* @type {INtsDataRowEvent}
*/
rowClick: EventEmitter<INtsDataRowEvent> = new EventEmitter<INtsDataRowEvent>();
columnsComponents: QueryList<NtsDatagridColumnComponent> = null;
customRowDirective: NtsDatagridRowDirective = null;
columns: NtsDatagridColumnComponent[];
customRow: TemplateRef<Object>;
constructor(
private modalService: ModalService,
private viewContainerRef: ViewContainerRef
) { }
ngOnChanges(changes) {
if (changes.data) { this.updateData(); }
if (changes.local && !this.local) {
this.localPage = -1;
this.localSort = null;
}
}
/**
* Initialize the children elements got from the content
*/
ngAfterContentInit() {
this.columns = this.columnsComponents.toArray();
this.customRow = this.customRowDirective ? this.customRowDirective.templateRef : null;
this.sortBydefault();
}
/**
* Apply the sort based on the column.sort value
* *
* If more than one column specified: the last one will be taken
*/
sortBydefault() {
let sort = null;
this.columns.forEach(c => {
if (c.sort === 'asc' || c.sort === 'desc') {
sort = { column: c, dir: c.sort };
}
});
if (sort) { this.onSortBy(sort.column, sort.dir); }
}
/**
* @param {any} column The field selected to sort the items
*/
onSortBy(column: NtsDatagridColumnComponent, dir: boolean | 'asc' | 'desc' = null) {
if (
!column
|| column.sortable === false
|| column.sortable === undefined
&& this.sortable === false
) { return; }
if (dir !== null) {
this.sort = {
field: column.field,
dir: dir === 'desc' ? true : dir === 'asc' ? false : dir
};
} else if (this.sort.field === column.field) {
if (this.sort.dir) {
this.sort.field = null;
this.sort.dir = false;
} else {
this.sort.dir = true;
}
} else {
this.sort = {
field: column.field,
dir: false
};
}
if (this.local) {
this.localSort = this.sort.field ? ((this.sort.dir ? '+' : '-') + this.sort.field) : null;
}
if (!this.sort.field) {
this.dataView = [...this.dataView];
}
this.onPageChange(0);
this.sortChange.emit(this.sort);
}
/**
* @returns {boolean} True if all the items are selected
*/
allSelected(): boolean {
return this.selected.length === this.dataView.length;
}
/**
* Fired when the user clicks on the checkbox of the header
* Selects or unselects all the items keeping the consistency of the data stores
*
* @param {boolean} value If true: select, else: unselect
*/
onSelectAll(value: boolean) {
this.selected = value ? [...this.dataView] : [];
this.dataView.forEach(id => this.dataSource[id].selected = value);
if (!this.selected.length) {
this.deletingSelection = false;
}
}
/**
* Fired when the user clicks on a checkbox of a row
* Selects or unselects an items keeping the consistency of the data stores
*
* @param {string} id The ID o the item to select/unselect
* @param {boolean} [value=true] If true: select, else: unselect
*/
onSelectItem(id: string, value: boolean = true) {
const index = this.selected.indexOf(id);
if (value && index === -1) {
this.selected.push(id);
} else if (!value && index !== -1) {
this.selected.splice(index, 1);
}
this.dataSource[id].selected = value;
if (!this.selected.length) {
this.deletingSelection = false;
}
if (this.randomIds) {
this.selectedChange.emit(this.fillIds(this.selected));
} else { this.selectedChange.emit(this.selected); }
}
/**
* Fired when the user change the current page through the paginator
*
* @param {number} page
*/
onPageChange(page: number) {
if (this.local) {
this.localPage = page;
}
this.page = page;
this.pageChange.emit(page);
}
openDeleteModal(itemsCount: number = 1): Observable<any> {
return this.modalService.createModal(
NtsDatagridDeleteComponent,
{ hideHeader: true, itemsCount },
this.viewContainerRef
);
}
/**
* Fired when an item selection is confirmed (at the row)
* @param {string} id
*/
onDeleteItem(id: string) {
if (this.randomIds) {
this.delete.emit([this.fillIds(id)]);
} else { this.delete.emit([id]); }
if (this.local) {
this.deleteLocally([id]);
}
}
/**
* Fired when the bulk deletion is confirmed (at the header)
*/
onDeleteSelection() {
if (!this.deletable || !this.selectable || !this.selected || !this.selected.length) { return; }
if (this.randomIds) {
this.delete.emit(this.fillIds(this.selected));
} else { this.delete.emit(this.selected); }
if (this.local) {
this.deleteLocally(this.selected);
this.deletingSelection = false;
this.selected = [];
}
}
applyFilter(filter: NtsFilter) {
this.filter = filter;
this.updateData();
}
/**
* Delete items locally by a given array of IDS
*
* @private
* @param {string[]} ids
*/
private deleteLocally(ids: string[]) {
ids.forEach(id => {
this.dataView.splice(this.dataView.indexOf(id), 1);
delete this.dataSource[id];
});
this.dataView = [...this.dataView];
this.checkLocalPage();
}
private checkLocalPage() {
if (this.localPage > this.lastPage) {
this.onPageChange(this.lastPage);
}
}
/**
* From an ID or an array of IDs returns the object or the array of objects
*
* @private
* @param {(string | string[])} ids
* @returns {(any | any[])}
*/
private fillIds(ids: string | string[]): any | any[] {
if (!ids || !ids.length) { return []; }
if (Array.isArray(ids)) {
return ids.map(id => this.dataSource[id]);
} else { return this.dataSource[ids]; }
}
/**
* Fills the auxiliar data stores (dataView, dataSource and selected) from the variable 'data'.
*
* @private
*/
private updateData(data: Object[] = this.data) {
const dataSource = {};
const dataView = [];
this.randomIds = false;
if (data && data.length) {
data.filter(el => {
if (this.filterable && this.filterFn && this.filter) {
return this.filterFn(el, this.filter);
} else { return true; }
}).forEach(el => {
const id = el[this.idField] || uuid();
if (!el[this.idField]) {
el[this.idField] = id;
el['randomId'] = true;
this.randomIds = true;
}
dataView.push(el['id']);
dataSource[el['id']] = deepClone(el);
});
}
this.selected = [];
this.dataSource = dataSource;
this.dataView = dataView;
}
}