angular-slickgrid
Version:
Slickgrid components made available in Angular
2,262 lines • 123 kB
JavaScript
import { unsubscribeAll, createDomElement, SlickRowSelectionModel, SlickEventData, castObservableToPromise, addToArrayWhenNotExists, FileType, DelimiterType, EventNamingStyle, OperatorType, Filters, SlickEventHandler, SlickgridConfig as SlickgridConfig$1, BackendUtilityService, GridEventService, SharedService, CollectionService, ExtensionUtility, FilterFactory, FilterService, ResizerService, SortService, TreeDataService, PaginationService, ExtensionService, GridStateService, GridService, HeaderGroupingService, emptyElement, SlickGroupItemMetadataProvider, SlickDataView, autoAddEditorFormatterToColumnsWithEditor, SlickGrid, GridStateType, ExtensionName, isColumnDateType } from '@slickgrid-universal/common';
export * from '@slickgrid-universal/common';
import * as i0 from '@angular/core';
import { Injectable, Optional, EventEmitter, ContentChild, Input, Output, Inject, Component, NgModule } from '@angular/core';
import * as i1 from '@ngx-translate/core';
import { TranslateModule } from '@ngx-translate/core';
import { SlickRowDetailView as SlickRowDetailView$1 } from '@slickgrid-universal/row-detail-view-plugin';
import { Observable } from 'rxjs';
import { EventPubSubService } from '@slickgrid-universal/event-pub-sub';
import { SlickEmptyWarningComponent } from '@slickgrid-universal/empty-warning-component';
import { SlickFooterComponent } from '@slickgrid-universal/custom-footer-component';
import { SlickPaginationComponent } from '@slickgrid-universal/pagination-component';
import { RxJsResource } from '@slickgrid-universal/rxjs-observable';
import { extend } from '@slickgrid-universal/utils';
import { dequal } from 'dequal/lite';
import * as i5 from '@angular/common';
import { CommonModule } from '@angular/common';
class AngularUtilService {
vcr;
constructor(vcr) {
this.vcr = vcr;
}
createInteractiveAngularComponent(component, targetElement, data, createCompOptions) {
// Create a component reference from the component
const componentRef = this.vcr.createComponent(component, createCompOptions);
// user could provide data to assign to the component instance
if (componentRef?.instance && data) {
Object.assign(componentRef.instance, data);
}
// Get DOM element from component
let domElem = null;
const viewRef = componentRef.hostView;
if (viewRef && Array.isArray(viewRef.rootNodes) && viewRef.rootNodes[0]) {
domElem = viewRef.rootNodes[0];
// when user provides the DOM element target, we will move the dynamic component into that target (aka portal-ing it)
if (targetElement && domElem) {
targetElement.replaceChildren(componentRef.location.nativeElement);
}
}
return { componentRef, domElement: domElem };
}
/**
* Dynamically create an Angular component, user could also provide optional arguments for target, data & createComponent options
* @param {Component} component
* @param {HTMLElement} [targetElement]
* @param {*} [data]
* @param {CreateComponentOption} [createCompOptions]
* @returns
*/
createAngularComponent(component, targetElement, data, createCompOptions) {
// Create a component reference from the component
const componentRef = this.vcr.createComponent(component, createCompOptions);
// user could provide data to assign to the component instance
if (componentRef?.instance && data) {
Object.assign(componentRef.instance, data);
// NOTE: detectChanges() MUST be done BEFORE returning the DOM element in the next step,
// because if we do it only after returning the rootNodes (domElement) then it won't have the instance data yet
// and we would have to wait an extra cycle to see the result, this basically helps with Example22
componentRef.changeDetectorRef.detectChanges();
}
// Get DOM element from component
let domElem = null;
const viewRef = componentRef.hostView;
// get DOM element from the new dynamic Component, make sure this is read after any data and detectChanges()
if (viewRef && Array.isArray(viewRef.rootNodes) && viewRef.rootNodes[0]) {
domElem = viewRef.rootNodes[0];
// when user provides the DOM element target, we will read the new Component html and use it to replace the target html
if (targetElement && domElem) {
targetElement.innerHTML =
typeof createCompOptions?.sanitizer === 'function'
? createCompOptions.sanitizer(domElem.innerHTML || '')
: domElem.innerHTML;
}
}
return { componentRef, domElement: domElem };
}
/**
* Dynamically create an Angular component and append it to the DOM unless a target element is provided,
* user could also provide other optional arguments for data & createComponent options.
* @param {Component} component
* @param {HTMLElement} [targetElement]
* @param {*} [data]
* @param {CreateComponentOption} [createCompOptions]
* @returns
*/
createAngularComponentAppendToDom(component, targetElement, data, createCompOptions) {
const componentOutput = this.createAngularComponent(component, targetElement, data, createCompOptions);
// Append DOM element to the HTML element specified
if (targetElement?.replaceChildren) {
targetElement.replaceChildren(componentOutput.domElement);
}
else {
document.body.appendChild(componentOutput.domElement); // when no target provided, we'll simply add it to the HTML Body
}
return componentOutput;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AngularUtilService, deps: [{ token: i0.ViewContainerRef }], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AngularUtilService });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AngularUtilService, decorators: [{
type: Injectable
}], ctorParameters: () => [{ type: i0.ViewContainerRef }] });
class ContainerService {
dependencies = [];
get(key) {
const dependency = this.dependencies.find((dep) => dep.key === key);
if (dependency?.instance) {
return dependency.instance;
}
return null;
}
dispose() {
this.dependencies = [];
}
registerInstance(key, instance) {
const dependency = this.dependencies.some((dep) => dep.key === key);
if (!dependency) {
this.dependencies.push({ key, instance });
}
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ContainerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ContainerService });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ContainerService, decorators: [{
type: Injectable
}] });
/**
* This is a Translate Service Wrapper for Slickgrid-Universal monorepo lib to work properly,
* it must implement Slickgrid-Universal TranslaterService interface to work properly
*/
class TranslaterService {
translateService;
constructor(translateService) {
this.translateService = translateService;
}
/**
* Method to return the current language used by the App
* @return {string} current language
*/
getCurrentLanguage() {
return this.translateService?.currentLang ?? '';
}
/**
* Method to set the language to use in the App and Translate Service
* @param {string} language
* @return {Promise} output
*/
async use(newLang) {
return this.translateService?.use?.(newLang);
}
/**
* Method which receives a translation key and returns the translated value assigned to that key
* @param {string} translation key
* @return {string} translated value
*/
translate(translationKey) {
return this.translateService?.instant?.(translationKey || ' ');
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: TranslaterService, deps: [{ token: i1.TranslateService, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: TranslaterService });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: TranslaterService, decorators: [{
type: Injectable
}], ctorParameters: () => [{ type: i1.TranslateService, decorators: [{
type: Optional
}] }] });
/**
* Unsubscribe all Observables Subscriptions
* It will return an empty array if it all went well
* @param subscriptions
*/
function unsubscribeAllObservables(subscriptions) {
if (Array.isArray(subscriptions)) {
let subscription = subscriptions.pop();
while (subscription) {
if (typeof subscription.unsubscribe === 'function') {
subscription.unsubscribe();
}
subscription = subscriptions.pop();
}
}
// TODO: deprecated, remove the return type in next major version
return subscriptions;
}
const ROW_DETAIL_CONTAINER_PREFIX = 'container_';
const PRELOAD_CONTAINER_PREFIX = 'container_loading';
class SlickRowDetailView extends SlickRowDetailView$1 {
angularUtilService;
appRef;
eventPubSubService;
gridContainerElement;
rxjs;
rowDetailContainer;
_preloadComponent;
_preloadCompRef;
_views = [];
_viewComponent;
_subscriptions = [];
_userProcessFn;
constructor(angularUtilService, appRef, eventPubSubService, gridContainerElement, rxjs) {
super(eventPubSubService);
this.angularUtilService = angularUtilService;
this.appRef = appRef;
this.eventPubSubService = eventPubSubService;
this.gridContainerElement = gridContainerElement;
this.rxjs = rxjs;
}
get addonOptions() {
return this.getOptions();
}
get datasetIdPropName() {
return this.gridOptions.datasetIdPropertyName || 'id';
}
/** Getter for the Grid Options pulled through the Grid Object */
get gridOptions() {
return (this._grid?.getOptions() || {});
}
get rowDetailViewOptions() {
return this.gridOptions.rowDetailView;
}
addRxJsResource(rxjs) {
this.rxjs = rxjs;
}
/** Dispose of the RowDetailView Extension */
dispose() {
this.disposeAllViewComponents();
this._subscriptions = unsubscribeAll(this._subscriptions); // also unsubscribe all RxJS subscriptions
super.dispose();
}
/** Dispose of all the opened Row Detail Panels Angular View Components */
disposeAllViewComponents() {
do {
const view = this._views.pop();
if (view) {
this.disposeView(view);
}
} while (this._views.length > 0);
}
/** Get the instance of the SlickGrid addon (control or plugin). */
getAddonInstance() {
return this;
}
init(grid) {
this._grid = grid;
super.init(this._grid);
this.register(grid?.getSelectionModel());
}
/**
* Create the plugin before the Grid creation, else it will behave oddly.
* Mostly because the column definitions might change after the grid creation
*/
register(rowSelectionPlugin) {
if (typeof this.gridOptions.rowDetailView?.process === 'function') {
// we need to keep the user "process" method and replace it with our own execution method
// we do this because when we get the item detail, we need to call "onAsyncResponse.notify" for the plugin to work
this._userProcessFn = this.gridOptions.rowDetailView.process; // keep user's process method
this.addonOptions.process = (item) => this.onProcessing(item); // replace process method & run our internal one
}
else {
throw new Error('[Angular-Slickgrid] You need to provide a "process" function for the Row Detail Extension to work properly');
}
if (this._grid && this.gridOptions?.rowDetailView) {
// load the Preload & RowDetail Templates (could be straight HTML or Angular View/ViewModel)
// when those are Angular View/ViewModel, we need to create View Component & provide the html containers to the Plugin (preTemplate/postTemplate methods)
if (!this.gridOptions.rowDetailView.preTemplate) {
this._preloadComponent = this.gridOptions?.rowDetailView?.preloadComponent;
this.addonOptions.preTemplate = () => createDomElement('div', { className: `${PRELOAD_CONTAINER_PREFIX}` });
}
if (!this.gridOptions.rowDetailView.postTemplate) {
this._viewComponent = this.gridOptions?.rowDetailView?.viewComponent;
this.addonOptions.postTemplate = (itemDetail) => createDomElement('div', { className: `${ROW_DETAIL_CONTAINER_PREFIX}${itemDetail[this.datasetIdPropName]}` });
}
// this also requires the Row Selection Model to be registered as well
if (!rowSelectionPlugin || !this._grid.getSelectionModel()) {
rowSelectionPlugin = new SlickRowSelectionModel(this.gridOptions.rowSelectionOptions || { selectActiveRow: true });
this._grid.setSelectionModel(rowSelectionPlugin);
}
// hook all events
if (this._grid && this.rowDetailViewOptions) {
if (this.rowDetailViewOptions.onExtensionRegistered) {
this.rowDetailViewOptions.onExtensionRegistered(this);
}
this.eventHandler.subscribe(this.onAsyncResponse, (event, args) => {
if (this.rowDetailViewOptions && typeof this.rowDetailViewOptions.onAsyncResponse === 'function') {
this.rowDetailViewOptions.onAsyncResponse(event, args);
}
});
this.eventHandler.subscribe(this.onAsyncEndUpdate, (e, args) => {
// destroy preload if exists
this._preloadCompRef?.destroy();
// triggers after backend called "onAsyncResponse.notify()"
this.renderViewModel(args?.item);
if (this.rowDetailViewOptions && typeof this.rowDetailViewOptions.onAsyncEndUpdate === 'function') {
this.rowDetailViewOptions.onAsyncEndUpdate(e, args);
}
});
this.eventHandler.subscribe(this.onAfterRowDetailToggle, (e, args) => {
// display preload template & re-render all the other Detail Views after toggling
// the preload View will eventually go away once the data gets loaded after the "onAsyncEndUpdate" event
this.renderPreloadView();
if (this.rowDetailViewOptions && typeof this.rowDetailViewOptions.onAfterRowDetailToggle === 'function') {
this.rowDetailViewOptions.onAfterRowDetailToggle(e, args);
}
});
this.eventHandler.subscribe(this.onBeforeRowDetailToggle, (e, args) => {
// before toggling row detail, we need to create View Component if it doesn't exist
this.handleOnBeforeRowDetailToggle(e, args);
if (this.rowDetailViewOptions && typeof this.rowDetailViewOptions.onBeforeRowDetailToggle === 'function') {
return this.rowDetailViewOptions.onBeforeRowDetailToggle(e, args);
}
return true;
});
this.eventHandler.subscribe(this.onRowBackToViewportRange, (e, args) => {
// when row is back to viewport range, we will re-render the View Component(s)
this.handleOnRowBackToViewportRange(e, args);
if (this.rowDetailViewOptions && typeof this.rowDetailViewOptions.onRowBackToViewportRange === 'function') {
this.rowDetailViewOptions.onRowBackToViewportRange(e, args);
}
});
this._eventHandler.subscribe(this.onBeforeRowOutOfViewportRange, (event, args) => {
if (typeof this.rowDetailViewOptions?.onBeforeRowOutOfViewportRange === 'function') {
this.rowDetailViewOptions.onBeforeRowOutOfViewportRange(event, args);
}
this.disposeViewByItem(args.item);
});
this.eventHandler.subscribe(this.onRowOutOfViewportRange, (e, args) => {
if (this.rowDetailViewOptions && typeof this.rowDetailViewOptions.onRowOutOfViewportRange === 'function') {
this.rowDetailViewOptions.onRowOutOfViewportRange(e, args);
}
});
// --
// hook some events needed by the Plugin itself
// we need to redraw the open detail views if we change column position (column reorder)
this.eventHandler.subscribe(this._grid.onColumnsReordered, this.redrawAllViewComponents.bind(this, false));
// on row selection changed, we also need to redraw
if (this.gridOptions.enableRowSelection || this.gridOptions.enableCheckboxSelector) {
this.eventHandler.subscribe(this._grid.onSelectedRowsChanged, this.redrawAllViewComponents.bind(this, false));
}
// on sort, all row detail are collapsed so we can dispose of all the Views as well
this.eventHandler.subscribe(this._grid.onSort, this.disposeAllViewComponents.bind(this));
// redraw all Views whenever certain events are triggered
this._subscriptions.push(this.eventPubSubService?.subscribe(['onFilterChanged', 'onGridMenuColumnsChanged', 'onColumnPickerColumnsChanged'], this.redrawAllViewComponents.bind(this, false)), this.eventPubSubService?.subscribe(['onGridMenuClearAllFilters', 'onGridMenuClearAllSorting'], () => window.setTimeout(() => this.redrawAllViewComponents())));
}
}
return this;
}
/** Redraw (re-render) all the expanded row detail View Components */
redrawAllViewComponents(forceRedraw = false) {
this.resetRenderedRows();
this._views.forEach((view) => {
if (!view.rendered || forceRedraw) {
forceRedraw && view.componentRef?.destroy();
this.redrawViewComponent(view);
}
});
}
/** Redraw the necessary View Component */
redrawViewComponent(createdView) {
const containerElement = this.gridContainerElement.querySelector(`.${ROW_DETAIL_CONTAINER_PREFIX}${createdView.id}`);
if (containerElement) {
this.renderViewModel(createdView.dataContext);
}
}
/** Render (or re-render) the View Component (Row Detail) */
renderPreloadView() {
const containerElement = this.gridContainerElement.querySelector(`.${PRELOAD_CONTAINER_PREFIX}`);
if (this._preloadComponent && containerElement) {
const preloadComp = this.angularUtilService.createAngularComponentAppendToDom(this._preloadComponent, containerElement, {}, { sanitizer: this._grid.sanitizeHtmlString });
this._preloadCompRef = preloadComp.componentRef;
}
}
/** Render (or re-render) the View Component (Row Detail) */
renderViewModel(item) {
const containerElement = this.gridContainerElement.querySelector(`.${ROW_DETAIL_CONTAINER_PREFIX}${item[this.datasetIdPropName]}`);
if (this._viewComponent && containerElement) {
// render row detail
const componentOutput = this.angularUtilService.createAngularComponentAppendToDom(this._viewComponent, containerElement, {
model: item,
addon: this,
grid: this._grid,
dataView: this.dataView,
parent: this.rowDetailViewOptions?.parent,
}, {
sanitizer: this._grid.sanitizeHtmlString,
});
if (componentOutput?.componentRef) {
const viewObj = this._views.find((obj) => obj.id === item[this.datasetIdPropName]);
if (viewObj) {
viewObj.componentRef = componentOutput.componentRef;
viewObj.rendered = true;
}
return viewObj;
}
}
return undefined;
}
// --
// protected functions
// ------------------
disposeViewByItem(item, removeFromArray = false) {
const foundViewIndex = this._views.findIndex((view) => view.id === item[this.datasetIdPropName]);
if (foundViewIndex >= 0) {
this.disposeView(this._views[foundViewIndex]);
if (removeFromArray) {
this._views.splice(foundViewIndex, 1);
}
}
}
disposeView(expandedView) {
expandedView.rendered = false;
const compRef = expandedView?.componentRef;
if (compRef) {
this.appRef.detachView(compRef.hostView);
if (typeof compRef?.destroy === 'function') {
compRef.destroy();
}
return expandedView;
}
}
/**
* notify the onAsyncResponse with the "args.item" (required property)
* the plugin will then use item to populate the row detail panel with the "postTemplate"
* @param item
*/
notifyTemplate(item) {
this.onAsyncResponse.notify({ item, itemDetail: item }, new SlickEventData(), this);
}
/**
* On Processing, we will notify the plugin with the new item detail once backend server call completes
* @param item
*/
async onProcessing(item) {
if (item && typeof this._userProcessFn === 'function') {
let awaitedItemDetail;
const userProcessFn = this._userProcessFn(item);
// wait for the "userProcessFn", once resolved we will save it into the "collection"
const response = await userProcessFn;
if (this.datasetIdPropName in response) {
awaitedItemDetail = response; // from Promise
}
else if ((response && response instanceof Observable) || response instanceof Promise) {
awaitedItemDetail = await castObservableToPromise(this.rxjs, response); // from Angular-http-client
}
if (!awaitedItemDetail || !(this.datasetIdPropName in awaitedItemDetail)) {
throw new Error('[Angular-Slickgrid] could not process the Row Detail, you must make sure that your "process" callback ' +
`returns an item object that has an "${this.datasetIdPropName}" property`);
}
// notify the plugin with the new item details
this.notifyTemplate(awaitedItemDetail || {});
}
}
/**
* Just before the row get expanded or collapsed we will do the following
* First determine if the row is expanding or collapsing,
* if it's expanding we will add it to our View Components reference array if we don't already have it
* or if it's collapsing we will remove it from our View Components reference array
*/
handleOnBeforeRowDetailToggle(_e, args) {
// expanding
if (args?.item?.__collapsed) {
// expanding row detail
const viewInfo = {
id: args.item[this.datasetIdPropName],
dataContext: args.item,
rendered: false,
};
addToArrayWhenNotExists(this._views, viewInfo, this.datasetIdPropName);
}
else {
// collapsing, so dispose of the View/Component
this.disposeViewByItem(args.item, true);
}
}
/** When Row comes back to Viewport Range, we need to redraw the View */
handleOnRowBackToViewportRange(_e, args) {
const viewModel = this._views.find((x) => x.id === args.rowId);
if (viewModel && !viewModel.rendered) {
this.redrawViewComponent(viewModel);
}
}
}
/** Global Grid Options Defaults */
const GlobalGridOptions = {
alwaysShowVerticalScroll: true,
autoEdit: false,
asyncEditorLoading: false,
autoFitColumnsOnFirstLoad: true,
autoResize: {
applyResizeToContainer: true,
autoHeight: true,
autoHeightRecalcRow: 100,
calculateAvailableSizeBy: 'window',
bottomPadding: 20,
minHeight: 250,
minWidth: 300,
rightPadding: 0,
},
cellHighlightCssClass: 'slick-cell-modified',
checkboxSelector: {
cssClass: 'slick-cell-checkboxsel',
},
columnPicker: {
hideForceFitButton: false,
hideSyncResizeButton: true,
headerColumnValueExtractor: pickerHeaderColumnValueExtractor,
},
cellMenu: {
autoAdjustDrop: true,
autoAlignSide: true,
hideCloseButton: true,
hideCommandSection: false,
hideOptionSection: false,
},
contextMenu: {
autoAdjustDrop: true,
autoAlignSide: true,
hideCloseButton: true,
hideClearAllGrouping: false,
hideCollapseAllGroups: false,
hideCommandSection: false,
hideCopyCellValueCommand: false,
hideExpandAllGroups: false,
hideExportCsvCommand: false,
hideExportExcelCommand: false,
hideExportTextDelimitedCommand: true,
hideMenuOnScroll: true,
hideOptionSection: false,
iconCollapseAllGroupsCommand: 'mdi mdi-arrow-collapse',
iconExpandAllGroupsCommand: 'mdi mdi-arrow-expand',
iconClearGroupingCommand: 'mdi mdi-close',
iconCopyCellValueCommand: 'mdi mdi-content-copy',
iconExportCsvCommand: 'mdi mdi-download',
iconExportExcelCommand: 'mdi mdi-file-excel-outline',
iconExportTextDelimitedCommand: 'mdi mdi-download',
showBulletWhenIconMissing: true,
subItemChevronClass: 'mdi mdi-chevron-down mdi-rotate-270',
},
customFooterOptions: {
dateFormat: 'YYYY-MM-DD, hh:mm a',
hideRowSelectionCount: false,
hideTotalItemCount: false,
hideLastUpdateTimestamp: true,
footerHeight: 25,
leftContainerClass: 'col-xs-12 col-sm-5',
rightContainerClass: 'col-xs-6 col-sm-7',
metricSeparator: '|',
metricTexts: {
items: 'items',
itemsKey: 'ITEMS',
itemsSelected: 'items selected',
itemsSelectedKey: 'ITEMS_SELECTED',
of: 'of',
ofKey: 'OF',
},
},
dataView: {
// when enabled, this will preserve the row selection even after filtering/sorting/grouping
syncGridSelection: {
preserveHidden: false,
preserveHiddenOnSelectionChange: true,
},
syncGridSelectionWithBackendService: false, // but disable it when using backend services
},
datasetIdPropertyName: 'id',
defaultFilter: Filters.input,
defaultBackendServiceFilterTypingDebounce: 500,
defaultColumnSortFieldId: 'id',
defaultFilterPlaceholder: '🔎︎',
defaultFilterRangeOperator: OperatorType.rangeInclusive,
editable: false,
enableAutoResize: true,
enableAutoSizeColumns: true,
enableCellNavigation: false,
enableColumnPicker: true,
enableColumnReorder: true,
enableColumnResizeOnDoubleClick: true,
enableContextMenu: true,
enableExcelExport: false,
enableTextExport: false, // CSV/Text with Tab Delimited
enableFilterTrimWhiteSpace: false, // do we want to trim white spaces on all Filters?
enableGridMenu: true,
enableHeaderMenu: true,
enableEmptyDataWarningMessage: true,
emptyDataWarning: {
className: 'slick-empty-data-warning',
message: 'No data to display.',
messageKey: 'EMPTY_DATA_WARNING_MESSAGE',
hideFrozenLeftWarning: false,
hideFrozenRightWarning: false,
leftViewportMarginLeft: '40%',
rightViewportMarginLeft: '40%',
frozenLeftViewportMarginLeft: '0px',
frozenRightViewportMarginLeft: '40%',
},
enableMouseHoverHighlightRow: true,
enableSorting: true,
enableTextSelectionOnCells: true,
eventNamingStyle: EventNamingStyle.camelCase,
explicitInitialization: true,
excelExportOptions: {
addGroupIndentation: true,
exportWithFormatter: false,
filename: 'export',
format: FileType.xlsx,
groupingColumnHeaderTitle: 'Group By',
groupCollapsedSymbol: '⮞',
groupExpandedSymbol: '⮟',
groupingAggregatorRowText: '',
sanitizeDataExport: false,
},
textExportOptions: {
delimiter: DelimiterType.comma,
exportWithFormatter: false,
filename: 'export',
format: FileType.csv,
groupingColumnHeaderTitle: 'Group By',
groupingAggregatorRowText: '',
sanitizeDataExport: false,
useUtf8WithBom: true,
},
filterTypingDebounce: 0,
forceFitColumns: false,
frozenHeaderWidthCalcDifferential: 0,
gridMenu: {
dropSide: 'left',
commandLabels: {
clearAllFiltersCommandKey: 'CLEAR_ALL_FILTERS',
clearAllSortingCommandKey: 'CLEAR_ALL_SORTING',
clearFrozenColumnsCommandKey: 'CLEAR_PINNING',
exportCsvCommandKey: 'EXPORT_TO_CSV',
exportExcelCommandKey: 'EXPORT_TO_EXCEL',
exportTextDelimitedCommandKey: 'EXPORT_TO_TAB_DELIMITED',
refreshDatasetCommandKey: 'REFRESH_DATASET',
toggleDarkModeCommandKey: 'TOGGLE_DARK_MODE',
toggleFilterCommandKey: 'TOGGLE_FILTER_ROW',
togglePreHeaderCommandKey: 'TOGGLE_PRE_HEADER_ROW',
},
hideClearAllFiltersCommand: false,
hideClearAllSortingCommand: false,
hideClearFrozenColumnsCommand: true, // opt-in command
hideExportCsvCommand: false,
hideExportExcelCommand: false,
hideExportTextDelimitedCommand: true,
hideForceFitButton: false,
hideRefreshDatasetCommand: false,
hideSyncResizeButton: true,
hideToggleDarkModeCommand: true,
hideToggleFilterCommand: false,
hideTogglePreHeaderCommand: false,
iconCssClass: 'mdi mdi-menu',
iconClearAllFiltersCommand: 'mdi mdi-filter-remove-outline',
iconClearAllSortingCommand: 'mdi mdi-sort-variant-off',
iconClearFrozenColumnsCommand: 'mdi mdi-pin-off-outline',
iconExportCsvCommand: 'mdi mdi-download',
iconExportExcelCommand: 'mdi mdi-file-excel-outline',
iconExportTextDelimitedCommand: 'mdi mdi-download',
iconRefreshDatasetCommand: 'mdi mdi-sync',
iconToggleDarkModeCommand: 'mdi mdi-brightness-4',
iconToggleFilterCommand: 'mdi mdi-flip-vertical',
iconTogglePreHeaderCommand: 'mdi mdi-flip-vertical',
menuWidth: 16,
resizeOnShowHeaderRow: true,
subItemChevronClass: 'mdi mdi-chevron-down mdi-rotate-270',
headerColumnValueExtractor: pickerHeaderColumnValueExtractor,
},
headerMenu: {
autoAlign: true,
autoAlignOffset: 12,
minWidth: 140,
iconClearFilterCommand: 'mdi mdi-filter-remove-outline',
iconClearSortCommand: 'mdi mdi-sort-variant-off',
iconFreezeColumns: 'mdi mdi-pin-outline',
iconSortAscCommand: 'mdi mdi-sort-ascending',
iconSortDescCommand: 'mdi mdi-sort-descending',
iconColumnHideCommand: 'mdi mdi-close',
iconColumnResizeByContentCommand: 'mdi mdi-arrow-expand-horizontal',
hideColumnResizeByContentCommand: false,
hideColumnHideCommand: false,
hideClearFilterCommand: false,
hideClearSortCommand: false,
hideFreezeColumnsCommand: true, // opt-in command
hideSortCommands: false,
subItemChevronClass: 'mdi mdi-chevron-down mdi-rotate-270',
},
headerRowHeight: 35,
multiColumnSort: true,
numberedMultiColumnSort: true,
tristateMultiColumnSort: false,
sortColNumberInSeparateSpan: true,
suppressActiveCellChangeOnEdit: false,
pagination: {
pageSizes: [10, 15, 20, 25, 30, 40, 50, 75, 100],
pageSize: 25,
totalItems: 0,
},
// technically speaking the Row Detail requires the process & viewComponent but we'll ignore it just to set certain options
rowDetailView: {
collapseAllOnSort: true,
cssClass: 'detail-view-toggle',
panelRows: 1,
keyPrefix: '__',
useRowClick: false,
saveDetailViewOnScroll: false,
},
rowHeight: 35,
topPanelHeight: 35,
preHeaderPanelWidth: '100%', // mostly useful for Draggable Grouping dropzone to take full width
translationNamespaceSeparator: ':',
resetFilterSearchValueAfterOnBeforeCancellation: true,
resizeByContentOnlyOnFirstLoad: true,
resizeByContentOptions: {
alwaysRecalculateColumnWidth: false,
cellCharWidthInPx: 7.8,
cellPaddingWidthInPx: 14,
defaultRatioForStringType: 0.88,
formatterPaddingWidthInPx: 0,
maxItemToInspectCellContentWidth: 1000,
maxItemToInspectSingleColumnWidthByContent: 5000,
widthToRemoveFromExceededWidthReadjustment: 50,
},
treeDataOptions: {
exportIndentMarginLeft: 5,
exportIndentationLeadingChar: '͏͏͏͏͏͏͏͏͏·',
},
};
/**
* Value Extractor for both ColumnPicker & GridMenu Picker
* when using Column Header Grouping, we'll prefix the column group title
* else we'll simply return the column name title
*/
function pickerHeaderColumnValueExtractor(column, gridOptions) {
let colName = column?.columnPickerLabel ?? column?.name ?? '';
if (colName instanceof HTMLElement || colName instanceof DocumentFragment) {
colName = colName.textContent || '';
}
const headerGroup = column?.columnGroup || '';
const columnGroupSeparator = gridOptions?.columnGroupSeparator ?? ' - ';
if (headerGroup) {
return headerGroup + columnGroupSeparator + colName;
}
return colName;
}
class SlickgridConfig {
options;
constructor() {
this.options = GlobalGridOptions;
}
}
class Constants {
// English Locale texts when using only 1 Locale instead of I18N
static locales = {
TEXT_ALL_SELECTED: 'All Selected',
TEXT_ALL_X_RECORDS_SELECTED: 'All {{x}} records selected',
TEXT_APPLY_MASS_UPDATE: 'Apply Mass Update',
TEXT_APPLY_TO_SELECTION: 'Update Selection',
TEXT_CANCEL: 'Cancel',
TEXT_CLEAR_ALL_FILTERS: 'Clear all Filters',
TEXT_CLEAR_ALL_GROUPING: 'Clear all Grouping',
TEXT_CLEAR_ALL_SORTING: 'Clear all Sorting',
TEXT_CLEAR_PINNING: 'Unfreeze Columns/Rows',
TEXT_CLONE: 'Clone',
TEXT_COLLAPSE_ALL_GROUPS: 'Collapse all Groups',
TEXT_CONTAINS: 'Contains',
TEXT_COLUMNS: 'Columns',
TEXT_COLUMN_RESIZE_BY_CONTENT: 'Resize by Content',
TEXT_COMMANDS: 'Commands',
TEXT_COPY: 'Copy',
TEXT_EQUALS: 'Equals',
TEXT_EQUAL_TO: 'Equal to',
TEXT_ENDS_WITH: 'Ends With',
TEXT_ERROR_EDITABLE_GRID_REQUIRED: 'Your grid must be editable in order to use the Composite Editor Modal.',
TEXT_ERROR_ENABLE_CELL_NAVIGATION_REQUIRED: 'Composite Editor requires the flag "enableCellNavigation" to be set to True in your Grid Options.',
TEXT_ERROR_NO_CHANGES_DETECTED: 'Sorry we could not detect any changes.',
TEXT_ERROR_NO_EDITOR_FOUND: 'We could not find any Editor in your Column Definition.',
TEXT_ERROR_NO_RECORD_FOUND: 'No records selected for edit or clone operation.',
TEXT_ERROR_ROW_NOT_EDITABLE: 'Current row is not editable.',
TEXT_ERROR_ROW_SELECTION_REQUIRED: 'You must select some rows before trying to apply new value(s).',
TEXT_EXPAND_ALL_GROUPS: 'Expand all Groups',
TEXT_EXPORT_TO_CSV: 'Export in CSV format',
TEXT_EXPORT_TO_TEXT_FORMAT: 'Export in Text format (Tab delimited)',
TEXT_EXPORT_TO_EXCEL: 'Export to Excel',
TEXT_EXPORT_TO_TAB_DELIMITED: 'Export in Text format (Tab delimited)',
TEXT_FORCE_FIT_COLUMNS: 'Force fit columns',
TEXT_FREEZE_COLUMNS: 'Freeze Columns',
TEXT_GREATER_THAN: 'Greater than',
TEXT_GREATER_THAN_OR_EQUAL_TO: 'Greater than or equal to',
TEXT_GROUP_BY: 'Group By',
TEXT_HIDE_COLUMN: 'Hide Column',
TEXT_ITEMS: 'items',
TEXT_ITEMS_PER_PAGE: 'items per page',
TEXT_ITEMS_SELECTED: 'items selected',
TEXT_OF: 'of',
TEXT_OK: 'OK',
TEXT_LAST_UPDATE: 'Last Update',
TEXT_LESS_THAN: 'Less than',
TEXT_LESS_THAN_OR_EQUAL_TO: 'Less than or equal to',
TEXT_NO_ELEMENTS_FOUND: 'Aucun élément trouvé',
TEXT_NOT_CONTAINS: 'Not contains',
TEXT_NOT_EQUAL_TO: 'Not equal to',
TEXT_PAGE: 'Page',
TEXT_REFRESH_DATASET: 'Refresh Dataset',
TEXT_REMOVE_FILTER: 'Remove Filter',
TEXT_REMOVE_SORT: 'Remove Sort',
TEXT_SAVE: 'Save',
TEXT_SELECT_ALL: 'Select All',
TEXT_SYNCHRONOUS_RESIZE: 'Synchronous resize',
TEXT_SORT_ASCENDING: 'Sort Ascending',
TEXT_SORT_DESCENDING: 'Sort Descending',
TEXT_STARTS_WITH: 'Starts With',
TEXT_TOGGLE_DARK_MODE: 'Toggle Dark Mode',
TEXT_TOGGLE_FILTER_ROW: 'Toggle Filter Row',
TEXT_TOGGLE_PRE_HEADER_ROW: 'Toggle Pre-Header Row',
TEXT_X_OF_Y_SELECTED: '# of % selected',
TEXT_X_OF_Y_MASS_SELECTED: '{{x}} of {{y}} selected',
};
static treeDataProperties = {
CHILDREN_PROP: 'children',
COLLAPSED_PROP: '__collapsed',
HAS_CHILDREN_PROP: '__hasChildren',
TREE_LEVEL_PROP: '__treeLevel',
PARENT_PROP: '__parentId',
};
// some Validation default texts
static VALIDATION_REQUIRED_FIELD = 'Field is required';
static VALIDATION_EDITOR_VALID_NUMBER = 'Please enter a valid number';
static VALIDATION_EDITOR_VALID_INTEGER = 'Please enter a valid integer number';
static VALIDATION_EDITOR_INTEGER_BETWEEN = 'Please enter a valid integer number between {{minValue}} and {{maxValue}}';
static VALIDATION_EDITOR_INTEGER_MAX = 'Please enter a valid integer number that is lower than {{maxValue}}';
static VALIDATION_EDITOR_INTEGER_MAX_INCLUSIVE = 'Please enter a valid integer number that is lower than or equal to {{maxValue}}';
static VALIDATION_EDITOR_INTEGER_MIN = 'Please enter a valid integer number that is greater than {{minValue}}';
static VALIDATION_EDITOR_INTEGER_MIN_INCLUSIVE = 'Please enter a valid integer number that is greater than or equal to {{minValue}}';
static VALIDATION_EDITOR_NUMBER_BETWEEN = 'Please enter a valid number between {{minValue}} and {{maxValue}}';
static VALIDATION_EDITOR_NUMBER_MAX = 'Please enter a valid number that is lower than {{maxValue}}';
static VALIDATION_EDITOR_NUMBER_MAX_INCLUSIVE = 'Please enter a valid number that is lower than or equal to {{maxValue}}';
static VALIDATION_EDITOR_NUMBER_MIN = 'Please enter a valid number that is greater than {{minValue}}';
static VALIDATION_EDITOR_NUMBER_MIN_INCLUSIVE = 'Please enter a valid number that is greater than or equal to {{minValue}}';
static VALIDATION_EDITOR_DECIMAL_BETWEEN = 'Please enter a valid number with a maximum of {{maxDecimal}} decimals';
static VALIDATION_EDITOR_TEXT_LENGTH_BETWEEN = 'Please make sure your text length is between {{minLength}} and {{maxLength}} characters';
static VALIDATION_EDITOR_TEXT_MAX_LENGTH = 'Please make sure your text is less than {{maxLength}} characters';
static VALIDATION_EDITOR_TEXT_MAX_LENGTH_INCLUSIVE = 'Please make sure your text is less than or equal to {{maxLength}} characters';
static VALIDATION_EDITOR_TEXT_MIN_LENGTH = 'Please make sure your text is more than {{minLength}} character(s)';
static VALIDATION_EDITOR_TEXT_MIN_LENGTH_INCLUSIVE = 'Please make sure your text is at least {{minLength}} character(s)';
}
const WARN_NO_PREPARSE_DATE_SIZE = 10000; // data size to warn user when pre-parse isn't enabled
class AngularSlickgridComponent {
angularUtilService;
appRef;
cd;
containerService;
elm;
translate;
translaterService;
forRootConfig;
_dataset;
_columnDefinitions;
_currentDatasetLength = 0;
_darkMode = false;
_eventHandler = new SlickEventHandler();
_eventPubSubService;
_angularGridInstances;
_hideHeaderRowAfterPageLoad = false;
_isAutosizeColsCalled = false;
_isGridInitialized = false;
_isDatasetInitialized = false;
_isDatasetHierarchicalInitialized = false;
_isPaginationInitialized = false;
_isLocalGrid = true;
_paginationOptions;
_registeredResources = [];
_scrollEndCalled = false;
dataView;
slickGrid;
groupingDefinition = {};
groupItemMetadataProvider;
backendServiceApi;
locales;
metrics;
showPagination = false;
serviceList = [];
totalItems = 0;
paginationData;
subscriptions = [];
// components / plugins
slickEmptyWarning;
slickFooter;
slickPagination;
paginationComponent;
slickRowDetailView;
// services
backendUtilityService;
collectionService;
extensionService;
extensionUtility;
filterFactory;
filterService;
gridEventService;
gridService;
gridStateService;
headerGroupingService;
paginationService;
resizerService;
rxjs;
sharedService;
sortService;
treeDataService;
customDataView;
gridId = '';
gridOptions = {};
get paginationOptions() {
return this._paginationOptions;
}
set paginationOptions(newPaginationOptions) {
if (newPaginationOptions && this._paginationOptions) {
this._paginationOptions = { ...this.gridOptions.pagination, ...this._paginationOptions, ...newPaginationOptions };
}
else {
this._paginationOptions = newPaginationOptions;
}
this.gridOptions.pagination = this._paginationOptions ?? this.gridOptions.pagination;
this.paginationService.updateTotalItems(this.gridOptions.pagination?.totalItems ?? 0, true);
}
get columnDefinitions() {
return this._columnDefinitions;
}
set columnDefinitions(columnDefinitions) {
this._columnDefinitions = columnDefinitions;
if (this._isGridInitialized) {
this.updateColumnDefinitionsList(columnDefinitions);
}
if (columnDefinitions.length > 0) {
this.copyColumnWidthsReference(columnDefinitions);
}
}
// make the columnDefinitions a 2-way binding so that plugin adding cols
// are synched on user's side as well (RowMove, RowDetail, RowSelections)
columnDefinitionsChange = new EventEmitter(true);
get dataset() {
return (this.customDataView ? this.slickGrid?.getData?.() : this.dataView?.getItems()) || [];
}
set dataset(newDataset) {
const prevDatasetLn = this._currentDatasetLength;
const isDatasetEqual = dequal(newDataset, this._dataset || []);
let data = newDataset;
// when Tree Data is enabled and we don't yet have the hierarchical dataset filled, we can force a convert+sort of the array
if (this.slickGrid &&
this.gridOptions?.enableTreeData &&
Array.isArray(newDataset) &&
(newDataset.length > 0 || newDataset.length !== prevDatasetLn || !isDatasetEqual)) {
this._isDatasetHierarchicalInitialized = false;
data = this.sortTreeDataset(newDataset, !isDatasetEqual); // if dataset changed, then force a refresh anyway
}
this._dataset = data;
this.refreshGridData(data || []);
this._currentDatasetLength = (newDataset || []).length;
// expand/autofit columns on first page load
// we can assume that if the prevDataset was empty then we are on first load
if (this.slickGrid && this.gridOptions?.autoFitColumnsOnFirstLoad && prevDatasetLn === 0 && !this._isAutosizeColsCalled) {
this.slickGrid.autosizeColumns();
this._isAutosizeColsCalled = true;
}
this.suggestDateParsingWhenHelpful();
}
get datasetHierarchical() {
return this.sharedService.hierarchicalDataset;
}
set datasetHierarchical(newHierarchicalDataset) {
const isDatasetEqual = dequal(newHierarchicalDataset, this.sharedService?.hierarchicalDataset ?? []);
const prevFlatDatasetLn = this._currentDatasetLength;
this.sharedService.hierarchicalDataset = newHierarchicalDataset;
if (newHierarchicalDataset && this.columnDefinitions && this.filterService?.clearFilters) {
this.filterService.clearFilters();
}
// when a hierarchical dataset is set afterward, we can reset the flat dataset and call a tree data sort that will overwrite the flat dataset
if (newHierarchicalDataset && this.slickGrid && this.sortService?.processTreeDataInitialSort) {
this.sortService.processTreeDataInitialSort();
// we also need to reset/refresh the Tree Data filters because if we inserted new item(s) then it might not show up without doing this refresh
// however we need to queue our process until the flat dataset is ready, so we can queue a microtask to execute the DataView refresh only after everything is ready
queueMicrotask(() => {
const flatDatasetLn = this.dataView.getItemCount();
if (flatDatasetLn > 0 && (flatDatasetLn !== prevFlatDatasetLn || !isDatasetEqual)) {
this.filterService.refreshTreeDataFilters();
}
});
this._isDatasetHierarchicalInitialized = true;
}
}
get elementRef() {
return this.elm;
}
get backendService() {
return this.gridOptions?.backendServiceApi?.service;
}
get eventHandler() {
return this._eventHandler;
}
get gridContainerElement() {
return document.querySelector(`#${this.gridOptions.gridContainerId || ''}`);
}
/** GETTER to know if dataset was initialized or not */
get isDatasetInitialized() {
return this._isDatasetInitialized;
}
/** SETTER to change if dataset was initialized or not (stringly used for unit testing purposes) */
set isDatasetInitialized(isInitialized) {
this._isDatasetInitialized = isInitialized;
}
set isDatasetHierarchicalInitialized(isInitialized) {
this._isDatasetHierarchicalInitialized = isInitialized;
}
get registeredResources() {
return this._registeredResources;
}
slickgridHeader;
slickgridFooter;
constructor(angularUtilService, appRef, cd, containerService, elm, translate, translaterService, forRootConfig, externalServices) {
this.angularUtilService = angularUtilService;
this.appRef = appRef;
this.cd = cd;
this.containerService = containerService;
this.elm = elm;
this.translate = translate;
this.translaterService = translaterService;
this.forRootConfig = forRootConfig;
const slickgridConfig = new SlickgridConfig$1();
// initialize and assign all Service Dependencies
this._eventPubSubService = externalServices?.eventPubSubService ?? new EventPubSubService(this.elm.nativeElement);
this._eventPubSubService.eventNamingStyle = EventNamingStyle.camelCase;
this.backendUtilityService = externalServices?.backendUtilityService ?? new BackendUtilityService();
this.gridEventService = externalServices?.gridEventService ?? new GridEventService();
this.sharedService = externalServices?.sharedService ?? new SharedService();
this.collectionService = externalServices?.collectionService ?? new CollectionService(this.translaterService);
// prettier-ignore
this.extensionUtility = externalServices?.extensionUtility ?? new ExtensionUtility(this.sharedService, this.backendUtilityService, this.translaterService);
this.filterFactory = new FilterFactory(slickgridConfig, this.translaterService, this.collectionService);
// prettier-ignore
this.filterService = externalServices?.filterService ?? new FilterService(this.filterFactory, this._eventPubSubService, this.sharedService, this.backendUtilityService);
this.resizerService = externalServices?.resizerService ?? new ResizerService(this._eventPubSubService);
// prettier-ignore
this.sortService = externalServices?.sortService ?? new SortService(this.collectionService, this.sharedService, this._eventPubSubService, this.backendUtilityService);
this.treeDataService =
externalServices?.treeDataService ?? new TreeDataService(this._eventPubSubService, this.sharedService, this.sortService);
// prettier-ignore
this.paginationService = externalServices?.paginationService ?? new PaginationService(this._eventPubSubService, this.sharedService, this.backendUtilityService);
this.extensionService =
externalServices?.extensionService ??
new ExtensionService(this.extensionUtility, this.filterService, this._eventPubSubService, this.sharedService, this.sortService, this.treeDataService, this.translaterService, () => this.gridService);
// prettier-ignore
/* v8 ignore next 8 */
this.gridStateService = externalServices?.gridStateService ?? new GridStateService(this.extensionService, this.filterService, this._eventPubSubService, this.sharedService, this.sortService, this.treeDataService);
// prettier-ignore
/* v8 ignore next 9 */
this.gridService = externalServices?.gridService ?? new GridService(this.gridStateService, this.filterService, this._eventPubSubService, this.paginationService, this.sharedService, this.sortService, this.treeDataService);
this.headerGroupingService = externalServices?.headerGroupingService ?? new HeaderGroupingService(this.extensionUtility);
this.serviceList = [
this.containerService,
this.extensionService,
this.filterService,
this.gridEventService,
this.gridService,
this.gridStateService,
this.headerGroupingService,
this.paginationService,
this.resizerService,
this.sortService,
this.treeDataService,
];
// register all Service instances in the container
this.containerService.registerInstance('ExtensionUtility', this.extensionUtility);
this.containerService.registerInstance('FilterService', this.filterService);
this.containerService.registerInstance('CollectionService', this.collectionService);
this.containerService.registerInstance('ExtensionService', this.extensionService);
this.containerService.registerInstance('GridEventService', this.gridEventService);
this.containerService.registerInstance('GridService', this.gridService);
this.containerService.registerInstance('GridStateService', this.gridStateService);
this.containerService.registerInstance('HeaderGroupingService', this.headerGroupingService);
this.containerService.registerInstance('PaginationService', this.paginationService);
this.containerService.registerInstance('ResizerService', this.resizerService);
this.containerService.registerInstance('SharedService', this.sharedService);
this.containerService.registerInstance('SortService', this.sortService);
this.containerService.registerInstance('EventPubSubService', this._eventPubSubService);
this.containerService.registerInstance('PubSubService', this._eventPubSubService);
this.containerService.registerInstance('TranslaterService', this.translaterService);
this.containerService.registerInstance('TreeDataService', this.treeDataService);
}
ngAfterViewInit() {
if (!this.columnDefinitions) {
throw new Error('Using `<angular-slickgrid>` requires [columnDefinitions], it seems that you might have forgot to provide the missing bindable input.');
}
this.initialization(this._eventHandler);
this._isGridInitialized = true;
// recheck the empty warning message after grid is shown so that it works in every use case
if (this.gridOptions?.enableEmptyDataWarningMessage && Array.isArray(this.dataset)) {
const finalTotalCount = this.dataset.length;
this.displayEmptyDataWarning(finalTotalCount < 1);
}
// add dark mode CSS class when enabled
if (this.gridOptions.darkMode) {
this.setDarkMode(true);
}
this.suggestDateParsingWhenHelpful();
}
ngOnDestroy() {
this._eventPubSubService.publish('onBeforeGridDestroy', this.slickGrid);
this.destroy();
this._eventPubSubService.publish('onAfterGridDestroyed', true);
}
destroy(shouldEmptyDomElementContainer = false) {
// dispose of all Services
this.serviceList.forEach((service) => {
if (typeof service?.dispose === 'function') {
service.dispose();
}
});
this.serviceList = [];
// dispose backend service when defined and a dispose method exists
this.backendService?.dispose?.();
// dispose all registered external resources
this.disposeExternalResources();
// dispose the Components
this.slickEmptyWarning?.dispose();
this.slickFooter?.dispose();
this.slickPagination?.dispose();
if (this._eventHandler?.unsubscribeAll) {
this._eventHandler.unsubscribeAll();
}
this._eventPubSubService?.unsubscribeAll();
if (this.dataView) {
this.dataView.setItems([]);
this.dataView.destroy();
}
if (this.slickGrid?.destroy) {
this.slickGrid.destroy(shouldEmptyDomElementContainer);
}
if (this.backendServiceApi) {
for (const prop of Object.keys(this.backendServiceApi)) {
delete this.backendServiceApi[prop];
}
this.backendServiceApi = undefined;
}
if (this.columnDefinitions) {
for (const prop of Object.keys(this.columnDefinitions)) {
this.columnDefinitions[prop] = null;
}
}
for (const prop of Object.keys(this.sharedService)) {
this.sharedService[prop] = null;
}
// we could optionally also empty the content of the grid container DOM element
if (shouldEmptyDomElementContainer) {
this.emptyGridContainerElm();
}
// also unsubscribe all RxJS subscriptions
this.subscriptions = unsubscribeAll(this.subscriptions);
this._dataset = null;
this.datasetHierarchical = undefined;
this._columnDefinitions = [];
this._angularGridInstances = undefined;
this.slickGrid = undefined;
}
disposeExternalResources() {
if (Array.isArray(this._registeredResources)) {
while (this._registeredResources.length > 0) {
const res = this._registeredResources.pop();
if (res?.dispose) {
res.dispose();
}
}
}
this._registeredResources = [];
}
emptyGridContainerElm() {
const gridContainerId = this.gridOptions?.gridContainerId || 'grid1';
const gridContainerElm = document.querySelector(`#${gridContainerId}`);
emptyElement(gridContainerElm);
}
/**
* Define our internal Post Process callback, it will execute internally after we get back result from the Process backend call
* Currently ONLY available with the GraphQL Backend Service.
* The behavior is to refresh the Dataset & Pagination without requiring the user to create his own PostProcess every time
*/
createBackendApiInternalPostProcessCallback(gridOptions) {
const backendApi = gridOptions?.backendServiceApi;
if (backendApi?.service) {
const backendApiService = backendApi.service;
// internalPostProcess only works (for now) with a GraphQL Service, so make sure it is of that type
if (typeof backendApiService.getDatasetName === 'function') {
backendApi.internalPostProcess = (processResult) => {
// prettier-ignore
const datasetName = backendApi && backendApiService && typeof backendApiService.getDatasetName === 'function' ? backendApiService.getDatasetName() : '';
if (processResult?.data[datasetName]) {
const data = 'nodes' in processResult.data[datasetName]
? processResult.data[datasetName].nodes
: processResult.data[datasetName];
const totalCount = 'totalCount' in processResult.data[datasetName]
? processResult.data[datasetName].totalCount
: processResult.data[datasetName].length;
this.refreshGridData(data, totalCount || 0);
}
};
}
}
}
initialization(eventHandler) {
this.gridOptions.translater = this.translaterService;
this._eventHandler = eventHandler;
this._isAutosizeColsCalled = false;
// when detecting a frozen grid, we'll automatically enable the mousewheel scroll handler so that we can scroll from both left/right frozen containers
if (this.gridOptions &&
((this.gridOptions.frozenRow !== undefined && this.gridOptions.frozenRow >= 0) ||
(this.gridOptions.frozenColumn !== undefined && this.gridOptions.frozenColumn >= 0)) &&
this.gridOptions.enableMouseWheelScrollHandler === undefined) {
this.gridOptions.enableMouseWheelScrollHandler = true;
}
this._eventPubSubService.eventNamingStyle = this.gridOptions?.eventNamingStyle ?? EventNamingStyle.camelCase;
this._eventPubSubService.publish('onBeforeGridCreate', true);
// make sure the dataset is initialized (if not it will throw an error that it cannot getLength of null)
this._dataset ||= [];
this.gridOptions = this.mergeGridOptions(this.gridOptions);
this._paginationOptions = this.gridOptions?.pagination;
this.locales = this.gridOptions?.locales ?? Constants.locales;
this.backendServiceApi = this.gridOptions?.backendServiceApi;
this._isLocalGrid = !this.backendServiceApi; // considered a local grid if it doesn't have a backend service set
// unless specified, we'll create an internal postProcess callback (currently only available for GraphQL)
if (this.gridOptions.backendServiceApi && !this.gridOptions.backendServiceApi?.disableInternalPostProcess) {
this.createBackendApiInternalPostProcessCallback(this.gridOptions);
}
if (!this.customDataView) {
const dataviewInlineFilters = this.gridOptions?.dataView?.inlineFilters ?? false;
let dataViewOptions = { ...this.gridOptions.dataView, inlineFilters: dataviewInlineFilters };
if (this.gridOptions.draggableGrouping || this.gridOptions.enableGrouping) {
this.groupItemMetadataProvider = new SlickGroupItemMetadataProvider();
this.sharedService.groupItemMetadataProvider = this.groupItemMetadataProvider;
dataViewOptions = { ...dataViewOptions, groupItemMetadataProvider: this.groupItemMetadataProvider };
}
this.dataView = new SlickDataView(dataViewOptions, this._eventPubSubService);
this._eventPubSubService.publish('onDataviewCreated', this.dataView);
}
// get any possible Services that user want to register which don't require SlickGrid to be instantiated
// RxJS Resource is in this lot because it has to be registered before anything else and doesn't require SlickGrid to be initialized
this.preRegisterResources();
// prepare and load all SlickGrid editors, if an async editor is found then we'll also execute it.
this._columnDefinitions = this.loadSlickGridEditors(this._columnDefinitions || []);
// if the user wants to automatically add a Custom Editor Formatter, we need to call the auto add function again
if (this.gridOptions.autoAddCustomEditorFormatter) {
autoAddEditorFormatterToColumnsWithEditor(this._columnDefinitions, this.gridOptions.autoAddCustomEditorFormatter);
}
// save reference for all columns before they optionally become hidden/visible
this.sharedService.allColumns = this._columnDefinitions;
this.sharedService.visibleColumns = this._columnDefinitions;
// before certain extentions/plugins potentially adds extra columns not created by the user itself (RowMove, RowDetail, RowSelections)
// we'll subscribe to the event and push back the change to the user so they always use full column defs array including extra cols
this.subscriptions.push(this._eventPubSubService.subscribe('onPluginColumnsChanged', (data) => {
this._columnDefinitions = data.columns;
this.columnDefinitionsChange.emit(this._columnDefinitions);
}));
// after subscribing to potential columns changed, we are ready to create these optional extensions
// when we did find some to create (RowMove, RowDetail, RowSelections), it will automatically modify column definitions (by previous subscribe)
this.extensionService.createExtensionsBeforeGridCreation(this._columnDefinitions, this.gridOptions);
// if user entered some Pinning/Frozen "presets", we need to apply them in the grid options
if (this.gridOptions.presets?.pinning) {
this.gridOptions = { ...this.gridOptions, ...this.gridOptions.presets.pinning };
}
// build SlickGrid Grid, also user might optionally pass a custom dataview (e.g. remote model)
this.slickGrid = new SlickGrid(`#${this.gridId}`, this.customDataView || this.dataView, this._columnDefinitions, this.gridOptions, this._eventPubSubService);
this.sharedService.dataView = this.dataView;
this.sharedService.slickGrid = this.slickGrid;
this.sharedService.gridContainerElement = this.elm.nativeElement;
if (this.groupItemMetadataProvider) {
this.slickGrid.registerPlugin(this.groupItemMetadataProvider); // register GroupItemMetadataProvider when Grouping is enabled
}
this.extensionService.bindDifferentExtensions();
this.bindDifferentHooks(this.slickGrid, this.gridOptions, this.dataView);
// when it's a frozen grid, we need to keep the frozen column id for reference if we ever show/hide column from ColumnPicker/GridMenu afterward
const frozenColumnIndex = this.gridOptions.frozenColumn !== undefined ? this.gridOptions.frozenColumn : -1;
if (frozenColumnIndex >= 0 && frozenColumnIndex <= this._columnDefinitions.length) {
this.sharedService.frozenVisibleColumnId = this._columnDefinitions[frozenColumnIndex].id || '';
}
// get any possible Services that user want to register
this.registerResources();
// initialize the SlickGrid grid
this.slickGrid.init();
// initialized the resizer service only after SlickGrid is initialized
// if we don't we end up binding our resize to a grid element that doesn't yet exist in the DOM and the resizer service will fail silently (because it has a try/catch that unbinds the resize without throwing back)
if (this.gridContainerElement) {
this.resizerService.init(this.slickGrid, this.gridContainerElement);
}
// user could show a custom footer with the data metrics (dataset length and last updated timestamp)
if (!this.gridOptions.enablePagination &&
this.gridOptions.showCustomFooter &&
this.gridOptions.customFooterOptions &&
this.gridContainerElement) {
this.slickFooter = new SlickFooterComponent(this.slickGrid, this.gridOptions.customFooterOptions, this._eventPubSubService, this.translaterService);
this.slickFooter.renderFooter(this.gridContainerElement);
}
if (!this.customDataView && this.dataView) {
// load the data in the DataView (unless it's a hierarchical dataset, if so it will be loaded after the initial tree sort)
const initialDataset = this.gridOptions?.enableTreeData ? this.sortTreeDataset(this._dataset) : this._dataset;
this.dataView.beginUpdate();
this.dataView.setItems(initialDataset || [], this.gridOptions.datasetIdPropertyName ?? 'id');
this.dataView.endUpdate();
// if you don't want the items that are not visible (due to being filtered out or being on a different page)
// to stay selected, pass 'false' to the second arg
if (this.slickGrid?.getSelectionModel() && this.gridOptions?.dataView && 'syncGridSelection' in this.gridOptions.dataView) {
// if we are using a Backend Service, we will do an extra flag check, the reason is because it might have some unintended behaviors
// with the BackendServiceApi because technically the data in the page changes the DataView on every page change.
let preservedRowSelectionWithBackend = false;
if (this.gridOptions.backendServiceApi && 'syncGridSelectionWithBackendService' in this.gridOptions.dataView) {
preservedRowSelectionWithBackend = this.gridOptions.dataView.syncGridSelectionWithBackendService;
}
const syncGridSelection = this.gridOptions.dataView.syncGridSelection;
if (typeof syncGridSelection === 'boolean') {
let preservedRowSelection = syncGridSelection;
if (!this._isLocalGrid) {
// when using BackendServiceApi, we'll be using the "syncGridSelectionWithBackendService" flag BUT "syncGridSelection" must also be set to True
preservedRowSelection = syncGridSelection && preservedRowSelectionWithBackend;
}
this.dataView.syncGridSelection(this.slickGrid, preservedRowSelection);
}
else if (typeof syncGridSelection === 'object') {
this.dataView.syncGridSelection(this.slickGrid, syncGridSelection.preserveHidden, syncGridSelection.preserveHiddenOnSelectionChange);
}
}
const datasetLn = this.dataView.getLength() || this._dataset?.length || 0;
if (datasetLn > 0) {
if (!this._isDatasetInitialized && (this.gridOptions.enableCheckboxSelector || this.gridOptions.enableRowSelection)) {
this.loadRowSelectionPresetWhenExists();
}
this.loadFilterPresetsWhenDatasetInitialized();
this._isDatasetInitialized = true;
}
}
// user might want to hide the header row on page load but still have `enableFiltering: true`
// if that is the case, we need to hide the headerRow ONLY AFTER all filters got created & dataView exist
if (this._hideHeaderRowAfterPageLoad) {
this.showHeaderRow(false);
this.sharedService.hideHeaderRowAfterPageLoad = this._hideHeaderRowAfterPageLoad;
}
// publish & dispatch certain events
this._eventPubSubService.publish('onGridCreated', this.slickGrid);
// after the DataView is created & updated execute some processes
if (!this.customDataView) {
this.executeAfterDataviewCreated(this.slickGrid, this.gridOptions);
}
// bind resize ONLY after the dataView is ready
this.bindResizeHook(this.slickGrid, this.gridOptions);
// bind the Backend Service API callback functions only after the grid is initialized
// because the preProcess() and onInit() might get triggered
if (this.gridOptions?.backendServiceApi) {
this.bindBackendCallbackFunctions(this.gridOptions);
}
// local grid, check if we need to show the Pagination
// if so then also check if there's any presets and finally initialize the PaginationService
// a local grid with Pagination presets will potentially have a different total of items, we'll need to get it from the DataView and update our total
if (this.gridOptions?.enablePagination && this._isLocalGrid) {
this.showPagination = true;
this.loadLocalGridPagination(this.dataset);
}
this._angularGridInstances = {
// Slick Grid & DataView objects
dataView: this.dataView,
slickGrid: this.slickGrid,
extensions: this.extensionService?.extensionList,
// public methods
destroy: this.destroy.bind(this),
// return all available Services (non-singleton)
backendService: this.backendService,
eventPubSubService: this._eventPubSubService,
filterService: this.filterService,
gridEventService: this.gridEventService,
gridStateService: this.gridStateService,
gridService: this.gridService,
groupingService: this.headerGroupingService,
headerGroupingService: this.headerGroupingService,
extensionService: this.extensionService,
paginationComponent: this.slickPagination,
paginationService: this.paginationService,
resizerService: this.resizerService,
sortService: this.sortService,
treeDataService: this.treeDataService,
};
// all instances (SlickGrid, DataView & all Services)
this._eventPubSubService.publish('onAngularGridCreated', this._angularGridInstances);
}
/**
* On a Pagination changed, we will trigger a Grid State changed with the new pagination info
* Also if we use Row Selection or the Checkbox Selector with a Backend Service (Odata, GraphQL), we need to reset any selection
*/
paginationChanged(pagination) {
const isSyncGridSelectionEnabled = this.gridStateService?.needToPreserveRowSelection() ?? false;
if (this.slickGrid &&
!isSyncGridSelectionEnabled &&
this.gridOptions?.backendServiceApi &&
(this.gridOptions.enableRowSelection || this.gridOptions.enableCheckboxSelector)) {
this.slickGrid.setSelectedRows([]);
}
const { pageNumber, pageSize } = pagination;
if (this.sharedService) {
if (pageSize !== undefined && pageNumber !== undefined) {
this.sharedService.currentPagination = { pageNumber, pageSize };
}
}
this._eventPubSubService.publish('onGridStateChanged', {
change: { newValues: { pageNumber, pageSize }, type: GridStateType.pagination },
gridState: this.gridStateService.getCurrentGridState(),
});
this.cd.markForCheck();
}
/**
* When dataset changes, we need to refresh the entire grid UI & possibly resize it as well
* @param dataset
*/
refreshGridData(dataset, totalCount) {
if (this.gridOptions?.enableEmptyDataWarningMessage && Array.isArray(dataset)) {
const finalTotalCount = totalCount || dataset.length;
this.displayEmptyDataWarning(finalTotalCount < 1);
}
if (Array.isArray(dataset) && this.slickGrid && this.dataView?.setItems) {
this.dataView.setItems(dataset, this.gridOptions.datasetIdPropertyName ?? 'id');
if (!this.gridOptions.backendServiceApi && !this.gridOptions.enableTreeData) {
this.dataView.reSort();
}
if (dataset.length > 0) {
if (!this._isDatasetInitialized) {
this.loadFilterPresetsWhenDatasetInitialized();
if (this.gridOptions.enableCheckboxSelector) {
this.loadRowSelectionPresetWhenExists();
}
}
this._isDatasetInitialized = true;
}
if (dataset) {
this.slickGrid.invalidate();
}
// display the Pagination component only after calling this refresh data first, we call it here so that if we preset pagination page number it will be shown correctly
this.showPagination = !!(this.gridOptions &&
(this.gridOptions.enablePagination ||
(this.gridOptions.backendServiceApi && this.gridOptions.enablePagination === undefined)));
if (this._paginationOptions && this.gridOptions?.pagination && this.gridOptions?.backendServiceApi) {
const paginationOptions = this.setPaginationOptionsWhenPresetDefined(this.gridOptions, this._paginationOptions);
// when we have a totalCount use it, else we'll take it from the pagination object
// only update the total items if it's different to avoid refreshing the UI
const totalRecords = totalCount !== undefined ? totalCount : this.gridOptions?.pagination?.totalItems;
if (totalRecords !== undefined && totalRecords !== this.totalItems) {
this.totalItems = +totalRecords;
}
// initialize the Pagination Service with new pagination options (which might have presets)
if (!this._isPaginationInitialized) {
this.initializePaginationService(paginationOptions);
}
else {
// update the pagination service with the new total
this.paginationService.updateTotalItems(this.totalItems);
}
}
// resize the grid inside a slight timeout, in case other DOM element changed prior to the resize (like a filter/pagination changed)
if (this.slickGrid && this.gridOptions.enableAutoResize) {
const delay = this.gridOptions.autoResize && this.gridOptions.autoResize.delay;
this.resizerService.resizeGrid(delay || 10);
}
}
}
setData(data, shouldAutosizeColumns = false) {
if (shouldAutosizeColumns) {
this._isAutosizeColsCalled = false;
this._currentDatasetLength = 0;
}
this.dataset = data || [];
}
/**
* Check if there's any Pagination Presets defined in the Grid Options,
* if there are then load them in the paginationOptions object
*/
setPaginationOptionsWhenPresetDefined(gridOptions, paginationOptions) {
if (gridOptions.presets?.pagination && paginationOptions && !this._isPaginationInitialized) {
if (this.hasBackendInfiniteScroll()) {
console.warn('[Angular-Slickgrid] `presets.pagination` is not supported with Infinite Scroll, reverting to first page.');
}
else {
paginationOptions.pageSize = gridOptions.presets.pagination.pageSize;
paginationOptions.pageNumber = gridOptions.presets.pagination.pageNumber;
}
}
return paginationOptions;
}
setDarkMode(dark = false) {
if (dark) {
this.sharedService.gridContainerElement?.classList.add('slick-dark-mode');
}
else {
this.sharedService.gridContainerElement?.classList.remove('slick-dark-mode');
}
}
/**
* Dynamically change or update the column definitions list.
* We will re-render the grid so that the new header and data shows up correctly.
* If using i18n, we also need to trigger a re-translate of the column headers
*/
updateColumnDefinitionsList(newColumnDefinitions) {
// map the Editor model to editorClass and load editor collectionAsync
newColumnDefinitions = this.loadSlickGridEditors(newColumnDefinitions);
if (this.gridOptions.enableTranslate) {
this.extensionService.translateColumnHeaders(undefined, newColumnDefinitions);
}
else {
this.extensionService.renderColumnHeaders(newColumnDefinitions, true);
}
if (this.gridOptions?.enableAutoSizeColumns) {
this.slickGrid.autosizeColumns();
}
else if (this.gridOptions?.enableAutoResizeColumnsByCellContent && this.resizerService?.resizeColumnsByCellContent) {
this.resizerService.resizeColumnsByCellContent();
}
}
/**
* Show the filter row displayed on first row, we can optionally pass false to hide it.
* @param showing
*/
showHeaderRow(showing = true) {
this.slickGrid.setHeaderRowVisibility(showing);
if (showing === true && this._isGridInitialized) {
this.slickGrid.setColumns(this.columnDefinitions);
}
return showing;
}
/**
* Toggle the empty data warning message visibility.
* @param showWarning
*/
displayEmptyDataWarning(showWarning = true) {
this.slickEmptyWarning?.showEmptyDataMessage(showWarning);
}
//
// protected functions
// ------------------
/**
* Loop through all column definitions and copy the original optional `width` properties optionally provided by the user.
* We will use this when doing a resize by cell content, if user provided a `width` it won't override it.
*/
copyColumnWidthsReference(columnDefinitions) {
columnDefinitions.forEach((col) => (col.originalWidth = col.width));
}
bindDifferentHooks(grid, gridOptions, dataView) {
// on locale change, we have to manually translate the Headers, GridMenu
if (this.translate?.onLangChange) {
// translate some of them on first load, then on each language change
if (gridOptions.enableTranslate) {
this.extensionService.translateAllExtensions();
}
this.subscriptions.push(this.translate.onLangChange.subscribe(({ lang }) => {
// publish event of the same name that Slickgrid-Universal uses on a language change event
this._eventPubSubService.publish('onLanguageChange');
if (gridOptions.enableTranslate) {
this.extensionService.translateAllExtensions(lang);
if ((gridOptions.createPreHeaderPanel && gridOptions.createTopHeaderPanel) ||
(gridOptions.createPreHeaderPanel && !gridOptions.enableDraggableGrouping)) {
this.headerGroupingService.translateHeaderGrouping();
}
}
}));
}
// if user set an onInit Backend, we'll run it right away (and if so, we also need to run preProcess, internalPostProcess & postProcess)
if (gridOptions.backendServiceApi) {
const backendApi = gridOptions.backendServiceApi;
if (backendApi?.service?.init) {
backendApi.service.init(backendApi.options, gridOptions.pagination, this.slickGrid, this.sharedService);
}
}
if (dataView && grid) {
// on cell click, mainly used with the columnDef.action callback
this.gridEventService.bindOnCellChange(grid);
this.gridEventService.bindOnClick(grid);
// bind external sorting (backend) when available or default onSort (dataView)
if (gridOptions.enableSorting) {
// bind external sorting (backend) unless specified to use the local one
if (gridOptions.backendServiceApi && !gridOptions.backendServiceApi.useLocalSorting) {
this.sortService.bindBackendOnSort(grid);
}
else {
this.sortService.bindLocalOnSort(grid);
}
}
// bind external filter (backend) when available or default onFilter (dataView)
if (gridOptions.enableFiltering) {
this.filterService.init(grid);
// bind external filter (backend) unless specified to use the local one
if (gridOptions.backendServiceApi && !gridOptions.backendServiceApi.useLocalFiltering) {
this.filterService.bindBackendOnFilter(grid);
}
else {
this.filterService.bindLocalOnFilter(grid);
}
}
// when column are reordered, we need to update the visibleColumn array
this._eventHandler.subscribe(grid.onColumnsReordered, (_e, args) => {
this.sharedService.hasColumnsReordered = true;
this.sharedService.visibleColumns = args.impactedColumns;
});
this._eventHandler.subscribe(grid.onSetOptions, (_e, args) => {
// add/remove dark mode CSS class when enabled
if (args.optionsBefore.darkMode !== args.optionsAfter.darkMode && this.gridContainerElement) {
this.setDarkMode(args.optionsAfter.darkMode);
}
});
// load any presets if any (after dataset is initialized)
this.loadColumnPresetsWhenDatasetInitialized();
this.loadFilterPresetsWhenDatasetInitialized();
// When data changes in the DataView, we need to refresh the metrics and/or display a warning if the dataset is empty
this._eventHandler.subscribe(dataView.onRowCountChanged, (_e, args) => {
if (!gridOptions.enableRowDetailView || !Array.isArray(args.changedRows) || args.changedRows.length === args.itemCount) {
grid.invalidate();
}
else {
grid.invalidateRows(args.changedRows);
grid.render();
}
this.handleOnItemCountChanged(dataView.getFilteredItemCount() || 0, dataView.getItemCount() || 0);
});
this._eventHandler.subscribe(dataView.onSetItemsCalled, (_e, args) => {
this.sharedService.isItemsDateParsed = false;
this.handleOnItemCountChanged(dataView.getFilteredItemCount() || 0, args.itemCount);
// when user has resize by content enabled, we'll force a full width calculation since we change our entire dataset
if (args.itemCount > 0 &&
(this.gridOptions.autosizeColumnsByCellContentOnFirstLoad || this.gridOptions.enableAutoResizeColumnsByCellContent)) {
this.resizerService.resizeColumnsByCellContent(!this.gridOptions?.resizeByContentOnlyOnFirstLoad);
}
});
if (gridOptions?.enableFiltering && !gridOptions.enableRowDetailView) {
this._eventHandler.subscribe(dataView.onRowsChanged, (_e, { calledOnRowCountChanged, rows }) => {
// filtering data with local dataset will not always show correctly unless we call this updateRow/render
// also don't use "invalidateRows" since it destroys the entire row and as bad user experience when updating a row
// see commit: https://github.com/ghiscoding/aurelia-slickgrid/commit/8c503a4d45fba11cbd8d8cc467fae8d177cc4f60
if (!calledOnRowCountChanged && Array.isArray(rows)) {
const ranges = grid.getRenderedRange();
rows.filter((row) => row >= ranges.top && row <= ranges.bottom).forEach((row) => grid.updateRow(row));
grid.render();
}
});
}
}
// @deprecated @user `dataview.globalItemMetadataProvider.getRowMetadata`
// did the user add a colspan callback? If so, hook it into the DataView getItemMetadata
if (gridOptions?.colspanCallback && dataView && dataView.getItem && dataView.getItemMetadata) {
dataView.getItemMetadata = (rowNumber) => {
let callbackResult = null;
if (gridOptions.colspanCallback) {
callbackResult = gridOptions.colspanCallback(dataView.getItem(rowNumber));
}
return callbackResult;
};
}
}
bindBackendCallbackFunctions(gridOptions) {
const backendApi = gridOptions.backendServiceApi;
const backendApiService = backendApi?.service;
const serviceOptions = backendApiService?.options ?? {};
// prettier-ignore
const isExecuteCommandOnInit = (!serviceOptions) ? false : ((serviceOptions && 'executeProcessCommandOnInit' in serviceOptions) ? serviceOptions['executeProcessCommandOnInit'] : true);
if (backendApiService) {
// update backend filters (if need be) BEFORE the query runs (via the onInit command a few lines below)
// if user entered some any "presets", we need to reflect them all in the grid
if (gridOptions?.presets) {
// Filters "presets"
if (backendApiService.updateFilters &&
Array.isArray(gridOptions.presets.filters) &&
gridOptions.presets.filters.length > 0) {
backendApiService.updateFilters(gridOptions.presets.filters, true);
}
// Sorters "presets"
if (backendApiService.updateSorters &&
Array.isArray(gridOptions.presets.sorters) &&
gridOptions.presets.sorters.length > 0) {
// when using multi-column sort, we can have multiple but on single sort then only grab the first sort provided
const sortColumns = this.gridOptions.multiColumnSort
? gridOptions.presets.sorters
: gridOptions.presets.sorters.slice(0, 1);
backendApiService.updateSorters(undefined, sortColumns);
}
// Pagination "presets"
if (backendApiService.updatePagination && gridOptions.presets.pagination && !this.hasBackendInfiniteScroll()) {
const { pageNumber, pageSize } = gridOptions.presets.pagination;
backendApiService.updatePagination(pageNumber, pageSize);
}
}
else {
const columnFilters = this.filterService.getColumnFilters();
if (columnFilters && backendApiService.updateFilters) {
backendApiService.updateFilters(columnFilters, false);
}
}
// execute onInit command when necessary
if (backendApi && backendApiService && (backendApi.onInit || isExecuteCommandOnInit)) {
const query = typeof backendApiService.buildQuery === 'function' ? backendApiService.buildQuery() : '';
// prettier-ignore
const process = (isExecuteCommandOnInit) ? (backendApi.process && backendApi.process(query) || null) : (backendApi.onInit && backendApi.onInit(query) || null);
// wrap this inside a microtask to be executed at the end of the task and avoid timing issue since the gridOptions needs to be ready before running this onInit
queueMicrotask(() => {
const backendUtilityService = this.backendUtilityService;
// keep start time & end timestamps & return it after process execution
const startTime = new Date();
// run any pre-process, if defined, for example a spinner
if (backendApi.preProcess) {
backendApi.preProcess();
}
// the processes can be a Promise (like Http)
const totalItems = this.gridOptions?.pagination?.totalItems ?? 0;
if (process instanceof Promise) {
process
.then((processResult) => backendUtilityService.executeBackendProcessesCallback(startTime, processResult, backendApi, totalItems))
.catch((error) => backendUtilityService.onBackendError(error, backendApi));
}
else if (process && this.rxjs?.isObservable(process)) {
this.subscriptions.push(process.subscribe({
next: (processResult) => backendUtilityService.executeBackendProcessesCallback(startTime, processResult, backendApi, totalItems),
error: (error) => backendUtilityService.onBackendError(error, backendApi),
}));
}
});
}
// when user enables Infinite Scroll
if (backendApi.service.options?.infiniteScroll) {
this.addBackendInfiniteScrollCallback();
}
}
}
addBackendInfiniteScrollCallback() {
if (this.slickGrid &&
this.gridOptions.backendServiceApi &&
this.hasBackendInfiniteScroll() &&
!this.gridOptions.backendServiceApi?.onScrollEnd) {
const onScrollEnd = () => {
this.backendUtilityService.setInfiniteScrollBottomHit(true);
// even if we're not showing pagination, we still use pagination service behind the scene
// to keep track of the scroll position and fetch next set of data (aka next page)
// we also need a flag to know if we reached the of the dataset or not (no more pages)
this.paginationService.goToNextPage().then((hasNext) => {
if (!hasNext) {
this.backendUtilityService.setInfiniteScrollBottomHit(false);
}
});
};
this.gridOptions.backendServiceApi.onScrollEnd = onScrollEnd;
// subscribe to SlickGrid onScroll to determine when reaching the end of the scroll bottom position
// run onScrollEnd() method when that happens
this._eventHandler.subscribe(this.slickGrid.onScroll, (_e, args) => {
const viewportElm = args.grid.getViewportNode();
if (['mousewheel', 'scroll'].includes(args.triggeredBy || '') &&
this.paginationService?.totalItems &&
args.scrollTop > 0 &&
Math.ceil(viewportElm.offsetHeight + args.scrollTop) >= args.scrollHeight) {
if (!this._scrollEndCalled) {
onScrollEnd();
this._scrollEndCalled = true;
}
}
});
// use postProcess to identify when scrollEnd process is finished to avoid calling the scrollEnd multiple times
// we also need to keep a ref of the user's postProcess and call it after our own postProcess
const orgPostProcess = this.gridOptions.backendServiceApi.postProcess;
this.gridOptions.backendServiceApi.postProcess = (processResult) => {
this._scrollEndCalled = false;
if (orgPostProcess) {
orgPostProcess(processResult);
}
};
}
}
bindResizeHook(grid, options) {
if ((options.autoFitColumnsOnFirstLoad && options.autosizeColumnsByCellContentOnFirstLoad) ||
(options.enableAutoSizeColumns && options.enableAutoResizeColumnsByCellContent)) {
throw new Error(`[Angular-Slickgrid] You cannot enable both autosize/fit viewport & resize by content, you must choose which resize technique to use. You can enable these 2 options ("autoFitColumnsOnFirstLoad" and "enableAutoSizeColumns") OR these other 2 options ("autosizeColumnsByCellContentOnFirstLoad" and "enableAutoResizeColumnsByCellContent").`);
}
// auto-resize grid on browser resize
if (options.gridHeight || options.gridWidth) {
this.resizerService.resizeGrid(0, { height: options.gridHeight, width: options.gridWidth });
}
else {
this.resizerService.resizeGrid();
}
// expand/autofit columns on first page load
if (grid &&
options?.enableAutoResize &&
options.autoFitColumnsOnFirstLoad &&
options.enableAutoSizeColumns &&
!this._isAutosizeColsCalled) {
grid.autosizeColumns();
this._isAutosizeColsCalled = true;
}
}
executeAfterDataviewCreated(_grid, gridOptions) {
// if user entered some Sort "presets", we need to reflect them all in the DOM
if (gridOptions.enableSorting && Array.isArray(gridOptions.presets?.sorters)) {
// when using multi-column sort, we can have multiple but on single sort then only grab the first sort provided
const sortColumns = this.gridOptions.multiColumnSort
? gridOptions.presets.sorters
: gridOptions.presets.sorters.slice(0, 1);
this.sortService.loadGridSorters(sortColumns);
}
}
/** When data changes in the DataView, we'll refresh the metrics and/or display a warning if the dataset is empty */
handleOnItemCountChanged(currentPageRowItemCount, totalItemCount) {
this._currentDatasetLength = totalItemCount;
this.metrics = {
startTime: new Date(),
endTime: new Date(),
itemCount: currentPageRowItemCount,
totalItemCount,
};
// if custom footer is enabled, then we'll update its metrics
if (this.slickFooter) {
this.slickFooter.metrics = this.metrics;
}
// when using local (in-memory) dataset, we'll display a warning message when filtered data is empty
if (this._isLocalGrid && this.gridOptions?.enableEmptyDataWarningMessage) {
this.displayEmptyDataWarning(currentPageRowItemCount === 0);
}
// when autoResize.autoHeight is enabled, we'll want to call a resize
if (this.gridOptions.enableAutoResize && this.resizerService.isAutoHeightEnabled && currentPageRowItemCount > 0) {
this.resizerService.resizeGrid();
}
}
initializePaginationService(paginationOptions) {
if (this.gridOptions) {
this.paginationData = {
gridOptions: this.gridOptions,
paginationService: this.paginationService,
};
this.paginationService.totalItems = this.totalItems;
this.paginationService.init(this.slickGrid, paginationOptions, this.backendServiceApi);
this.subscriptions.push(this._eventPubSubService.subscribe('onPaginationChanged', (paginationChanges) => {
this.paginationChanged(paginationChanges);
}), this._eventPubSubService.subscribe('onPaginationVisibilityChanged', (visibility) => {
this.showPagination = visibility?.visible ?? false;
if (this.gridOptions?.backendServiceApi) {
this.backendUtilityService?.refreshBackendDataset(this.gridOptions);
}
this.renderPagination(this.showPagination);
}));
// also initialize (render) the pagination component
this.renderPagination();
this._isPaginationInitialized = true;
}
this.cd.detectChanges();
}
/** Load the Editor Collection asynchronously and replace the "collection" property when Observable resolves */
loadEditorCollectionAsync(column) {
if (column?.editor) {
const collectionAsync = column.editor.collectionAsync;
column.editor.disabled = true; // disable the Editor DOM element, we'll re-enable it after receiving the collection with "updateEditorCollection()"
if (collectionAsync instanceof Observable) {
this.subscriptions.push(collectionAsync.subscribe((resolvedCollection) => this.updateEditorCollection(column, resolvedCollection)));
}
else if (collectionAsync instanceof Promise) {
// wait for the "collectionAsync", once resolved we will save it into the "collection"
// the collectionAsync can be of 3 types HttpClient, HttpFetch or a Promise
collectionAsync.then((response) => {
if (Array.isArray(response)) {
this.updateEditorCollection(column, response); // from Promise
}
});
}
}
}
insertDynamicPresetColumns(columnId, gridPresetColumns) {
if (this._columnDefinitions) {
const columnPosition = this._columnDefinitions.findIndex((c) => c.id === columnId);
if (columnPosition >= 0) {
const dynColumn = this._columnDefinitions[columnPosition];
if (dynColumn?.id === columnId && !gridPresetColumns.some((c) => c.id === columnId)) {
columnPosition > 0 ? gridPresetColumns.splice(columnPosition, 0, dynColumn) : gridPresetColumns.unshift(dynColumn);
}
}
}
}
/** Load any possible Columns Grid Presets */
loadColumnPresetsWhenDatasetInitialized() {
// if user entered some Columns "presets", we need to reflect them all in the grid
if (Array.isArray(this.gridOptions.presets?.columns) && this.gridOptions.presets.columns.length > 0) {
const gridPresetColumns = this.gridStateService.getAssociatedGridColumns(this.slickGrid, this.gridOptions.presets.columns);
if (Array.isArray(gridPresetColumns) && gridPresetColumns.length > 0 && Array.isArray(this._columnDefinitions)) {
// make sure that the dynamic columns are included in presets (1.Row Move, 2. Row Selection, 3. Row Detail)
if (this.gridOptions.enableRowMoveManager) {
const rmmColId = this.gridOptions?.rowMoveManager?.columnId ?? '_move';
this.insertDynamicPresetColumns(rmmColId, gridPresetColumns);
}
if (this.gridOptions.enableCheckboxSelector) {
const chkColId = this.gridOptions?.checkboxSelector?.columnId ?? '_checkbox_selector';
this.insertDynamicPresetColumns(chkColId, gridPresetColumns);
}
if (this.gridOptions.enableRowDetailView) {
const rdvColId = this.gridOptions?.rowDetailView?.columnId ?? '_detail_selector';
this.insertDynamicPresetColumns(rdvColId, gridPresetColumns);
}
// keep copy the original optional `width` properties optionally provided by the user.
// We will use this when doing a resize by cell content, if user provided a `width` it won't override it.
gridPresetColumns.forEach((col) => (col.originalWidth = col.width));
// finally set the new presets columns (including checkbox selector if need be)
this.slickGrid.setColumns(gridPresetColumns);
this.sharedService.visibleColumns = gridPresetColumns;
}
}
}
/** Load any possible Filters Grid Presets */
loadFilterPresetsWhenDatasetInitialized() {
if (this.gridOptions && !this.customDataView) {
// if user entered some Filter "presets", we need to reflect them all in the DOM
// also note that a presets of Tree Data Toggling will also call this method because Tree Data toggling does work with data filtering
// (collapsing a parent will basically use Filter for hidding (aka collapsing) away the child underneat it)
if (Array.isArray(this.gridOptions.presets?.filters) || Array.isArray(this.gridOptions.presets?.treeData?.toggledItems)) {
this.filterService.populateColumnFilterSearchTermPresets(this.gridOptions.presets?.filters || []);
}
}
}
/**
* local grid, check if we need to show the Pagination
* if so then also check if there's any presets and finally initialize the PaginationService
* a local grid with Pagination presets will potentially have a different total of items, we'll need to get it from the DataView and update our total
*/
loadLocalGridPagination(dataset) {
if (this.gridOptions && this._paginationOptions) {
this.totalItems = Array.isArray(dataset) ? dataset.length : 0;
if (this._paginationOptions && this.dataView?.getPagingInfo) {
const slickPagingInfo = this.dataView.getPagingInfo();
if (slickPagingInfo &&
'totalRows' in slickPagingInfo &&
this._paginationOptions.totalItems !== slickPagingInfo.totalRows) {
this.totalItems = slickPagingInfo.totalRows || 0;
}
}
this._paginationOptions.totalItems = this.totalItems;
const paginationOptions = this.setPaginationOptionsWhenPresetDefined(this.gridOptions, this._paginationOptions);
this.initializePaginationService(paginationOptions);
}
}
/** Load any Row Selections into the DataView that were presets by the user */
loadRowSelectionPresetWhenExists() {
// if user entered some Row Selections "presets"
const presets = this.gridOptions?.presets;
const enableRowSelection = this.gridOptions && (this.gridOptions.enableCheckboxSelector || this.gridOptions.enableRowSelection);
if (enableRowSelection &&
this.slickGrid?.getSelectionModel() &&
presets?.rowSelection &&
(Array.isArray(presets.rowSelection.gridRowIndexes) || Array.isArray(presets.rowSelection.dataContextIds))) {
let dataContextIds = presets.rowSelection.dataContextIds;
let gridRowIndexes = presets.rowSelection.gridRowIndexes;
// maps the IDs to the Grid Rows and vice versa, the "dataContextIds" has precedence over the other
if (Array.isArray(dataContextIds) && dataContextIds.length > 0) {
gridRowIndexes = this.dataView.mapIdsToRows(dataContextIds) || [];
}
else if (Array.isArray(gridRowIndexes) && gridRowIndexes.length > 0) {
dataContextIds = this.dataView.mapRowsToIds(gridRowIndexes) || [];
}
// apply row selection when defined as grid presets
if (this.slickGrid && Array.isArray(gridRowIndexes)) {
this.slickGrid.setSelectedRows(gridRowIndexes);
this.dataView.setSelectedIds(dataContextIds || [], {
isRowBeingAdded: true,
shouldTriggerEvent: false, // do not trigger when presetting the grid
applyRowSelectionToGrid: true,
});
}
}
}
hasBackendInfiniteScroll(gridOptions) {
return !!(gridOptions || this.gridOptions).backendServiceApi?.service.options?.infiniteScroll;
}
mergeGridOptions(gridOptions) {
gridOptions.gridId = this.gridId;
gridOptions.gridContainerId = `slickGridContainer-${this.gridId}`;
// use extend to deep merge & copy to avoid immutable properties being changed in GlobalGridOptions after a route change
const options = extend(true, {}, GlobalGridOptions, this.forRootConfig, gridOptions);
// if we have a backendServiceApi and the enablePagination is undefined, we'll assume that we do want to see it, else get that defined value
if (!this.hasBackendInfiniteScroll(gridOptions)) {
gridOptions.enablePagination = !!(gridOptions.backendServiceApi && gridOptions.enablePagination === undefined
? true
: gridOptions.enablePagination);
}
// using copy extend to do a deep clone has an unwanted side on objects and pageSizes but ES6 spread has other worst side effects
// so we will just overwrite the pageSizes when needed, this is the only one causing issues so far.
// On a deep extend, Object and Array are extended, but object wrappers on primitive types such as String, Boolean, and Number are not.
if (options?.pagination &&
(gridOptions.enablePagination || gridOptions.backendServiceApi) &&
(this.forRootConfig.pagination || gridOptions.pagination)) {
options.pagination.pageSize =
gridOptions.pagination?.pageSize ?? this.forRootConfig.pagination?.pageSize ?? GlobalGridOptions.pagination.pageSize;
options.pagination.pageSizes =
gridOptions.pagination?.pageSizes ?? this.forRootConfig.pagination?.pageSizes ?? GlobalGridOptions.pagination.pageSizes;
}
// also make sure to show the header row if user have enabled filtering
this._hideHeaderRowAfterPageLoad = options.showHeaderRow === false;
if (options.enableFiltering && !options.showHeaderRow) {
options.showHeaderRow = options.enableFiltering;
}
// when we use Pagination on Local Grid, it doesn't seem to work without enableFiltering
// so we'll enable the filtering but we'll keep the header row hidden
if (options && !options.enableFiltering && options.enablePagination && this._isLocalGrid) {
options.enableFiltering = true;
options.showHeaderRow = false;
this._hideHeaderRowAfterPageLoad = true;
if (this.sharedService) {
this.sharedService.hideHeaderRowAfterPageLoad = true;
}
}
return options;
}
/** Add a register of a new external resource, user could also optional dispose all previous resources before pushing any new resources to the resources array list. */
registerExternalResources(resources, disposePreviousResources = false) {
if (disposePreviousResources) {
this.disposeExternalResources();
}
resources.forEach((res) => this._registeredResources.push(res));
this.initializeExternalResources(resources);
}
resetExternalResources() {
this._registeredResources = [];
}
/** Pre-Register any Resource that don't require SlickGrid to be instantiated (for example RxJS Resource & RowDetail) */
preRegisterResources() {
this._registeredResources = this.gridOptions?.externalResources || [];
// Angular-Slickgrid requires RxJS, so we'll register it as the first resource
this.registerRxJsResource(new RxJsResource());
if (this.gridOptions.enableRowDetailView) {
this.slickRowDetailView = new SlickRowDetailView(this.angularUtilService, this.appRef, this._eventPubSubService, this.elm.nativeElement, this.rxjs);
this.slickRowDetailView.create(this.columnDefinitions, this.gridOptions);
this.extensionService.addExtensionToList(ExtensionName.rowDetailView, {
name: ExtensionName.rowDetailView,
instance: this.slickRowDetailView,
});
}
}
initializeExternalResources(resources) {
if (Array.isArray(resources)) {
for (const resource of resources) {
if (this.slickGrid && typeof resource.init === 'function') {
resource.init(this.slickGrid, this.containerService);
}
}
}
}
registerResources() {
// at this point, we consider all the registered services as external services, anything else registered afterward aren't external
if (Array.isArray(this._registeredResources)) {
this.sharedService.externalRegisteredResources = this._registeredResources;
}
// push all other Services that we want to be registered
this._registeredResources.push(this.gridService, this.gridStateService);
// when using Grouping/DraggableGrouping/Colspan register its Service
if ((this.gridOptions.createPreHeaderPanel && this.gridOptions.createTopHeaderPanel) ||
(this.gridOptions.createPreHeaderPanel && !this.gridOptions.enableDraggableGrouping)) {
this._registeredResources.push(this.headerGroupingService);
}
// when using Tree Data View, register its Service
if (this.gridOptions.enableTreeData) {
this._registeredResources.push(this.treeDataService);
}
// when user enables translation, we need to translate Headers on first pass & subsequently in the bindDifferentHooks
if (this.gridOptions.enableTranslate) {
this.extensionService.translateColumnHeaders();
}
// also initialize (render) the empty warning component
this.slickEmptyWarning = new SlickEmptyWarningComponent();
this._registeredResources.push(this.slickEmptyWarning);
// bind & initialize all Components/Services that were tagged as enabled
// register all services by executing their init method and providing them with the Grid object
this.initializeExternalResources(this._registeredResources);
// initialize RowDetail separately since we already added it to the ExtensionList via `addExtensionToList()` but not in external resources,
// because we don't want to dispose the extension/resource more than once (because externalResources/extensionList are both looping through their list to dispose of them)
if (this.gridOptions.enableRowDetailView && this.slickRowDetailView) {
this.slickRowDetailView.init(this.slickGrid);
}
}
/** Register the RxJS Resource in all necessary services which uses */
registerRxJsResource(resource) {
this.rxjs = resource;
this.backendUtilityService.addRxJsResource(this.rxjs);
this.filterFactory.addRxJsResource(this.rxjs);
this.filterService.addRxJsResource(this.rxjs);
this.sortService.addRxJsResource(this.rxjs);
this.paginationService.addRxJsResource(this.rxjs);
this.containerService.registerInstance('RxJsResource', this.rxjs);
}
/**
* Render (or dispose) the Pagination Component, user can optionally provide False (to not show it) which will in term dispose of the Pagination,
* also while disposing we can choose to omit the disposable of the Pagination Service (if we are simply toggling the Pagination, we want to keep the Service alive)
* @param {Boolean} showPagination - show (new render) or not (dispose) the Pagination
* @param {Boolean} shouldDisposePaginationService - when disposing the Pagination, do we also want to dispose of the Pagination Service? (defaults to True)
*/
renderPagination(showPagination = true) {
if (this.slickGrid && this.gridOptions?.enablePagination && !this._isPaginationInitialized && showPagination) {
if (this.gridOptions.customPaginationComponent) {
const paginationComp = this.angularUtilService.createAngularComponent(this.gridOptions.customPaginationComponent);
this.slickPagination = paginationComp.componentRef.instance;
}
else {
this.slickPagination = new SlickPaginationComponent();
}
if (this.slickPagination) {
this.slickPagination.init(this.slickGrid, this.paginationService, this._eventPubSubService, this.translaterService);
this.slickPagination.renderPagination(this.gridContainerElement);
this._isPaginationInitialized = true;
}
}
else if (!showPagination) {
this.slickPagination?.dispose();
this._isPaginationInitialized = false;
}
}
/**
* Takes a flat dataset with parent/child relationship, sort it (via its tree structure) and return the sorted flat array
* @param {Array<Object>} flatDatasetInput - flat dataset input
* @param {Boolean} forceGridRefresh - optionally force a full grid refresh
* @returns {Array<Object>} sort flat parent/child dataset
*/
sortTreeDataset(flatDatasetInput, forceGridRefresh = false) {
const prevDatasetLn = this._currentDatasetLength;
let sortedDatasetResult;
let flatDatasetOutput = [];
// if the hierarchical dataset was already initialized then no need to re-convert it, we can use it directly from the shared service ref
if (this._isDatasetHierarchicalInitialized && this.datasetHierarchical) {
sortedDatasetResult = this.treeDataService.sortHierarchicalDataset(this.datasetHierarchical);
flatDatasetOutput = sortedDatasetResult.flat;
}
else if (Array.isArray(flatDatasetInput) && flatDatasetInput.length > 0) {
// we need to first convert the flat dataset to a hierarchical dataset and then sort it
// we'll also add props, by mutation, required by the TreeDataService on the flat array like `__hasChildren`, `parentId` and anything else to work properly
sortedDatasetResult = this.treeDataService.convertFlatParentChildToTreeDatasetAndSort(flatDatasetInput, this._columnDefinitions, this.gridOptions);
this.sharedService.hierarchicalDataset = sortedDatasetResult.hierarchical;
flatDatasetOutput = sortedDatasetResult.flat;
}
// if we add/remove item(s) from the dataset, we need to also refresh our tree data filters
if (flatDatasetInput.length > 0 && (forceGridRefresh || flatDatasetInput.length !== prevDatasetLn)) {
this.filterService.refreshTreeDataFilters(flatDatasetOutput);
}
return flatDatasetOutput;
}
/** Prepare and load all SlickGrid editors, if an async editor is found then we'll also execute it. */
loadSlickGridEditors(columnDefinitions) {
if (columnDefinitions.some((col) => `${col.id}`.includes('.'))) {
console.error('[Angular-Slickgrid] Make sure that none of your Column Definition "id" property includes a dot in its name because that will cause some problems with the Editors. For example if your column definition "field" property is "user.firstName" then use "firstName" as the column "id".');
}
return columnDefinitions.map((column) => {
// on every Editor that have a "collectionAsync", resolve the data and assign it to the "collection" property
if (column?.editor?.collectionAsync) {
this.loadEditorCollectionAsync(column);
}
return { ...column, editorClass: column.editor?.model };
});
}
suggestDateParsingWhenHelpful() {
if (this.dataView?.getItemCount() > WARN_NO_PREPARSE_DATE_SIZE &&
!this.gridOptions.silenceWarnings &&
!this.gridOptions.preParseDateColumns &&
this.slickGrid.getColumns().some((c) => isColumnDateType(c.type))) {
console.warn('[Slickgrid-Universal] For getting better perf, we suggest you enable the `preParseDateColumns` grid option, ' +
'for more info visit => https://ghiscoding.gitbook.io/angular-slickgrid/column-functionalities/sorting#pre-parse-date-columns-for-better-perf');
}
}
/**
* When the Editor(s) has a "editor.collection" property, we'll load the async collection.
* Since this is called after the async call resolves, the pointer will not be the same as the "column" argument passed.
*/
updateEditorCollection(column, newCollection) {
if (this.slickGrid && column.editor) {
column.editor.collection = newCollection;
column.editor.disabled = false;
// get current Editor, remove it from the DOM then re-enable it and re-render it with the new collection.
const currentEditor = this.slickGrid.getCellEditor();
if (currentEditor?.disable && currentEditor?.renderDomElement) {
currentEditor.destroy();
currentEditor.disable(false);
currentEditor.renderDomElement(newCollection);
}
}
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AngularSlickgridComponent, deps: [{ token: AngularUtilService }, { token: i0.ApplicationRef }, { token: i0.ChangeDetectorRef }, { token: ContainerService }, { token: i0.ElementRef }, { token: i1.TranslateService, optional: true }, { token: TranslaterService, optional: true }, { token: 'config' }, { token: 'externalService' }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: AngularSlickgridComponent, selector: "angular-slickgrid", inputs: { customDataView: "customDataView", gridId: "gridId", gridOptions: "gridOptions", paginationOptions: "paginationOptions", columnDefinitions: "columnDefinitions", dataset: "dataset", datasetHierarchical: "datasetHierarchical" }, outputs: { columnDefinitionsChange: "columnDefinitionsChange" }, providers: [
// make everything transient (non-singleton)
AngularUtilService,
TranslaterService,
], queries: [{ propertyName: "slickgridHeader", first: true, predicate: ["slickgridHeader"], descendants: true, static: true }, { propertyName: "slickgridFooter", first: true, predicate: ["slickgridFooter"], descendants: true, static: true }], ngImport: i0, template: "<div id=\"slickGridContainer-{{ gridId }}\" class=\"gridPane\">\n <ng-container *ngTemplateOutlet=\"slickgridHeader\"></ng-container>\n <div attr.id=\"{{ gridId }}\" class=\"slickgrid-container\"></div>\n <ng-container *ngTemplateOutlet=\"slickgridFooter\"></ng-container>\n</div>\n", dependencies: [{ kind: "directive", type: i5.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], preserveWhitespaces: true });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AngularSlickgridComponent, decorators: [{
type: Component,
args: [{ selector: 'angular-slickgrid', providers: [
// make everything transient (non-singleton)
AngularUtilService,
TranslaterService,
], template: "<div id=\"slickGridContainer-{{ gridId }}\" class=\"gridPane\">\n <ng-container *ngTemplateOutlet=\"slickgridHeader\"></ng-container>\n <div attr.id=\"{{ gridId }}\" class=\"slickgrid-container\"></div>\n <ng-container *ngTemplateOutlet=\"slickgridFooter\"></ng-container>\n</div>\n" }]
}], ctorParameters: () => [{ type: AngularUtilService }, { type: i0.ApplicationRef }, { type: i0.ChangeDetectorRef }, { type: ContainerService }, { type: i0.ElementRef }, { type: i1.TranslateService, decorators: [{
type: Optional
}] }, { type: TranslaterService, decorators: [{
type: Optional
}] }, { type: undefined, decorators: [{
type: Inject,
args: ['config']
}] }, { type: undefined, decorators: [{
type: Inject,
args: ['externalService']
}] }], propDecorators: { customDataView: [{
type: Input
}], gridId: [{
type: Input
}], gridOptions: [{
type: Input
}], paginationOptions: [{
type: Input
}], columnDefinitions: [{
type: Input
}], columnDefinitionsChange: [{
type: Output
}], dataset: [{
type: Input
}], datasetHierarchical: [{
type: Input
}], slickgridHeader: [{
type: ContentChild,
args: ['slickgridHeader', { static: true }]
}], slickgridFooter: [{
type: ContentChild,
args: ['slickgridFooter', { static: true }]
}] } });
class AngularSlickgridModule {
static forRoot(config = {}) {
return {
ngModule: AngularSlickgridModule,
providers: [
{ provide: 'config', useValue: config },
{ provide: 'externalService', useValue: null },
AngularUtilService,
ContainerService,
],
};
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AngularSlickgridModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.13", ngImport: i0, type: AngularSlickgridModule, declarations: [AngularSlickgridComponent], imports: [CommonModule, TranslateModule], exports: [AngularSlickgridComponent] });
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AngularSlickgridModule, imports: [CommonModule, TranslateModule] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AngularSlickgridModule, decorators: [{
type: NgModule,
args: [{
imports: [CommonModule, TranslateModule],
declarations: [AngularSlickgridComponent],
exports: [AngularSlickgridComponent],
}]
}] });
/**
* Generated bundle index. Do not edit.
*/
export { AngularSlickgridComponent, AngularSlickgridModule, AngularUtilService, SlickRowDetailView, SlickgridConfig, unsubscribeAllObservables };
//# sourceMappingURL=angular-slickgrid.mjs.map