UNPKG

@angular-generic-table/core

Version:
1,311 lines (1,306 loc) 352 kB
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