@angular-generic-table/core
Version:
A generic table component for Angular
1,311 lines (1,306 loc) • 352 kB
JavaScript
import { Component, EventEmitter, Input, Output, Pipe, Renderer2, ChangeDetectorRef, ComponentFactoryResolver, Directive, ViewContainerRef, ElementRef, HostListener, HostBinding, NgModule } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import { ReplaySubject } from 'rxjs/ReplaySubject';
import { Subject } from 'rxjs/Subject';
import * as Tether from 'tether';
import { Observable } from 'rxjs/Observable';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
// unsupported: template constraints.
/**
* @template R
*/
class GtExpandedRow {
constructor() {
this.redrawEvent = new EventEmitter();
this.toggleRowEvent = new EventEmitter();
}
/**
* @return {?}
*/
$hide() {
this.toggleRowEvent.emit(this.row);
}
/**
* @param {?=} $event
* @return {?}
*/
$redraw($event) {
this.redrawEvent.emit(this.row);
}
/**
* @param {?} row
* @param {?} $event
* @return {?}
*/
$rowClick(row, $event) {
this.gtEvent.emit({
name: 'gt-row-clicked',
value: { row: row, event: $event }
});
}
}
// unsupported: template constraints.
// unsupported: template constraints.
/**
* @template R, C
*/
class GtExpandingRowComponent {
constructor() {
this.redrawEvent = new EventEmitter();
this.toggleRowEvent = new EventEmitter();
}
/**
* @param {?} instance
* @return {?}
*/
newInstance(instance) {
instance.row = this.row;
instance.columnWidth = this.columnWidth;
instance.gtSettings = this.gtSettings;
instance.gtFields = this.gtFields;
instance.gtOptions = this.gtOptions;
instance.gtInfo = this.gtInfo;
instance.data =
typeof this.data === 'function' ? this.data(this.row) : this.data;
instance.redrawEvent.subscribe(this.redrawEvent);
instance.toggleRowEvent.subscribe(this.toggleRowEvent);
instance.gtEvent = this.gtEvent;
}
}
GtExpandingRowComponent.decorators = [
{ type: Component, args: [{
selector: 'gt-expanding-row',
template: `
<div gtComponentAnchor
[ctor]="type" (instance)="newInstance($event)"></div>`
},] },
];
/** @nocollapse */
GtExpandingRowComponent.propDecorators = {
"type": [{ type: Input },],
"row": [{ type: Input },],
"columnWidth": [{ type: Input },],
"gtSettings": [{ type: Input },],
"gtFields": [{ type: Input },],
"gtOptions": [{ type: Input },],
"gtInfo": [{ type: Input },],
"data": [{ type: Input },],
"redrawEvent": [{ type: Output },],
"toggleRowEvent": [{ type: Output },],
"gtEvent": [{ type: Input },],
};
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
class GtMetaPipe {
/**
* @param {?} allRows
* @param {?=} rowIndex
* @param {?=} page
* @param {?=} recordLength
* @return {?}
*/
transform(allRows, rowIndex, page, recordLength) {
for (let /** @type {?} */ i = 0; i < allRows.length; i++) {
if (!allRows[i].$$gtRowId) {
allRows[i].$$gtRowId = rowIndex
? allRows[i][rowIndex]
: page
? page * recordLength +
i +
'_' +
Math.random()
.toString(36)
.substr(2, 16)
: i +
'_' +
Math.random()
.toString(36)
.substr(2, 16);
}
if (!allRows[i].$$gtInitialRowIndex) {
allRows[i].$$gtInitialRowIndex = i;
}
}
return allRows;
}
}
GtMetaPipe.decorators = [
{ type: Pipe, args: [{
name: 'gtMeta'
},] },
];
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
// unsupported: template constraints.
// unsupported: template constraints.
/**
* @template R, C
*/
class GenericTableComponent {
/**
* @param {?} renderer
* @param {?} gtMetaPipe
*/
constructor(renderer, gtMetaPipe) {
this.renderer = renderer;
this.gtMetaPipe = gtMetaPipe;
this.columnWidth = {};
this.sortOrder = [];
this.metaInfo = {};
this.selectedRows = [];
this.openRows = [];
this._gtSettings = [];
this._gtFields = [];
this.gtDefaultTexts = {
loading: 'Loading...',
noData: 'No data',
noMatchingData: 'No data matching results found',
noVisibleColumnsHeading: 'No visible columns',
noVisibleColumns: 'Please select at least one column to be visible.',
tableInfo: 'Showing #recordFrom to #recordTo of #recordsAfterSearch entries.',
tableInfoAfterSearch: 'Showing #recordFrom to #recordTo of #recordsAfterSearch entries (filtered from a total of #recordsAll entries).',
csvDownload: 'download',
sortLabel: 'Sort:',
paginateNext: 'Next page',
paginatePrevious: 'Previous page',
inlineEditEdited: 'Press enter to save'
};
this.gtTexts = this.gtDefaultTexts;
this.gtEvent = new EventEmitter();
this.gtDefaultOptions = {
csvDelimiter: ';',
stack: false,
lazyLoad: false,
cache: false,
debounceTime: 200,
highlightSearch: false,
rowSelection: false,
rowSelectionAllowMultiple: true,
rowExpandAllowMultiple: true,
numberOfRows: 10,
reportColumnWidth: false,
allowUnsorted: true,
mutateData: true
};
this._gtOptions = this.gtDefaultOptions;
this.store = [];
this.loading = true;
this.debounceTimer = null;
this.lazyAllSelected = false;
this.gtInfo = {
pageCurrent: 1,
pageTotal: 0,
recordFrom: 0,
recordTo: 0,
recordLength: this.gtOptions.numberOfRows,
recordsAll: 0,
recordsAfterFilter: 0,
recordsAfterSearch: 0
};
this.refreshPipe = false;
this.refreshTotals = false;
this.refreshSorting = false;
this.refreshFilter = false;
this.refreshPageArray = false;
this.editedRows = {};
this.data = { exportData: [] };
/**
* Sort table by object key.
* @param objectKey - name of key to sort on.
* @param event - such as key press during sorting.
*/
this.gtSort = function (objectKey, event) {
this.inlineEditCancel(); // cancel inline editing
// loop through current settings
for (let /** @type {?} */ i = 0; i < this._gtSettings.length; i++) {
if (this._gtSettings[i].objectKey === objectKey) {
// check if sorting is disabled...
if (this._gtSettings[i].sort &&
this._gtSettings[i].sort.indexOf('disable') !== -1) {
// ...if so, exit function without applying any sorting
return;
}
else if (/* check if sorting is undefined... */ typeof this._gtSettings[i]
.sort === 'undefined') {
// ...is so, set sorting property to enable
this._gtSettings[i].sort = 'enable';
}
}
}
// check length
const /** @type {?} */ ctrlKey = event.metaKey || event.ctrlKey;
const /** @type {?} */ sort = this.sortOrder.slice(0);
let /** @type {?} */ match = -1;
let /** @type {?} */ matchDesc = -1;
let /** @type {?} */ pos = -1;
// check if property already exits
for (let /** @type {?} */ i = 0; i < sort.length; i++) {
const /** @type {?} */ hit = sort[i].indexOf(objectKey);
if (hit !== -1) {
match = this.sortOrder.indexOf(objectKey);
matchDesc =
match === -1 ? this.sortOrder.indexOf('-' + objectKey) : match;
pos = Math.max(match, matchDesc);
}
}
// if ctrl key or meta key is press together with sort...
if (ctrlKey) {
if (this.sortOrder[this.sortOrder.length - 1] === '$$gtInitialRowIndex') {
this.sortOrder.pop();
}
switch (pos) {
// ...and property is not sorted before...
case -1:
// ...add property to sorting
this.sortOrder.push(objectKey);
break;
default:
// ...and property is sorted before...
if (match !== -1) {
// ...change from asc to desc if sorted asc
this.sortOrder[pos] = '-' + objectKey;
}
else if (this.sortOrder.length > 1) {
// ...remove sorting if sorted desc
if (ctrlKey) {
this.sortOrder[pos] = objectKey;
}
else {
this.sortOrder.splice(pos, 1);
}
}
else if (this.sortOrder.length === 1) {
// ...set sorting to asc if only sorted property
this.sortOrder[pos] = objectKey;
}
break;
}
}
else {
/* if ctrl key or meta key is not press together with sort... */
switch (pos) {
// ...and property is not sorted before...
case -1:
// ...sort by property
this.sortOrder = [objectKey];
break;
default:
// ...change from desc to asc and vise versa
this.sortOrder =
match !== -1
? ['-' + objectKey]
: ctrlKey || !this.gtOptions.allowUnsorted
? [objectKey]
: [];
break;
}
}
// update settings object with new sorting information
for (let /** @type {?} */ i = 0; i < this._gtSettings.length; i++) {
if (this._gtSettings[i].objectKey === objectKey) {
switch (this._gtSettings[i].sort) {
// if sorted asc...
case 'asc':
// ...change to desc
this._gtSettings[i].sort = 'desc';
break;
// if sorted desc...
case 'desc':
// ...change to asc if it's the only sorted property otherwise remove sorting
this._gtSettings[i].sort =
(this.sortOrder.length === 1 && sort.length < 2) ||
ctrlKey ||
!this.gtOptions.allowUnsorted
? 'asc'
: 'enable';
break;
// if sorting enabled...
case 'enable':
// ...change to asc
this._gtSettings[i].sort = 'asc';
break;
}
this._gtSettings[i].sortOrder =
this._gtSettings[i].sort === 'enable'
? this._gtSettings.length - 1
: this.sortOrder.indexOf(objectKey) === -1
? this.sortOrder.indexOf('-' + objectKey)
: this.sortOrder.indexOf(objectKey);
}
else if (this._gtSettings[i].sort &&
this._gtSettings[i].sort.indexOf('disable') === -1 &&
this.sortOrder.indexOf(this._gtSettings[i].objectKey) === -1 &&
this.sortOrder.indexOf('-' + this._gtSettings[i].objectKey) === -1) {
this._gtSettings[i].sort = 'enable';
this._gtSettings[i].sortOrder = this._gtSettings.length - 1;
}
}
// refresh sorting pipe
this.refreshSorting = !this.refreshSorting;
this.refreshPageArray = !this.refreshPageArray;
// sort by initial sort order as last resort
this.sortOrder.push('$$gtInitialRowIndex');
// emit sort event
this.gtEvent.emit({
name: 'gt-sorting-applied',
value: this.sortOrder
});
};
/**
* Change number of rows to be displayed.
* @param rowLength - total number of rows.
* @param reset - should page be reset to first page.
*/
this.changeRowLength = function (rowLength, reset) {
let /** @type {?} */ lengthValue = isNaN(parseInt(rowLength, 10))
? 0
: parseInt(rowLength, 10);
let /** @type {?} */ newPosition = 1;
if (!lengthValue && this.gtData) {
lengthValue = this.gtData.length;
}
// if reset is not true and we're not lazy loading data...
if (reset !== true && this._gtOptions.lazyLoad !== true) {
// ...get current position in record set
const /** @type {?} */ currentRecord = this.gtInfo.recordLength * (this.gtInfo.pageCurrent - 1);
const /** @type {?} */ currentPosition = this._gtData.indexOf(this._gtData[currentRecord]) + 1;
// ...get new position
newPosition = Math.ceil(currentPosition / lengthValue);
}
// change row length
this.gtInfo.recordLength = lengthValue;
// go to new position
this.gtInfo.pageCurrent = newPosition;
// if lazy loading data...
if (this._gtOptions.lazyLoad) {
// ...replace data with place holders for new data
this._gtData[0] = this.loadingContent(lengthValue);
// ...empty current store
this.store = [];
}
// this.updateRecordRange();
this.gtEvent.emit({
name: 'gt-row-length-changed',
value: lengthValue
});
};
/**
* Force a redraw of table rows.
* As the table uses pure pipes, we need to force a redraw if an object in the array is changed to see the changes.
*/
this.redraw = function ($event) {
this.refreshSorting = !this.refreshSorting;
this.refreshPageArray = !this.refreshPageArray;
this.refreshPipe = !this.refreshPipe;
};
/**
* Go to next page.
*/
this.nextPage = function () {
const /** @type {?} */ page = this.gtInfo.pageCurrent === this.gtInfo.pageTotal
? this.gtInfo.pageTotal
: this.gtInfo.pageCurrent + 1;
this.goToPage(page);
};
/**
* Go to previous page.
*/
this.previousPage = function () {
const /** @type {?} */ page = this.gtInfo.pageCurrent === 1 ? 1 : this.gtInfo.pageCurrent - 1;
this.goToPage(page);
};
/**
* Request more data (used when lazy loading)
*/
this.getData = function () {
// ...emit event requesting for more data
this.gtEvent.emit({
name: 'gt-page-changed-lazy',
value: {
pageCurrent: this.gtInfo.pageCurrent,
recordLength: this.gtInfo.recordLength
}
});
};
/**
* Go to specific page.
* @param page - page number.
*/
this.goToPage = function (page) {
const /** @type {?} */ previousPage = this.gtInfo.pageCurrent;
this.gtInfo.pageCurrent = page;
this.inlineEditCancel(); // cancel inline edit
// if lazy loading and if page contains no records...
if (this._gtOptions.lazyLoad) {
// ...if data for current page contains no entries...
if (this._gtOptions.cache === false ||
this._gtData[this.gtInfo.pageCurrent - 1].length === 0) {
// ...create temporary content while waiting for data
this._gtData[this.gtInfo.pageCurrent - 1] = this.loadingContent(this.gtInfo.recordLength);
this.loading = true; // loading true
}
// ...if first entry in current page equals our loading placeholder...
if (this._gtData[this.gtInfo.pageCurrent - 1][0][this.loadingProperty] ===
this.gtTexts.loading) {
// ...get data
clearTimeout(this.debounceTimer);
this.debounceTimer = setTimeout(() => {
this.getData();
}, this._gtOptions.debounceTime);
}
}
// this.updateRecordRange();
// ...emit page change event
if (previousPage !== page) {
this.gtEvent.emit({
name: 'gt-page-changed',
value: {
pageCurrent: this.gtInfo.pageCurrent,
pagePrevious: previousPage,
recordLength: this.gtInfo.recordLength
}
});
}
};
/**
* Sort by sort order
*/
this.getSortOrder = function (a, b) {
if (a.sortOrder < b.sortOrder) {
return -1;
}
if (a.sortOrder > b.sortOrder || typeof a.sortOrder === 'undefined') {
return 1;
}
return 0;
};
/**
* Sort by column order
*/
this.getColumnOrder = function (a, b) {
if (a.columnOrder === undefined) {
return -1;
}
if (a.columnOrder < b.columnOrder) {
return -1;
}
if (a.columnOrder > b.columnOrder) {
return 1;
}
return 0;
};
/**
* Create a deep copy of data
*/
this.cloneDeep = function (o) {
return JSON.parse(JSON.stringify(o));
};
/**
* Return property
*/
this.getProperty = function (array, key) {
for (let /** @type {?} */ i = 0; i < array.length; i++) {
if (array[i].objectKey === key) {
return array[i];
}
}
};
this.restructureSorting = function () {
/**
* Check and store sort order upon initialization.
* This is done by checking sort properties in the settings array of the table, if no sorting is defined
* we'll sort the data by the first visible and enabled column in the table(ascending). Please note that actually
* sorting have to be done server side when lazy loading data for obvious reasons.
*/
const /** @type {?} */ sorting = [];
if (this._gtSettings) {
// ...sort settings by sort order
this._gtSettings.sort(this.getSortOrder);
// ...loop through settings
for (let /** @type {?} */ i = 0; i < this._gtSettings.length; i++) {
const /** @type {?} */ setting = this._gtSettings[i];
// ...if sorted ascending...
if (setting.sort === 'asc') {
// ... add to sorting
sorting.push(setting.objectKey);
}
else if (setting.sort === 'desc') {
/* ...else if sorted descending... */
// ... add to sorting
sorting.push('-' + setting.objectKey);
}
}
// ...if no sorting applied...
if (sorting.length === 0) {
sorting.push('$$gtRowId');
/*// ...sort settings by column order
this._gtSettings.sort(this.getColumnOrder);
// ...loop through settings
for (let i = 0; i < this._gtSettings.length; i++) {
const setting = this._gtSettings[i];
// ...if column is enabled and visible...
if (setting.enabled !== false && setting.visible !== false) {
// ...add first match and exit function
this.sortOrder = [this._gtSettings[i].objectKey];
return;
}
}*/
}
}
if (this.sortOrder.length === 0) {
this.sortOrder = sorting;
}
};
/**
* Extend object function.
*/
this.extend = function (a, b) {
for (const /** @type {?} */ key in b) {
if (b.hasOwnProperty(key)) {
a[key] = b[key];
}
}
return a;
};
this.gtEvent.subscribe(($event) => {
if ($event.name === 'gt-info') {
this.updateRecordRange();
}
if ($event.name === 'gt-row-updated') {
this.updateTotals();
}
});
}
/**
* @return {?}
*/
get gtRowComponent() {
return this._gtRowComponent;
}
/**
* @return {?}
*/
get hasEdits() {
return Object.keys(this.editedRows).length > 0;
}
/**
* @return {?}
*/
get gtOptions() {
return this._gtOptions;
}
/**
* @return {?}
*/
get gtTotals() {
return this._gtTotals;
}
/**
* @return {?}
*/
get gtFields() {
return this._gtFields;
}
/**
* @return {?}
*/
get gtSettings() {
return this._gtSettings;
}
/**
* @return {?}
*/
get gtData() {
return this._gtData;
}
/**
* @param {?} value
* @return {?}
*/
set gtOptions(value) {
this._gtOptions = value;
// if number of rows is passed and if number of rows differs from current record length...
if (this.gtOptions.numberOfRows &&
this.gtInfo.recordLength !== this.gtOptions.numberOfRows) {
// ...update record length and redraw table
this.gtInfo.recordLength = this.gtOptions.numberOfRows;
this.redraw();
}
// ...extend gtOptions default values with values passed into component
this._gtOptions = /** @type {?} */ (this.extend(this.gtDefaultOptions, this._gtOptions));
}
/**
* @param {?} value
* @return {?}
*/
set gtTotals(value) {
this._gtTotals = value;
}
/**
* @param {?} value
* @return {?}
*/
set gtFields(value) {
this._gtFields = value;
const /** @type {?} */ COLUMNS_WITH_CLASS_NAMES = this._gtFields
.map(column => column)
.filter(column => column.classNames);
// TODO: remove deprecated warning when setting has been removed
if (COLUMNS_WITH_CLASS_NAMES.length > 0) {
console.warn('Field setting "classNames" have been deprecated in favor for "columnClass" and will be removed in the future, please update field settings for column with object key: ' +
COLUMNS_WITH_CLASS_NAMES[0].objectKey);
}
}
/**
* @param {?} value
* @return {?}
*/
set gtSettings(value) {
this._gtSettings = value;
// loop through current settings
for (let /** @type {?} */ i = 0; i < this._gtSettings.length; i++) {
// set sort enabled/disabled setting
this._gtSettings[i].sortEnabled =
this._gtSettings[i].sortEnabled !== false
? (this._gtSettings[i].sortEnabled = !(this._gtSettings[i].sort &&
this._gtSettings[i].sort.indexOf('disable') !== -1))
: false;
// check if sorting is undefined...
if (typeof this._gtSettings[i].sort === 'undefined') {
// ...is so, set sorting property to enable
this._gtSettings[i].sort = 'enable';
}
// check if column order is undefined...
if (typeof this._gtSettings[i].columnOrder === 'undefined' &&
this._gtSettings[i].enabled !== false) {
// ...is so, set sorting property to enable
this._gtSettings[i].columnOrder = this._gtSettings[i - 1]
? this._gtSettings[i - 1].columnOrder + 1
: 0;
}
// check if column lock settings are undefined...
if (typeof this._gtSettings[i].lockSettings === 'undefined') {
// ...if so, set lock settings to false unless field is disabled (enable === false)
this._gtSettings[i].lockSettings =
this._gtSettings[i].enabled === false || false;
}
}
this.restructureSorting();
}
/**
* @param {?} initialData
* @return {?}
*/
set gtData(initialData) {
const /** @type {?} */ data = this._gtOptions.mutateData
? [...initialData]
: this.cloneDeep(initialData);
if (this.gtOptions.lazyLoad && this.gtInfo) {
this.gtMetaPipe.transform(data, this.gtOptions.rowIndex, this.gtInfo.pageCurrent - 1, this.gtInfo.recordLength);
if (this.lazyAllSelected) {
const /** @type {?} */ UNIQUE_ROWS = this.selectedRows.map(row => row.$$gtRowId);
data.map(row => {
if (UNIQUE_ROWS.indexOf(row.$$gtRowId) === -1) {
this.selectedRows.push(row);
}
});
this._updateMetaInfo(this.selectedRows, 'isSelected', true);
}
}
else {
this.gtMetaPipe.transform(data, this.gtOptions.rowIndex);
}
if (this.gtOptions.rowSelectionInitialState) {
data.map(row => {
const /** @type {?} */ selected = typeof this.gtOptions.rowSelectionInitialState === 'function'
? this.gtOptions.rowSelectionInitialState(row)
: this.gtOptions.rowSelectionInitialState;
if (selected) {
if (typeof this.metaInfo[row.$$gtRowId] === 'undefined') {
this.metaInfo[row.$$gtRowId] = { isSelected: true };
}
else {
this.metaInfo[row.$$gtRowId].isSelected = true;
}
this.selectedRows.push(row);
}
});
}
if (this.gtOptions.rowExpandInitialState &&
this.gtOptions.rowExpandInitialComponent) {
data.map(row => {
const /** @type {?} */ expanded = typeof this.gtOptions.rowExpandInitialState === 'function'
? this.gtOptions.rowExpandInitialState(row)
: this.gtOptions.rowExpandInitialState;
this.expandedRow = this.gtOptions.rowExpandInitialComponent;
if (expanded) {
if (typeof this.metaInfo[row.$$gtRowId] === 'undefined') {
this.metaInfo[row.$$gtRowId] = { isOpen: true };
}
else {
this.metaInfo[row.$$gtRowId].isOpen = true;
}
}
});
}
this._gtData = data;
}
/**
* @param {?} value
* @return {?}
*/
set gtRowComponent(value) {
console.warn('GtRowComponent has been deprecated and support will be removed in a future release, see https://github.com/hjalmers/angular-generic-table/issues/34');
this._gtRowComponent = value;
}
/**
* Update record range.
* @return {?}
*/
updateRecordRange() {
this.gtInfo.recordFrom =
this.gtInfo.recordsAfterSearch === 0
? 0
: (this.gtInfo.pageCurrent - 1) * this.gtInfo.recordLength + 1;
this.gtInfo.recordTo =
this.gtInfo.recordsAfterSearch <
this.gtInfo.pageCurrent * this.gtInfo.recordLength
? this.gtInfo.recordsAfterSearch
: this.gtInfo.pageCurrent * this.gtInfo.recordLength;
}
/**
* Update totals.
* @return {?}
*/
updateTotals() {
this.refreshTotals = !this.refreshTotals;
}
/**
* Get meta data for row.
* @param {?} row
* @return {?}
*/
getRowState(row) {
return typeof this.metaInfo[row.$$gtRowId] === 'undefined'
? null
: this.metaInfo[row.$$gtRowId];
}
/**
* Expand all rows.
* @param {?} expandedRow - component to render when rows are expanded.
* @return {?}
*/
expandAllRows(expandedRow) {
this.expandedRow = expandedRow;
this._toggleAllRowProperty('isOpen', true);
}
/**
* Collapse all rows.
* @return {?}
*/
collapseAllRows() {
this._toggleAllRowProperty('isOpen', false);
}
/**
* Select all rows.
* @return {?}
*/
selectAllRows() {
this._toggleAllRowProperty('isSelected', true);
}
/**
* Deselect all rows.
* @return {?}
*/
deselectAllRows() {
this._toggleAllRowProperty('isSelected', false);
}
/**
* Toggle all rows.
* @return {?}
*/
toggleAllRows() {
if (this._gtOptions.lazyLoad) {
if (!this.lazyAllSelected || this.selectedRows.length === 0) {
this.selectAllRows();
this.lazyAllSelected = true;
}
else {
this.deselectAllRows();
this.lazyAllSelected = false;
}
}
else {
if (this.selectedRows.length !== this.gtData.length) {
this.selectAllRows();
}
else {
this.deselectAllRows();
}
}
}
/**
* Toggle row collapsed state ie. expanded/open or collapsed/closed.
* @param {?} row - row object that should be expanded/collapsed.
* @param {?=} expandedRow - component to render when row is expanded.
* @return {?}
*/
toggleCollapse(row, expandedRow) {
if (expandedRow) {
this.expandedRow = expandedRow;
}
this._toggleRowProperty(row, 'isOpen');
}
/**
* Toggle row selected state ie. selected or not.
* @param {?} row - row object that should be selected/deselected.
* @return {?}
*/
toggleSelect(row) {
this._toggleRowProperty(row, 'isSelected');
}
/**
* @param {?} row
* @param {?} $event
* @return {?}
*/
rowClick(row, $event) {
this.gtEvent.emit({
name: 'gt-row-clicked',
value: { row: row, event: $event }
});
}
/**
* Update row data.
* @param {?} row - row object that has been edited.
* @param {?} oldValue - row object before edit.
* @return {?}
*/
updateRow(row, oldValue) {
this._toggleRowProperty(row, 'isUpdated', oldValue);
}
/**
* removes a row from the table
* @param {?} row - the row object to remove
* @return {?}
*/
removeRow(row) {
if (this.isRowSelected(row)) {
this.toggleSelect(row);
}
const /** @type {?} */ index = this._gtData.indexOf(row);
this._gtData.splice(index, 1);
}
/**
* check if a row is selected
* @param {?} row - row object
* @return {?}
*/
isRowSelected(row) {
return (this.metaInfo[row.$$gtRowId] && this.metaInfo[row.$$gtRowId].isSelected);
}
/**
* Update meta info for all rows, ie. isSelected, isOpen.
* @param {?} array - array that holds rows that need to be updated.
* @param {?} property - name of property that should be changed/toggled.
* @param {?} active - should rows be expanded/open, selected.
* @param {?=} exception - update all rows except this one.
* @return {?}
*/
_updateMetaInfo(array, property, active, exception) {
for (let /** @type {?} */ i = 0; i < array.length; i++) {
if (!this.metaInfo[array[i].$$gtRowId]) {
this.metaInfo[array[i].$$gtRowId] = {};
}
if (exception && array[i].$$gtRowId === exception.$$gtRowId) ;
else {
this.metaInfo[array[i].$$gtRowId][property] = active;
}
}
}
/**
* Push selected/expanded lazy loaded rows to array with meta data.
* @param {?} target - array to which rows should be added.
* @param {?} source - array that holds rows that should be added.
* @return {?} array with added rows.
*/
_pushLazyRows(target, source) {
const /** @type {?} */ UNIQUE_ROWS = target.map(row => row.$$gtRowId);
for (let /** @type {?} */ i = 0; i < source.length; i++) {
// only add if not already in list
if (UNIQUE_ROWS.indexOf(source[i].$$gtRowId) === -1) {
target.push(source[i]);
}
}
return target;
}
/**
* Toggle meta info for all rows, ie. isSelected, isOpen.
* @param {?} property - name of property that should be changed/toggled.
* @param {?} active - should rows be expanded/open, selected.
* @return {?}
*/
_toggleAllRowProperty(property, active) {
let /** @type {?} */ eventName;
let /** @type {?} */ eventValue;
switch (property) {
case 'isOpen':
// check if multiple expanded rows are allowed...
if (this._gtOptions.rowExpandAllowMultiple === false) {
// ...if not, exit function
console.log('feature disabled: enable by setting "rowExpandAllowMultiple = true"');
return;
}
if (active) {
eventName = 'expand-all';
this.openRows = this._gtOptions.lazyLoad
? this._pushLazyRows(this.openRows, this._gtData[this.gtInfo.pageCurrent - 1].slice())
: this._gtData.slice();
this._updateMetaInfo(this.openRows, property, active);
}
else {
eventName = 'collapse-all';
this._updateMetaInfo(this.openRows, property, active);
this.openRows = [];
}
eventValue = {
expandedRows: this.openRows,
changedRow: 'all'
};
break;
case 'isSelected':
// check if multi row selection is allowed...
if (this._gtOptions.rowSelectionAllowMultiple === false) {
// ...if not, exit function
console.log('feature disabled: enable by setting "rowSelectionAllowMultiple = true"');
return;
}
if (active) {
eventName = 'select-all';
this.selectedRows = this._gtOptions.lazyLoad
? this._pushLazyRows(this.selectedRows, this._gtData[this.gtInfo.pageCurrent - 1].slice())
: this._gtData.slice();
this._updateMetaInfo(this.selectedRows, property, active);
}
else {
eventName = 'deselect-all';
this._updateMetaInfo(this.selectedRows, property, active);
this.selectedRows = [];
}
eventValue = {
selectedRows: this.selectedRows,
changedRow: 'all'
};
break;
}
this.gtEvent.emit({
name: 'gt-row-' + eventName,
value: eventValue
});
}
/**
* Toggle meta info for row, ie. isSelected, isOpen.
* @param {?} row - row object.
* @param {?} property - name of property that should be changed/toggled.
* @param {?=} propertyValues - optional property values that can be passed.
* @return {?}
*/
_toggleRowProperty(row, property, propertyValues) {
let /** @type {?} */ eventName;
let /** @type {?} */ eventValue;
// make sure gtRowId exists on row object
if (typeof row.$$gtRowId !== 'undefined') {
// check if meta info exists for row
if (!this.metaInfo[row.$$gtRowId]) {
// if not, add object to store meta info
this.metaInfo[row.$$gtRowId] = {};
}
switch (property) {
case 'isOpen':
const /** @type {?} */ opened = this.metaInfo[row.$$gtRowId][property];
// check if multiple expanded rows are allowed...
if (this._gtOptions.rowExpandAllowMultiple === false) {
// ...if not, collapse all rows except current row
this._updateMetaInfo(this.openRows, property, false, row);
this.openRows = [];
}
// check if row is expanded
if (!opened) {
eventName = 'expand';
// add row to expanded rows
this.openRows.push(row);
}
else {
eventName = 'collapse';
// loop through expanded rows...
for (let /** @type {?} */ i = 0; i < this.openRows.length; i++) {
// if expanded row equals passed row...
if (this.openRows[i].$$gtRowId === row.$$gtRowId) {
// ...remove row from expanded rows...
this.openRows.splice(i, 1);
// ...and exit loop
break;
}
}
}
eventValue = {
expandedRows: this.openRows,
changedRow: row
};
break;
case 'isSelected':
const /** @type {?} */ selected = this.metaInfo[row.$$gtRowId][property];
// check if multi row selection is allowed...
if (this._gtOptions.rowSelectionAllowMultiple === false) {
// ...if not, deselect all rows except current row
this._updateMetaInfo(this.selectedRows, property, false, row);
this.selectedRows = [];
}
// check if row is selected
if (!selected) {
eventName = 'select';
// add row to selected rows
this.selectedRows.push(row);
}
else {
if (this.gtOptions.lazyLoad && this.lazyAllSelected) {
this.lazyAllSelected = false;
}
eventName = 'deselect';
// loop through selected rows...
for (let /** @type {?} */ i = 0; i < this.selectedRows.length; i++) {
// if selected row equals passed row...
if (this.selectedRows[i].$$gtRowId === row.$$gtRowId) {
// ...remove row from selected rows...
this.selectedRows.splice(i, 1);
// ...and exit loop
break;
}
}
}
eventValue = {
selectedRows: this.selectedRows,
changedRow: row
};
break;
case 'isUpdated':
eventName = 'updated';
const /** @type {?} */ oldValue = propertyValues;
// check if edit object exists for row
if (typeof this.metaInfo[row.$$gtRowId][property] === 'undefined') {
this.metaInfo[row.$$gtRowId][property] = {
originalValue: oldValue,
oldValue: oldValue,
newValue: row
};
}
else {
this.metaInfo[row.$$gtRowId][property].oldValue = oldValue;
this.metaInfo[row.$$gtRowId][property].newValue = row;
}
eventValue = this.metaInfo[row.$$gtRowId][property];
this.redraw();
this.inlineEditCancel(row);
// this.gtData = [...this.gtData.map((r) => { return{...r}; })];
break;
}
this.gtEvent.emit({
name: 'gt-row-' + eventName,
value: eventValue
});
if (property !== 'isUpdated') {
this.metaInfo[row.$$gtRowId][property] = !this.metaInfo[row.$$gtRowId][property];
}
}
}
/**
* Update column.
* @param {?} $event - key up event.
* @param {?} row - row object.
* @param {?} column - column object.
* @return {?}
*/
gtUpdateColumn($event, row, column) {
this._editRow(row, column);
}
/**
* Dropdown select.
* @param {?} row - row object.
* @param {?} column - column object.
* @return {?}
*/
gtDropdownSelect(row, column) {
const /** @type {?} */ oldValue = Object.assign({}, row);
row[column.objectKey] = column.renderValue;
this.updateRow(row, oldValue);
}
/**
* @param {?} row
* @param {?} column
* @return {?}
*/
_editRow(row, column) {
const /** @type {?} */ OBJECT_KEY = column.objectKey; // declare object key which contains changes
// check if cell has changed value
column.edited = row[column.objectKey] !== column.renderValue;
// check if row contains changes...
if (!this.editedRows[row.$$gtRowId]) {
// if not, create an object for the changed row
this.editedRows[row.$$gtRowId] = {
changes: {},
// create placeholder for changes
row: row // store reference to the row that should be updated
};
}
// store changed column under changes if it has been edited
if (column.edited) {
this.editedRows[row.$$gtRowId].changes[OBJECT_KEY] = column;
}
else {
// delete change object if column is unchanged
delete this.editedRows[row.$$gtRowId].changes[OBJECT_KEY];
// check how many columns have been changed
const /** @type {?} */ CHANGED_COLUMNS = Object.keys(this.editedRows[row.$$gtRowId].changes).length;
if (CHANGED_COLUMNS === 0) {
// delete row from edited rows if no columns have been edited
delete this.editedRows[row.$$gtRowId];
}
}
// if no listener is present...
if (!this.globalInlineEditListener) {
// ...listen for update event
this._listenForKeydownEvent();
}
}
/**
* Listen for key down event - listen for key down event during inline edit.
* @return {?}
*/
_listenForKeydownEvent() {
// add global listener for key down events
this.globalInlineEditListener = this.renderer.listen('document', 'keydown', $event => {
switch ($event.key) {
case 'Enter':
// update data object
this.inlineEditUpdate();
break;
case 'Escape':
// cancel
this.inlineEditCancel();
break;
}
});
}
/**
* Inline edit update - accept changes and update row values.
* @return {?}
*/
inlineEditUpdate() {
// loop through rows that have been edited
Object.keys(this.editedRows).map(key => {
const /** @type {?} */ ROW = this.editedRows[key].row; // row to update
const /** @type {?} */ CHANGES = this.editedRows[key].changes; // changes to the row
// loop through changes in row
Object.keys(CHANGES).map(objectKey => {
const /** @type {?} */ oldValue = Object.assign({}, ROW);
ROW[objectKey] = CHANGES[objectKey].renderValue; // update data value
this.updateRow(ROW, oldValue); // update meta info for row and send event
CHANGES[objectKey].edited = false; // disable edit mode
});
});
// clear rows marked as edited as the rows have been updated
this.editedRows = {};
// remove listener
this._stopListeningForKeydownEvent();
}
/**
* Inline edit cancel - cancel and reset inline edits.
* @param {?=} row
* @return {?}
*/
inlineEditCancel(row) {
if (row) {
delete this.editedRows[row.$$gtRowId];
// remove listener
this._stopListeningForKeydownEvent();
return;
}
// loop through rows that have been edited
Object.keys(this.editedRows).map(key => {
const /** @type {?} */ ROW = this.editedRows[key].row; // row to update
const /** @type {?} */ CHANGES = this.editedRows[key].changes; // changes to the row
// loop through changes in row
Object.keys(CHANGES).map(objectKey => {
CHANGES[objectKey].renderValue = ROW[objectKey]; // reset rendered value
CHANGES[objectKey].edited = false; // disable edit mode
});
});
// clear rows marked as edited as the rows have been updated
this.editedRows = {};
// remove listener
this._stopListeningForKeydownEvent();
}
/**
* Stop listening for key down event - stop listening for key down events passed during inline edit.
* @return {?}
*/
_stopListeningForKeydownEvent() {
if (this.globalInlineEditListener) {
this.globalInlineEditListener();
this.globalInlineEditListener = null;
}
}
/**
* Apply filter(s).
* @param {?} filter - object containing key value pairs, where value should be array of values.
* @return {?}
*/
gtApplyFilter(filter) {
this.gtInfo.filter = filter;
// go to first page
this.goToPage(1);
this.updateTotals();
}
/**
* Clear/remove applied filter(s).
* @return {?}
*/
gtClearFilter() {
this.gtInfo.filter = false;
this.updateTotals();
// this.updateRecordRange();
}
/**
* Search
* @param {?} value - string containing one or more words
* @return {?}
*/
gtSearch(value) {
this.gtInfo.searchTerms = value;
// always go to first page when searching
this.goToPage(1);
this.updateTotals();
}
/**
* Add rows
* @param {?} rows - rows to add
* @return {?} new data array.
*/
gtAdd(rows) {
this.gtData = [...this.gtData, ...rows];
return [...this.gtData];
}
/**
* Delete row
* @param {?} objectKey - object key you want to find match with
* @param {?} value - the value that should be deleted
* @param {?=} match - all: delete all matches, f