diff --git a/README.md b/README.md index dc7403c8d..d55d5b19b 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ One of the best javascript datagrid [SlickGrid](https://github.com/mleibman/SlickGrid) which was originally developed by @mleibman is now available to Aurelia. I have tried and used a few datagrids and SlickGrid beats most of them in terms of functionalities and performance (it can easily deal with even a million row). ### SlickGrid Source -We will be using [6pac SlickGrid fork](https://github.com/6pac/SlickGrid/) (the most active fork since the original @mleibman fork was closed some time ago by his author personal reasons). +We will be using [6pac SlickGrid fork](https://github.com/6pac/SlickGrid/) (the most active fork since the original @mleibman fork was closed some time ago by his author for personal reasons). ### Goal The goal is of course to be able to run SlickGrid within Aurelia but also to incorporate as much as possible the entire list of functionalities (and more) that SlickGrid offers (you can see a vast list of samples on the [6pac SlickGrid examples](https://github.com/6pac/SlickGrid/wiki/Examples) website). diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/aurelia-slickgrid.ts b/aurelia-slickgrid/src/aurelia-slickgrid/aurelia-slickgrid.ts index 64344c333..050fe7f13 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/aurelia-slickgrid.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/aurelia-slickgrid.ts @@ -22,14 +22,11 @@ import 'slickgrid/plugins/slick.rowselectionmodel'; import { bindable, BindingEngine, bindingMode, Container, Factory, inject } from 'aurelia-framework'; import { EventAggregator, Subscription } from 'aurelia-event-aggregator'; -import { I18N } from 'aurelia-i18n'; import { GlobalGridOptions } from './global-grid-options'; import { + AureliaGridInstance, BackendServiceOption, - CellArgs, Column, - FormElementType, - GraphqlResult, GridOption, GridStateChange, GridStateType, @@ -41,7 +38,7 @@ import { FilterService, GraphqlService, GridEventService, - GridExtraService, + GridService, GridStateService, GroupingAndColspanService, ResizerService, @@ -64,12 +61,10 @@ const eventPrefix = 'sg'; Element, EventAggregator, FilterService, - GraphqlService, GridEventService, - GridExtraService, + GridService, GridStateService, GroupingAndColspanService, - I18N, ResizerService, SortService, Container @@ -78,12 +73,11 @@ export class AureliaSlickgridCustomElement { private _columnDefinitions: Column[] = []; private _dataset: any[]; private _eventHandler: any = new Slick.EventHandler(); - columnDefSubscriber: Subscription; - gridStateSubscriber: Subscription; groupItemMetadataProvider: any; isGridInitialized = false; - localeChangedSubscriber: Subscription; showPagination = false; + serviceList: any[] = []; + subscriptions: Subscription[] = []; @bindable({ defaultBindingMode: bindingMode.twoWay }) columnDefinitions: Column[] = []; @bindable({ defaultBindingMode: bindingMode.twoWay }) element: Element; @@ -104,16 +98,26 @@ export class AureliaSlickgridCustomElement { private elm: Element, private ea: EventAggregator, private filterService: FilterService, - private graphqlService: GraphqlService, private gridEventService: GridEventService, - private gridExtraService: GridExtraService, + private gridService: GridService, private gridStateService: GridStateService, private groupingAndColspanService: GroupingAndColspanService, - private i18n: I18N, - private resizer: ResizerService, + private resizerService: ResizerService, private sortService: SortService, private container: Container - ) { } + ) { + this.serviceList = [ + controlAndPluginService, + exportService, + filterService, + gridEventService, + gridService, + gridStateService, + groupingAndColspanService, + resizerService, + sortService + ]; + } attached() { this.initialization(); @@ -121,7 +125,7 @@ export class AureliaSlickgridCustomElement { } initialization() { - this.elm.dispatchEvent(new CustomEvent(`${eventPrefix}-on-before-grid-create`, { + this.elm.dispatchEvent(new CustomEvent(`${aureliaEventPrefix}-on-before-grid-create`, { bubbles: true, })); this.ea.publish('onBeforeGridCreate', true); @@ -140,6 +144,22 @@ export class AureliaSlickgridCustomElement { } else { this.dataview = new Slick.Data.DataView(); } + + // for convenience, we provide the property "editor" as an Aurelia-Slickgrid editor complex object + // however "editor" is used internally by SlickGrid for it's Editor Factory + // so in our lib we will swap "editor" and copy it into "internalColumnEditor" + // then take back "editor.model" and make it the new "editor" so that SlickGrid Editor Factory still works + // Wrap each editor class in the Factory resolver so consumers of this library can use + // dependency injection. Aurelia will resolve all dependencies when we pass the container + // and allow slickgrid to pass its arguments to the editors constructor last + // when slickgrid creates the editor + // https://github.com/aurelia/dependency-injection/blob/master/src/resolvers.js + this._columnDefinitions = this.columnDefinitions.map((c: Column | any) => ({ + ...c, + editor: c.editor && Factory.of(c.editor.model).get(this.container), + internalColumnEditor: { ...c.editor } + })); + this.controlAndPluginService.createPluginBeforeGridCreation(this._columnDefinitions, this.gridOptions); this.grid = new Slick.Grid(`#${this.gridId}`, this.dataview, this._columnDefinitions, this.gridOptions); this.controlAndPluginService.attachDifferentControlOrPlugins(this.grid, this.dataview, this.groupItemMetadataProvider); @@ -152,12 +172,12 @@ export class AureliaSlickgridCustomElement { this.dataview.endUpdate(); // publish certain events - this.elm.dispatchEvent(new CustomEvent(`${eventPrefix}-on-grid-created`, { + this.elm.dispatchEvent(new CustomEvent(`${aureliaEventPrefix}-on-grid-created`, { bubbles: true, detail: this.grid })); this.ea.publish('onGridCreated', this.grid); - this.elm.dispatchEvent(new CustomEvent(`${eventPrefix}-on-dataview-created`, { + this.elm.dispatchEvent(new CustomEvent(`${aureliaEventPrefix}-on-dataview-created`, { bubbles: true, detail: this.dataview })); @@ -171,8 +191,8 @@ export class AureliaSlickgridCustomElement { this.groupingAndColspanService.init(this.grid, this.dataview); } - // attach grid extra service - this.gridExtraService.init(this.grid, this.dataview); + // initialize grid service + this.gridService.init(this.grid, this.dataview); // when user enables translation, we need to translate Headers on first pass & subsequently in the attachDifferentHooks if (this.gridOptions.enableTranslate) { @@ -186,37 +206,66 @@ export class AureliaSlickgridCustomElement { // attach the Backend Service API callback functions only after the grid is initialized // because the preProcess() and onInit() might get triggered - if (this.gridOptions && (this.gridOptions.backendServiceApi || this.gridOptions.onBackendEventApi)) { + if (this.gridOptions && this.gridOptions.backendServiceApi) { this.attachBackendCallbackFunctions(this.gridOptions); } - this.gridStateService.init(this.grid, this.filterService, this.sortService); + this.gridStateService.init(this.grid, this.controlAndPluginService, this.filterService, this.sortService); + + // create the Aurelia Grid Instance with reference to all Services + const aureliaElementInstance: AureliaGridInstance = { + // Slick Grid & DataView objects + dataView: this.dataview, + slickGrid: this.grid, + + // return all available Services (non-singleton) + backendService: this.gridOptions && this.gridOptions.backendServiceApi && this.gridOptions.backendServiceApi.service, + exportService: this.exportService, + filterService: this.filterService, + gridEventService: this.gridEventService, + gridStateService: this.gridStateService, + gridService: this.gridService, + groupingService: this.groupingAndColspanService, + pluginService: this.controlAndPluginService, + resizerService: this.resizerService, + sortService: this.sortService, + }; + this.elm.dispatchEvent(new CustomEvent(`${aureliaEventPrefix}-on-aurelia-grid-created`, { + bubbles: true, + detail: aureliaElementInstance + })); } detached() { this.ea.publish('onBeforeGridDestroy', this.grid); - this.elm.dispatchEvent(new CustomEvent(`${eventPrefix}-on-before-grid-destroy`, { + this.elm.dispatchEvent(new CustomEvent(`${aureliaEventPrefix}-on-before-grid-destroy`, { bubbles: true, detail: this.grid })); this.dataview = []; this._eventHandler.unsubscribeAll(); - this.columnDefSubscriber.dispose(); - this.controlAndPluginService.dispose(); - this.filterService.dispose(); - this.gridEventService.dispose(); - this.gridStateService.dispose(); - this.groupingAndColspanService.dispose(); - this.resizer.dispose(); - this.sortService.dispose(); this.grid.destroy(); - this.gridStateSubscriber.dispose(); - this.localeChangedSubscriber.dispose(); this.ea.publish('onAfterGridDestroyed', true); - this.elm.dispatchEvent(new CustomEvent(`${eventPrefix}-on-after-grid-destroyed`, { + this.elm.dispatchEvent(new CustomEvent(`${aureliaEventPrefix}-on-after-grid-destroyed`, { bubbles: true, detail: this.grid })); + + // dispose of all Services + this.serviceList.forEach((service: any) => { + if (service && service.dispose) { + service.dispose(); + } + }); + this.serviceList = []; + + // also unsubscribe all Subscriptions + this.subscriptions.forEach((subscription: Subscription) => { + if (subscription && subscription.dispose) { + subscription.dispose(); + } + }); + this.subscriptions = []; } bind() { @@ -226,19 +275,10 @@ export class AureliaSlickgridCustomElement { // subscribe to column definitions assignment changes with BindingEngine // assignment changes are not triggering a "changed" event https://stackoverflow.com/a/30286225/1212166 - this.columnDefSubscriber = this.bindingEngine.collectionObserver(this.columnDefinitions) - .subscribe(changes => this.updateColumnDefinitionsList(this._columnDefinitions)); - - // Wrap each editor class in the Factory resolver so consumers of this library can use - // dependency injection. Aurelia will resolve all dependencies when we pass the container - // and allow slickgrid to pass its arguments to the editors constructor last - // when slickgrid creates the editor - // https://github.com/aurelia/dependency-injection/blob/master/src/resolvers.js - for (const c of this._columnDefinitions) { - if (c.editor) { - c.editor = Factory.of(c.editor).get(this.container); - } - } + this.subscriptions.push( + this.bindingEngine.collectionObserver(this.columnDefinitions) + .subscribe(changes => this.updateColumnDefinitionsList(this._columnDefinitions)) + ); } columnDefinitionsChanged(newColumnDefinitions: Column[]) { @@ -266,8 +306,8 @@ export class AureliaSlickgridCustomElement { * For now, this is GraphQL Service only feautre and it will basically refresh the Dataset & Pagination without having the user to create his own PostProcess every time */ createBackendApiInternalPostProcessCallback(gridOptions: GridOption) { - if (gridOptions && (gridOptions.backendServiceApi || gridOptions.onBackendEventApi)) { - const backendApi = gridOptions.backendServiceApi || gridOptions.onBackendEventApi; + if (gridOptions && gridOptions.backendServiceApi) { + const backendApi = gridOptions.backendServiceApi; // internalPostProcess only works with a GraphQL Service, so make sure it is that type if (backendApi && backendApi.service && backendApi.service instanceof GraphqlService) { @@ -286,18 +326,28 @@ export class AureliaSlickgridCustomElement { attachDifferentHooks(grid: any, gridOptions: GridOption, dataView: any) { // on locale change, we have to manually translate the Headers, GridMenu - this.localeChangedSubscriber = this.ea.subscribe('i18n:locale:changed', (payload: any) => { - if (gridOptions.enableTranslate) { - this.controlAndPluginService.translateColumnHeaders(); - this.controlAndPluginService.translateColumnPicker(); - this.controlAndPluginService.translateGridMenu(); - this.controlAndPluginService.translateHeaderMenu(); + this.subscriptions.push( + this.ea.subscribe('i18n:locale:changed', (payload: any) => { + if (gridOptions.enableTranslate) { + this.controlAndPluginService.translateColumnHeaders(); + this.controlAndPluginService.translateColumnPicker(); + this.controlAndPluginService.translateGridMenu(); + this.controlAndPluginService.translateHeaderMenu(); + } + }) + ); + + // if user entered some Columns "presets", we need to reflect them all in the grid + if (gridOptions.presets && gridOptions.presets.columns && Array.isArray(gridOptions.presets.columns) && gridOptions.presets.columns.length > 0) { + const gridColumns: Column[] = this.gridStateService.getAssociatedGridColumns(grid, gridOptions.presets.columns); + if (gridColumns && Array.isArray(gridColumns)) { + grid.setColumns(gridColumns); } - }); + } // attach external sorting (backend) when available or default onSort (dataView) if (gridOptions.enableSorting) { - (gridOptions.backendServiceApi || gridOptions.onBackendEventApi) ? this.sortService.attachBackendOnSort(grid, dataView) : this.sortService.attachLocalOnSort(grid, dataView); + gridOptions.backendServiceApi ? this.sortService.attachBackendOnSort(grid, dataView) : this.sortService.attachLocalOnSort(grid, dataView); } // attach external filter (backend) when available or default onFilter (dataView) @@ -305,18 +355,15 @@ export class AureliaSlickgridCustomElement { this.filterService.init(grid); // if user entered some "presets", we need to reflect them all in the DOM - if (gridOptions.presets && gridOptions.presets.filters) { - this.filterService.populateColumnFilterSearchTerms(grid); + if (gridOptions.presets && Array.isArray(gridOptions.presets.filters) && gridOptions.presets.filters.length > 0) { + this.filterService.populateColumnFilterSearchTerms(); } - (gridOptions.backendServiceApi || gridOptions.onBackendEventApi) ? this.filterService.attachBackendOnFilter(grid) : this.filterService.attachLocalOnFilter(grid, this.dataview); + gridOptions.backendServiceApi ? this.filterService.attachBackendOnFilter(grid) : this.filterService.attachLocalOnFilter(grid, this.dataview); } // 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 || gridOptions.onBackendEventApi) { - const backendApi = gridOptions.backendServiceApi || gridOptions.onBackendEventApi; - if (gridOptions.onBackendEventApi) { - console.warn(`"onBackendEventApi" has been DEPRECATED, please consider using "backendServiceApi" in the short term since "onBackendEventApi" will be removed in future versions. You can take look at the Aurelia-Slickgrid Wikis for OData/GraphQL Services implementation`); - } + if (gridOptions.backendServiceApi) { + const backendApi = gridOptions.backendServiceApi; if (backendApi && backendApi.service && backendApi.service.init) { backendApi.service.init(backendApi.options, gridOptions.pagination, this.grid); @@ -354,12 +401,14 @@ export class AureliaSlickgridCustomElement { } // expose GridState Service changes event through dispatch - this.gridStateSubscriber = this.ea.subscribe('gridStateService:changed', (gridStateChange: GridStateChange) => { - this.elm.dispatchEvent(new CustomEvent(`${aureliaEventPrefix}-on-grid-state-service-changed`, { - bubbles: true, - detail: gridStateChange - })); - }); + this.subscriptions.push( + this.ea.subscribe('gridStateService:changed', (gridStateChange: GridStateChange) => { + this.elm.dispatchEvent(new CustomEvent(`${aureliaEventPrefix}-on-grid-state-changed`, { + bubbles: true, + detail: gridStateChange + })); + }) + ); // on cell click, mainly used with the columnDef.action callback this.gridEventService.attachOnCellChange(grid, dataView); @@ -387,20 +436,25 @@ export class AureliaSlickgridCustomElement { } attachBackendCallbackFunctions(gridOptions: GridOption) { - const backendApi = gridOptions.backendServiceApi || gridOptions.onBackendEventApi; + const backendApi = gridOptions.backendServiceApi; const serviceOptions: BackendServiceOption = (backendApi && backendApi.service && backendApi.service.options) ? backendApi.service.options : {}; const isExecuteCommandOnInit = (!serviceOptions) ? false : ((serviceOptions && serviceOptions.hasOwnProperty('executeProcessCommandOnInit')) ? serviceOptions['executeProcessCommandOnInit'] : true); // update backend filters (if need be) before the query runs if (backendApi) { const backendService = backendApi.service; + + // if user entered some any "presets", we need to reflect them all in the grid if (gridOptions && gridOptions.presets) { - if (backendService && backendService.updateFilters && gridOptions.presets.filters) { + // Filters "presets" + if (backendService && backendService.updateFilters && Array.isArray(gridOptions.presets.filters) && gridOptions.presets.filters.length > 0) { backendService.updateFilters(gridOptions.presets.filters, true); } - if (backendService && backendService.updateSorters && gridOptions.presets.sorters) { + // Sorters "presets" + if (backendService && backendService.updateSorters && Array.isArray(gridOptions.presets.sorters) && gridOptions.presets.sorters.length > 0) { backendService.updateSorters(undefined, gridOptions.presets.sorters); } + // Pagination "presets" if (backendService && backendService.updatePagination && gridOptions.presets.pagination) { backendService.updatePagination(gridOptions.presets.pagination.pageNumber, gridOptions.presets.pagination.pageSize); } @@ -446,9 +500,9 @@ export class AureliaSlickgridCustomElement { } // auto-resize grid on browser resize - this.resizer.init(grid); + this.resizerService.init(grid); if (grid && options.enableAutoResize) { - this.resizer.attachAutoResizeDataGrid({ height: this.gridHeight, width: this.gridWidth }); + this.resizerService.attachAutoResizeDataGrid({ height: this.gridHeight, width: this.gridWidth }); if (options.autoFitColumnsOnFirstLoad && typeof grid.autosizeColumns === 'function') { grid.autosizeColumns(); } @@ -506,7 +560,7 @@ export class AureliaSlickgridCustomElement { } if (this.grid && this.gridOptions.enableAutoResize) { // resize the grid inside a slight timeout, in case other DOM element changed prior to the resize (like a filter/pagination changed) - this.resizer.resizeGrid(1, { height: this.gridHeight, width: this.gridWidth }); + this.resizerService.resizeGrid(1, { height: this.gridHeight, width: this.gridWidth }); } } } @@ -534,7 +588,7 @@ export class AureliaSlickgridCustomElement { */ updateColumnDefinitionsList(newColumnDefinitions?: Column[]) { if (this.gridOptions.enableTranslate) { - this.controlAndPluginService.translateColumnHeaders(); + this.controlAndPluginService.translateColumnHeaders(false, newColumnDefinitions); } else { this.controlAndPluginService.renderColumnHeaders(newColumnDefinitions); } diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/editors/floatEditor.ts b/aurelia-slickgrid/src/aurelia-slickgrid/editors/floatEditor.ts index 64b0cfe7d..3ac04ceb9 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/editors/floatEditor.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/editors/floatEditor.ts @@ -39,8 +39,9 @@ export class FloatEditor implements Editor { getDecimalPlaces() { // returns the number of fixed decimal places or null - const columnParams = this.args.column.params || {}; - let rtn = (columnParams && columnParams.hasOwnProperty('decimalPlaces')) ? columnParams.decimalPlaces : undefined; + const columnEditor = this.args && this.args.column && this.args.column.internalColumnEditor && this.args.column.internalColumnEditor; + let rtn = (columnEditor && columnEditor.params && columnEditor.params.hasOwnProperty('decimalPlaces')) ? columnEditor.params.decimalPlaces : undefined; + if (rtn === undefined) { rtn = defaultDecimalPlaces; } diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/editors/multipleSelectEditor.ts b/aurelia-slickgrid/src/aurelia-slickgrid/editors/multipleSelectEditor.ts index aa37d3fbc..025651a0d 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/editors/multipleSelectEditor.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/editors/multipleSelectEditor.ts @@ -50,7 +50,6 @@ export class MultipleSelectEditor implements Editor { constructor(private collectionService: CollectionService, private i18n: I18N, private args: any) { this.gridOptions = this.args.grid.getOptions() as GridOption; - const params = this.gridOptions.params || this.args.column.params || {}; this.defaultOptions = { container: 'body', @@ -85,29 +84,28 @@ export class MultipleSelectEditor implements Editor { throw new Error('[Aurelia-SlickGrid] An editor must always have an "init()" with valid arguments.'); } - this.columnDef = this.args.column; + this.columnDef = this.args.column as Column; - if (!this.columnDef || !this.columnDef.params || !this.columnDef.params.collection) { - throw new Error('[Aurelia-SlickGrid] You need to pass a "collection" on the params property in the column definition for ' + - 'the SingleSelect Editor to work correctly. Also each option should include ' + - 'a value/label pair (or value/labelKey when using Locale). For example: { params: { ' + - '{ collection: [{ value: true, label: \'True\' }, { value: false, label: \'False\'}] } } }'); + if (!this.columnDef || !this.columnDef.internalColumnEditor || !this.columnDef.internalColumnEditor.collection) { + throw new Error(`[Aurelia-SlickGrid] You need to pass a "collection" inside Column Definition Editor for the MultipleSelect Editor to work correctly. + Also each option should include a value/label pair (or value/labelKey when using Locale). + For example: { editor: { collection: [{ value: true, label: 'True' },{ value: false, label: 'False'}] } }`); } - this.enableTranslateLabel = (this.columnDef.params.enableTranslateLabel) ? this.columnDef.params.enableTranslateLabel : false; - let newCollection = this.columnDef.params.collection || []; - this.labelName = (this.columnDef.params.customStructure) ? this.columnDef.params.customStructure.label : 'label'; - this.valueName = (this.columnDef.params.customStructure) ? this.columnDef.params.customStructure.value : 'value'; + this.enableTranslateLabel = (this.columnDef.internalColumnEditor.enableTranslateLabel) ? this.columnDef.internalColumnEditor.enableTranslateLabel : false; + let newCollection = this.columnDef.internalColumnEditor.collection || []; + this.labelName = (this.columnDef.internalColumnEditor.customStructure) ? this.columnDef.internalColumnEditor.customStructure.label : 'label'; + this.valueName = (this.columnDef.internalColumnEditor.customStructure) ? this.columnDef.internalColumnEditor.customStructure.value : 'value'; // user might want to filter certain items of the collection - if (this.gridOptions && this.gridOptions.params && this.columnDef.params.collectionFilterBy) { - const filterBy = this.columnDef.params.collectionFilterBy; + if (this.columnDef && this.columnDef.internalColumnEditor && this.columnDef.internalColumnEditor.collectionFilterBy) { + const filterBy = this.columnDef.internalColumnEditor.collectionFilterBy; newCollection = this.collectionService.filterCollection(newCollection, filterBy); } // user might want to sort the collection - if (this.columnDef.params && this.columnDef.params.collectionSortBy) { - const sortBy = this.columnDef.params.collectionSortBy; + if (this.columnDef.internalColumnEditor && this.columnDef.internalColumnEditor.collectionSortBy) { + const sortBy = this.columnDef.internalColumnEditor.collectionSortBy; newCollection = this.collectionService.sortCollection(newCollection, sortBy, this.enableTranslateLabel); } @@ -230,7 +228,7 @@ export class MultipleSelectEditor implements Editor { // fallback to bootstrap this.$editorElm.addClass('form-control'); } else { - const elementOptions = (this.columnDef.params) ? this.columnDef.params.elementOptions : {}; + const elementOptions = (this.columnDef.internalColumnEditor) ? this.columnDef.internalColumnEditor.elementOptions : {}; this.editorElmOptions = { ...this.defaultOptions, ...elementOptions }; this.$editorElm = this.$editorElm.multipleSelect(this.editorElmOptions); setTimeout(() => this.$editorElm.multipleSelect('open')); diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/editors/singleSelectEditor.ts b/aurelia-slickgrid/src/aurelia-slickgrid/editors/singleSelectEditor.ts index 7eb05ef19..2ecd2f679 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/editors/singleSelectEditor.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/editors/singleSelectEditor.ts @@ -80,26 +80,26 @@ export class SingleSelectEditor implements Editor { this.columnDef = this.args.column; - if (!this.columnDef || !this.columnDef.params || !this.columnDef.params.collection) { - throw new Error(`[Aurelia-SlickGrid] You need to pass a "collection" on the params property in the column definition for the MultipleSelect Editor to work correctly. + if (!this.columnDef || !this.columnDef.internalColumnEditor || !this.columnDef.internalColumnEditor.collection) { + throw new Error(`[Aurelia-SlickGrid] You need to pass a "collection" inside Column Definition Editor for the SingleSelect Editor to work correctly. Also each option should include a value/label pair (or value/labelKey when using Locale). - For example: { params: { { collection: [{ value: true, label: 'True' },{ value: false, label: 'False'}] } } }`); + For example: { editor: { collection: [{ value: true, label: 'True' },{ value: false, label: 'False'}] } }`); } - this.enableTranslateLabel = (this.columnDef.params.enableTranslateLabel) ? this.columnDef.params.enableTranslateLabel : false; - let newCollection = this.columnDef.params.collection || []; - this.labelName = (this.columnDef.params.customStructure) ? this.columnDef.params.customStructure.label : 'label'; - this.valueName = (this.columnDef.params.customStructure) ? this.columnDef.params.customStructure.value : 'value'; + this.enableTranslateLabel = (this.columnDef.internalColumnEditor.enableTranslateLabel) ? this.columnDef.internalColumnEditor.enableTranslateLabel : false; + let newCollection = this.columnDef.internalColumnEditor.collection || []; + this.labelName = (this.columnDef.internalColumnEditor.customStructure) ? this.columnDef.internalColumnEditor.customStructure.label : 'label'; + this.valueName = (this.columnDef.internalColumnEditor.customStructure) ? this.columnDef.internalColumnEditor.customStructure.value : 'value'; // user might want to filter certain items of the collection - if (this.gridOptions.params && this.columnDef.params.collectionFilterBy) { - const filterBy = this.columnDef.params.collectionFilterBy; + if (this.columnDef.internalColumnEditor && this.columnDef.internalColumnEditor.collectionFilterBy) { + const filterBy = this.columnDef.internalColumnEditor.collectionFilterBy; newCollection = this.collectionService.filterCollection(newCollection, filterBy); } // user might want to sort the collection - if (this.columnDef.params && this.columnDef.params.collectionSortBy) { - const sortBy = this.columnDef.params.collectionSortBy; + if (this.columnDef.internalColumnEditor && this.columnDef.internalColumnEditor.collectionSortBy) { + const sortBy = this.columnDef.internalColumnEditor.collectionSortBy; newCollection = this.collectionService.sortCollection(newCollection, sortBy, this.enableTranslateLabel); } @@ -199,7 +199,7 @@ export class SingleSelectEditor implements Editor { collection.forEach((option: SelectOption) => { if (!option || (option[this.labelName] === undefined && option.labelKey === undefined)) { throw new Error('A collection with value/label (or value/labelKey when using ' + - 'Locale) is required to populate the Select list, for example: { params: { ' + + 'Locale) is required to populate the Select list, for example: { internalColumnEditor: { ' + '{ collection: [ { value: \'1\', label: \'One\' } ] } } }'); } const labelKey = (option.labelKey || option[this.labelName]) as string; @@ -222,7 +222,7 @@ export class SingleSelectEditor implements Editor { // fallback to bootstrap this.$editorElm.addClass('form-control'); } else { - const elementOptions = (this.columnDef.params) ? this.columnDef.params.elementOptions : {}; + const elementOptions = (this.columnDef.internalColumnEditor) ? this.columnDef.internalColumnEditor.elementOptions : {}; this.editorElmOptions = { ...this.defaultOptions, ...elementOptions }; this.$editorElm = this.$editorElm.multipleSelect(this.editorElmOptions); setTimeout(() => this.$editorElm.multipleSelect('open')); diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/editors/textEditor.ts b/aurelia-slickgrid/src/aurelia-slickgrid/editors/textEditor.ts index 8ac863cad..2ecb11e5d 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/editors/textEditor.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/editors/textEditor.ts @@ -14,7 +14,7 @@ export class TextEditor implements Editor { } init(): void { - this.$input = $(``) + this.$input = $(``) .appendTo(this.args.container) .on('keydown.nav', (e) => { if (e.keyCode === KeyCode.LEFT || e.keyCode === KeyCode.RIGHT) { diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/filter-conditions/booleanFilterCondition.ts b/aurelia-slickgrid/src/aurelia-slickgrid/filter-conditions/booleanFilterCondition.ts index 90e0cb895..7c2c348a2 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/filter-conditions/booleanFilterCondition.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/filter-conditions/booleanFilterCondition.ts @@ -5,5 +5,6 @@ function parseBoolean(input: boolean | number | string) { } export const booleanFilterCondition: FilterCondition = (options: FilterConditionOption) => { - return parseBoolean(options.cellValue) === parseBoolean(options.searchTerm || false); + const searchTerm = Array.isArray(options.searchTerms) && options.searchTerms[0] || ''; + return parseBoolean(options.cellValue) === parseBoolean(searchTerm); }; diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/filter-conditions/dateFilterCondition.ts b/aurelia-slickgrid/src/aurelia-slickgrid/filter-conditions/dateFilterCondition.ts index b390b9e97..af979af3e 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/filter-conditions/dateFilterCondition.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/filter-conditions/dateFilterCondition.ts @@ -4,13 +4,14 @@ import { testFilterCondition } from './filterUtilities'; import * as moment from 'moment'; export const dateFilterCondition: FilterCondition = (options: FilterConditionOption) => { + const searchTerm = Array.isArray(options.searchTerms) && options.searchTerms[0] || ''; const filterSearchType = options.filterSearchType || FieldType.dateIso; const searchDateFormat = mapMomentDateFormatWithFieldType(filterSearchType); - if (!moment(options.cellValue, moment.ISO_8601).isValid() || !moment(options.searchTerm, searchDateFormat, true).isValid()) { + if (searchTerm === null || searchTerm === '' || !moment(options.cellValue, moment.ISO_8601).isValid() || !moment(searchTerm, searchDateFormat, true).isValid()) { return false; } const dateCell = moment(options.cellValue); - const dateSearch = moment(options.searchTerm); + const dateSearch = moment(searchTerm); // run the filter condition with date in Unix Timestamp format return testFilterCondition(options.operator || '==', parseInt(dateCell.format('X'), 10), parseInt(dateSearch.format('X'), 10)); diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/filter-conditions/dateIsoFilterCondition.ts b/aurelia-slickgrid/src/aurelia-slickgrid/filter-conditions/dateIsoFilterCondition.ts index 0f3843843..1fbb61d0e 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/filter-conditions/dateIsoFilterCondition.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/filter-conditions/dateIsoFilterCondition.ts @@ -5,11 +5,12 @@ import * as moment from 'moment'; const FORMAT = mapMomentDateFormatWithFieldType(FieldType.dateIso); export const dateIsoFilterCondition: FilterCondition = (options: FilterConditionOption) => { - if (!moment(options.cellValue, FORMAT, true).isValid() || !moment(options.searchTerm, FORMAT, true).isValid()) { + const searchTerm = Array.isArray(options.searchTerms) && options.searchTerms[0] || ''; + if (searchTerm === null || searchTerm === '' || !moment(options.cellValue, FORMAT, true).isValid() || !moment(searchTerm, FORMAT, true).isValid()) { return false; } const dateCell = moment(options.cellValue, FORMAT, true); - const dateSearch = moment(options.searchTerm, FORMAT, true); + const dateSearch = moment(searchTerm, FORMAT, true); // run the filter condition with date in Unix Timestamp format return testFilterCondition(options.operator || '==', parseInt(dateCell.format('X'), 10), parseInt(dateSearch.format('X'), 10)); diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/filter-conditions/dateUsFilterCondition.ts b/aurelia-slickgrid/src/aurelia-slickgrid/filter-conditions/dateUsFilterCondition.ts index 3b2f238ef..955a26be5 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/filter-conditions/dateUsFilterCondition.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/filter-conditions/dateUsFilterCondition.ts @@ -5,11 +5,12 @@ import * as moment from 'moment'; const FORMAT = mapMomentDateFormatWithFieldType(FieldType.dateUs); export const dateUsFilterCondition: FilterCondition = (options: FilterConditionOption) => { - if (!moment(options.cellValue, FORMAT, true).isValid() || !moment(options.searchTerm, FORMAT, true).isValid()) { + const searchTerm = Array.isArray(options.searchTerms) && options.searchTerms[0] || ''; + if (searchTerm === null || searchTerm === '' || !moment(options.cellValue, FORMAT, true).isValid() || !moment(searchTerm, FORMAT, true).isValid()) { return false; } const dateCell = moment(options.cellValue, FORMAT, true); - const dateSearch = moment(options.searchTerm, FORMAT, true); + const dateSearch = moment(searchTerm, FORMAT, true); // run the filter condition with date in Unix Timestamp format return testFilterCondition(options.operator || '==', parseInt(dateCell.format('X'), 10), parseInt(dateSearch.format('X'), 10)); diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/filter-conditions/dateUsShortFilterCondition.ts b/aurelia-slickgrid/src/aurelia-slickgrid/filter-conditions/dateUsShortFilterCondition.ts index 82a8f02be..9cd44844d 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/filter-conditions/dateUsShortFilterCondition.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/filter-conditions/dateUsShortFilterCondition.ts @@ -5,11 +5,12 @@ import { mapMomentDateFormatWithFieldType } from './../services/utilities'; const FORMAT = mapMomentDateFormatWithFieldType(FieldType.dateUsShort); export const dateUsShortFilterCondition: FilterCondition = (options: FilterConditionOption) => { - if (!moment(options.cellValue, FORMAT, true).isValid() || !moment(options.searchTerm, FORMAT, true).isValid()) { + const searchTerm = Array.isArray(options.searchTerms) && options.searchTerms[0] || ''; + if (searchTerm === null || searchTerm === '' || !moment(options.cellValue, FORMAT, true).isValid() || !moment(searchTerm, FORMAT, true).isValid()) { return false; } const dateCell = moment(options.cellValue, FORMAT, true); - const dateSearch = moment(options.searchTerm, FORMAT, true); + const dateSearch = moment(searchTerm, FORMAT, true); // run the filter condition with date in Unix Timestamp format return testFilterCondition(options.operator || '==', parseInt(dateCell.format('X'), 10), parseInt(dateSearch.format('X'), 10)); diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/filter-conditions/dateUtcFilterCondition.ts b/aurelia-slickgrid/src/aurelia-slickgrid/filter-conditions/dateUtcFilterCondition.ts index c54fa8b7e..7c6bde21d 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/filter-conditions/dateUtcFilterCondition.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/filter-conditions/dateUtcFilterCondition.ts @@ -4,12 +4,13 @@ import { testFilterCondition } from './filterUtilities'; import * as moment from 'moment'; export const dateUtcFilterCondition: FilterCondition = (options: FilterConditionOption) => { + const searchTerm = Array.isArray(options.searchTerms) && options.searchTerms[0] || ''; const searchDateFormat = mapMomentDateFormatWithFieldType(options.filterSearchType || options.fieldType); - if (!moment(options.cellValue, moment.ISO_8601).isValid() || !moment(options.searchTerm, searchDateFormat, true).isValid()) { + if (searchTerm === null || searchTerm === '' || !moment(options.cellValue, moment.ISO_8601).isValid() || !moment(searchTerm, searchDateFormat, true).isValid()) { return false; } const dateCell = moment(options.cellValue, moment.ISO_8601, true); - const dateSearch = moment(options.searchTerm, searchDateFormat, true); + const dateSearch = moment(searchTerm, searchDateFormat, true); // run the filter condition with date in Unix Timestamp format return testFilterCondition(options.operator || '==', parseInt(dateCell.format('X'), 10), parseInt(dateSearch.format('X'), 10)); diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/filter-conditions/numberFilterCondition.ts b/aurelia-slickgrid/src/aurelia-slickgrid/filter-conditions/numberFilterCondition.ts index 110c79d9a..a2f1f5e71 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/filter-conditions/numberFilterCondition.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/filter-conditions/numberFilterCondition.ts @@ -3,7 +3,14 @@ import { testFilterCondition } from './filterUtilities'; export const numberFilterCondition: FilterCondition = (options: FilterConditionOption) => { const cellValue = parseFloat(options.cellValue); - const searchTerm = (typeof options.searchTerm === 'string') ? parseFloat(options.searchTerm) : options.searchTerm; + let searchTerm = (Array.isArray(options.searchTerms) && options.searchTerms[0]) || 0; + if (typeof searchTerm === 'string') { + searchTerm = parseFloat(searchTerm); + } + + if (!searchTerm && (!options.operator || options.operator === '')) { + return true; + } return testFilterCondition(options.operator || '==', cellValue, searchTerm); }; diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/filter-conditions/stringFilterCondition.ts b/aurelia-slickgrid/src/aurelia-slickgrid/filter-conditions/stringFilterCondition.ts index 7b8a3a09b..cf3b7ed71 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/filter-conditions/stringFilterCondition.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/filter-conditions/stringFilterCondition.ts @@ -7,7 +7,10 @@ export const stringFilterCondition: FilterCondition = (options: FilterConditionO // make both the cell value and search value lower for case insensitive comparison const cellValue = options.cellValue.toLowerCase(); - const searchTerm = (typeof options.searchTerm === 'string') ? options.searchTerm.toLowerCase() : options.searchTerm; + let searchTerm = (Array.isArray(options.searchTerms) && options.searchTerms[0]) || ''; + if (typeof searchTerm === 'string') { + searchTerm = searchTerm.toLowerCase(); + } if (options.operator === '*' || options.operator === OperatorType.endsWith) { return cellValue.endsWith(searchTerm); diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/filters/compoundDateFilter.ts b/aurelia-slickgrid/src/aurelia-slickgrid/filters/compoundDateFilter.ts index 3d924c56a..10ca1179e 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/filters/compoundDateFilter.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/filters/compoundDateFilter.ts @@ -7,7 +7,6 @@ import { Filter, FilterArguments, FilterCallback, - FilterType, GridOption, OperatorString, OperatorType, @@ -22,17 +21,27 @@ export class CompoundDateFilter implements Filter { private $filterInputElm: any; private $selectOperatorElm: any; private _currentValue: string; + private _operator: OperatorType | OperatorString; flatInstance: any; grid: any; - gridOptions: GridOption; - operator: OperatorType | OperatorString | undefined; - searchTerm: SearchTerm | undefined; + searchTerms: SearchTerm[]; columnDef: Column; callback: FilterCallback; - filterType = FilterType.compoundDate; constructor(private i18n: I18N) { } + /** Getter for the Grid Options pulled through the Grid Object */ + private get gridOptions(): GridOption { + return (this.grid && this.grid.getOptions) ? this.grid.getOptions() : {}; + } + + set operator(op: OperatorType | OperatorString) { + this._operator = op; + } + get operator(): OperatorType | OperatorString { + return this._operator || OperatorType.empty; + } + /** * Initialize the Filter */ @@ -42,14 +51,14 @@ export class CompoundDateFilter implements Filter { this.callback = args.callback; this.columnDef = args.columnDef; this.operator = args.operator || ''; - this.searchTerm = args.searchTerm; - if (this.grid && typeof this.grid.getOptions === 'function') { - this.gridOptions = this.grid.getOptions(); - } + this.searchTerms = args.searchTerms || []; + + // date input can only have 1 search term, so we will use the 1st array index if it exist + const searchTerm = (Array.isArray(this.searchTerms) && this.searchTerms[0]) || ''; // step 1, create the DOM Element of the filter which contain the compound Operator+Input // and initialize it if searchTerm is filled - this.$filterElm = this.createDomElement(); + this.$filterElm = this.createDomElement(searchTerm); // step 3, subscribe to the keyup event and run the callback when that happens // also add/remove "filled" class for styling purposes @@ -65,7 +74,7 @@ export class CompoundDateFilter implements Filter { /** * Clear the filter value */ - clear(triggerFilterKeyup = true) { + clear() { if (this.flatInstance && this.$selectOperatorElm) { this.$selectOperatorElm.val(0); this.flatInstance.clear(); @@ -84,9 +93,9 @@ export class CompoundDateFilter implements Filter { /** * Set value(s) on the DOM element */ - setValues(values: SearchTerm) { - if (values) { - this.flatInstance.setDate(values); + setValues(values: SearchTerm[]) { + if (values && Array.isArray(values)) { + this.flatInstance.setDate(values[0]); } } @@ -94,7 +103,7 @@ export class CompoundDateFilter implements Filter { // private functions // ------------------ - private buildDatePickerInput(searchTerm: SearchTerm) { + private buildDatePickerInput(searchTerm?: SearchTerm) { const inputFormat = mapFlatpickrDateFormatWithFieldType(this.columnDef.type || FieldType.dateIso); const outputFormat = mapFlatpickrDateFormatWithFieldType(this.columnDef.outputType || this.columnDef.type || FieldType.dateUtc); let currentLocale = this.i18n.getLocale() || 'en'; @@ -116,9 +125,9 @@ export class CompoundDateFilter implements Filter { // when using the time picker, we can simulate a keyup event to avoid multiple backend request // since backend request are only executed after user start typing, changing the time should be treated the same way if (pickerOptions.enableTime) { - this.onTriggerEvent(new CustomEvent('keyup')); + this.onTriggerEvent(new CustomEvent('keyup'), dateStr === ''); } else { - this.onTriggerEvent(undefined); + this.onTriggerEvent(undefined, dateStr === ''); } } }; @@ -159,15 +168,10 @@ export class CompoundDateFilter implements Filter { /** * Create the DOM element */ - private createDomElement() { + private createDomElement(searchTerm?: SearchTerm) { const $headerElm = this.grid.getHeaderRowColumn(this.columnDef.id); $($headerElm).empty(); - const searchTerm = (this.searchTerm || '') as string; - if (searchTerm) { - this._currentValue = searchTerm; - } - // create the DOM Select dropdown for the Operator this.$selectOperatorElm = $(this.buildSelectOperatorHtmlString()); this.$filterInputElm = this.buildDatePickerInput(searchTerm); @@ -199,8 +203,9 @@ export class CompoundDateFilter implements Filter { } // if there's a search term, we will add the "filled" class for styling purposes - if (this.searchTerm) { + if (searchTerm) { $filterContainerElm.addClass('filled'); + this._currentValue = searchTerm as string; } // append the new DOM element to the header row @@ -220,10 +225,14 @@ export class CompoundDateFilter implements Filter { return 'en'; } - private onTriggerEvent(e: Event | undefined) { - const selectedOperator = this.$selectOperatorElm.find('option:selected').text(); - (this._currentValue) ? this.$filterElm.addClass('filled') : this.$filterElm.removeClass('filled'); - this.callback(e, { columnDef: this.columnDef, searchTerm: this._currentValue, operator: selectedOperator || '=' }); + private onTriggerEvent(e: Event | undefined, clearFilterTriggered?: boolean) { + if (clearFilterTriggered) { + this.callback(e, { columnDef: this.columnDef, clearFilterTriggered: true }); + } else { + const selectedOperator = this.$selectOperatorElm.find('option:selected').text(); + (this._currentValue) ? this.$filterElm.addClass('filled') : this.$filterElm.removeClass('filled'); + this.callback(e, { columnDef: this.columnDef, searchTerms: [this._currentValue], operator: selectedOperator || '' }); + } } private hide() { diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/filters/compoundInputFilter.ts b/aurelia-slickgrid/src/aurelia-slickgrid/filters/compoundInputFilter.ts index 093c13808..bd0c3ec4c 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/filters/compoundInputFilter.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/filters/compoundInputFilter.ts @@ -1,13 +1,11 @@ import { inject } from 'aurelia-framework'; import { I18N } from 'aurelia-i18n'; -import { htmlEntityEncode } from './../services/utilities'; import { Column, FieldType, Filter, FilterArguments, FilterCallback, - FilterType, GridOption, OperatorString, OperatorType, @@ -22,16 +20,26 @@ export class CompoundInputFilter implements Filter { private $filterElm: any; private $filterInputElm: any; private $selectOperatorElm: any; + private _operator: OperatorType | OperatorString; grid: any; - gridOptions: GridOption; - operator: OperatorType | OperatorString | undefined; - searchTerm: SearchTerm | undefined; + searchTerms: SearchTerm[]; columnDef: Column; callback: FilterCallback; - filterType = FilterType.compoundInput; constructor(private i18n: I18N) { } + /** Getter for the Grid Options pulled through the Grid Object */ + private get gridOptions(): GridOption { + return (this.grid && this.grid.getOptions) ? this.grid.getOptions() : {}; + } + + set operator(op: OperatorType | OperatorString) { + this._operator = op; + } + get operator(): OperatorType | OperatorString { + return this._operator || OperatorType.empty; + } + /** * Initialize the Filter */ @@ -41,14 +49,14 @@ export class CompoundInputFilter implements Filter { this.callback = args.callback; this.columnDef = args.columnDef; this.operator = args.operator || ''; - this.searchTerm = args.searchTerm; - if (this.grid && typeof this.grid.getOptions === 'function') { - this.gridOptions = this.grid.getOptions(); - } + this.searchTerms = args.searchTerms || []; + + // filter input can only have 1 search term, so we will use the 1st array index if it exist + const searchTerm = (Array.isArray(this.searchTerms) && this.searchTerms[0]) || ''; // step 1, create the DOM Element of the filter which contain the compound Operator+Input // and initialize it if searchTerm is filled - this.$filterElm = this.createDomElement(); + this.$filterElm = this.createDomElement(searchTerm); // step 3, subscribe to the keyup event and run the callback when that happens // also add/remove "filled" class for styling purposes @@ -64,13 +72,11 @@ export class CompoundInputFilter implements Filter { /** * Clear the filter value */ - clear(triggerFilterKeyup = true) { + clear() { if (this.$filterElm && this.$selectOperatorElm) { this.$selectOperatorElm.val(0); this.$filterInputElm.val(''); - if (triggerFilterKeyup) { - this.$filterElm.trigger('keyup'); - } + this.onTriggerEvent(undefined, true); } } @@ -86,9 +92,9 @@ export class CompoundInputFilter implements Filter { /** * Set value(s) on the DOM element */ - setValues(values: SearchTerm) { - if (values) { - this.$filterElm.val(values); + setValues(values: SearchTerm[]) { + if (values && Array.isArray(values)) { + this.$filterElm.val(values[0]); } } @@ -146,7 +152,7 @@ export class CompoundInputFilter implements Filter { /** * Create the DOM element */ - private createDomElement() { + private createDomElement(searchTerm?: SearchTerm) { const $headerElm = this.grid.getHeaderRowColumn(this.columnDef.id); $($headerElm).empty(); @@ -173,7 +179,6 @@ export class CompoundInputFilter implements Filter { $filterContainerElm.append($containerInputGroup); $filterContainerElm.attr('id', `filter-${this.columnDef.id}`); - const searchTerm = (typeof this.searchTerm === 'boolean') ? `${this.searchTerm}` : this.searchTerm; this.$filterInputElm.val(searchTerm); this.$filterInputElm.data('columnId', this.columnDef.id); @@ -182,7 +187,7 @@ export class CompoundInputFilter implements Filter { } // if there's a search term, we will add the "filled" class for styling purposes - if (this.searchTerm) { + if (searchTerm) { $filterContainerElm.addClass('filled'); } @@ -194,10 +199,14 @@ export class CompoundInputFilter implements Filter { return $filterContainerElm; } - private onTriggerEvent(e: Event | undefined) { - const selectedOperator = this.$selectOperatorElm.find('option:selected').text(); - const value = this.$filterInputElm.val(); - (value) ? this.$filterElm.addClass('filled') : this.$filterElm.removeClass('filled'); - this.callback(e, { columnDef: this.columnDef, searchTerm: value, operator: selectedOperator || '' }); + private onTriggerEvent(e: Event | undefined, clearFilterTriggered?: boolean) { + if (clearFilterTriggered) { + this.callback(e, { columnDef: this.columnDef, clearFilterTriggered: true }); + } else { + const selectedOperator = this.$selectOperatorElm.find('option:selected').text(); + const value = this.$filterInputElm.val(); + (value) ? this.$filterElm.addClass('filled') : this.$filterElm.removeClass('filled'); + this.callback(e, { columnDef: this.columnDef, searchTerms: [value], operator: selectedOperator || '' }); + } } } diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/filters/filterFactory.ts b/aurelia-slickgrid/src/aurelia-slickgrid/filters/filterFactory.ts index c4c6ad723..266d3f698 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/filters/filterFactory.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/filters/filterFactory.ts @@ -1,10 +1,7 @@ import { inject, Container } from 'aurelia-framework'; -import { Filter, FilterType, FormElementType } from '../models/index'; +import { ColumnFilter, Filter } from '../models/index'; import { SlickgridConfig } from '../slickgrid-config'; -/** The name of the plugins the factory will initialize */ -export const PLUGIN_NAME = 'GRID_FILTERS'; - /** * Factory class to create a Filter interface implementation */ @@ -25,26 +22,21 @@ export class FilterFactory { } /** - * Creates a new Filter from the provided filterType - * @param {FilterType | FormElementType | string} [filterType] the type of filter to create - * as an enum or custom string. The default filter type will be used if no value is passed + * Creates a new Filter from the provided ColumnFilter or fallbacks to the default filter + * @param {columnFilter} a ColumnFilter object * @return {Filter} the new Filter */ - public createFilter(filterType?: FilterType | FormElementType | string): Filter { - const filters = this.container.getAll(PLUGIN_NAME); - - let filter: Filter | undefined = filters.find((f: Filter) => - f.filterType === filterType); + public createFilter(columnFilter: ColumnFilter | undefined): Filter | undefined { + let filter: Filter | undefined; - // default to the input filter type when none is found - if (!filter) { - filter = filters.find((f: Filter) => f.filterType === this._options.defaultFilterType); - - if (!filter) { - const enumOrCustom = FilterType[this._options.defaultFilterType] ? 'FilterType.enum' : 'custom'; + if (columnFilter && columnFilter.model) { + // the model either needs to be retrieved or is already instantiated + filter = typeof columnFilter.model === 'function' ? this.container.get(columnFilter.model) : columnFilter.model; + } - throw new Error(`Default filter of type ${enumOrCustom}=${this._options.defaultFilterType} was not found`); - } + // fallback to the default filter + if (!filter && this._options.defaultFilter) { + filter = this.container.get(this._options.defaultFilter); } return filter; diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/filters/index.ts b/aurelia-slickgrid/src/aurelia-slickgrid/filters/index.ts index e2714509c..149218225 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/filters/index.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/filters/index.ts @@ -1,4 +1,3 @@ -import { Column, Filter } from './../models/index'; import { CompoundDateFilter } from './compoundDateFilter'; import { CompoundInputFilter } from './compoundInputFilter'; import { InputFilter } from './inputFilter'; @@ -26,4 +25,4 @@ export const Filters = { select: SelectFilter }; -export { PLUGIN_NAME, FilterFactory } from './filterFactory'; +export { FilterFactory } from './filterFactory'; diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/filters/inputFilter.ts b/aurelia-slickgrid/src/aurelia-slickgrid/filters/inputFilter.ts index de08f5b4f..cfbd0ba82 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/filters/inputFilter.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/filters/inputFilter.ts @@ -1,10 +1,11 @@ import { Column, Filter, - FilterType, FilterArguments, FilterCallback, GridOption, + OperatorType, + OperatorString, SearchTerm } from './../models/index'; import * as $ from 'jquery'; @@ -12,11 +13,18 @@ import * as $ from 'jquery'; export class InputFilter implements Filter { private $filterElm: any; grid: any; - gridOptions: GridOption; - searchTerm: SearchTerm; + searchTerms: SearchTerm[]; columnDef: Column; callback: FilterCallback; - filterType = FilterType.input; + + /** Getter for the Grid Options pulled through the Grid Object */ + private get gridOptions(): GridOption { + return (this.grid && this.grid.getOptions) ? this.grid.getOptions() : {}; + } + + get operator(): OperatorType | OperatorString { + return OperatorType.equal; + } /** * Initialize the Filter @@ -28,34 +36,38 @@ export class InputFilter implements Filter { this.grid = args.grid; this.callback = args.callback; this.columnDef = args.columnDef; - this.searchTerm = args.searchTerm || ''; - if (this.grid && typeof this.grid.getOptions === 'function') { - this.gridOptions = this.grid.getOptions(); - } + this.searchTerms = args.searchTerms || []; + + // filter input can only have 1 search term, so we will use the 1st array index if it exist + const searchTerm = (Array.isArray(this.searchTerms) && this.searchTerms[0]) || ''; // step 1, create HTML string template const filterTemplate = this.buildTemplateHtmlString(); // step 2, create the DOM Element of the filter & initialize it if searchTerm is filled - this.$filterElm = this.createDomElement(filterTemplate); + this.$filterElm = this.createDomElement(filterTemplate, searchTerm); // step 3, subscribe to the keyup event and run the callback when that happens // also add/remove "filled" class for styling purposes this.$filterElm.keyup((e: any) => { - (e && e.target && e.target.value) ? this.$filterElm.addClass('filled') : this.$filterElm.removeClass('filled'); - this.callback(e, { columnDef: this.columnDef }); + const value = e && e.target && e.target.value || ''; + if (!value || value === '') { + this.callback(e, { columnDef: this.columnDef, clearFilterTriggered: true }); + this.$filterElm.removeClass('filled'); + } else { + this.$filterElm.addClass('filled'); + this.callback(e, { columnDef: this.columnDef, searchTerms: [value] }); + } }); } /** * Clear the filter value */ - clear(triggerFilterKeyup = true) { + clear() { if (this.$filterElm) { this.$filterElm.val(''); - if (triggerFilterKeyup) { - this.$filterElm.trigger('keyup'); - } + this.$filterElm.trigger('keyup'); } } @@ -93,19 +105,20 @@ export class InputFilter implements Filter { * From the html template string, create a DOM element * @param filterTemplate */ - private createDomElement(filterTemplate: string) { + private createDomElement(filterTemplate: string, searchTerm?: SearchTerm) { const $headerElm = this.grid.getHeaderRowColumn(this.columnDef.id); $($headerElm).empty(); // create the DOM element & add an ID and filter class const $filterElm = $(filterTemplate); - const searchTerm = (typeof this.searchTerm === 'boolean') ? `${this.searchTerm}` : this.searchTerm; - $filterElm.val(searchTerm); + const searchTermInput = searchTerm as string; + + $filterElm.val(searchTermInput); $filterElm.attr('id', `filter-${this.columnDef.id}`); $filterElm.data('columnId', this.columnDef.id); // if there's a search term, we will add the "filled" class for styling purposes - if (this.searchTerm) { + if (searchTerm) { $filterElm.addClass('filled'); } diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/filters/multipleSelectFilter.ts b/aurelia-slickgrid/src/aurelia-slickgrid/filters/multipleSelectFilter.ts index babebcce4..4316d27bf 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/filters/multipleSelectFilter.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/filters/multipleSelectFilter.ts @@ -5,10 +5,11 @@ import { Filter, FilterArguments, FilterCallback, - FilterType, GridOption, HtmlElementPosition, MultipleSelectOption, + OperatorType, + OperatorString, SearchTerm, SelectOption } from './../models/index'; @@ -19,13 +20,11 @@ import * as $ from 'jquery'; export class MultipleSelectFilter implements Filter { $filterElm: any; grid: any; - gridOptions: GridOption; searchTerms: SearchTerm[]; columnDef: Column; callback: FilterCallback; defaultOptions: MultipleSelectOption; isFilled = false; - filterType = FilterType.multipleSelect; labelName: string; valueName: string; enableTranslateLabel = false; @@ -62,6 +61,15 @@ export class MultipleSelectFilter implements Filter { }; } + /** Getter for the Grid Options pulled through the Grid Object */ + private get gridOptions(): GridOption { + return (this.grid && this.grid.getOptions) ? this.grid.getOptions() : {}; + } + + get operator(): OperatorType | OperatorString { + return OperatorType.in; + } + /** * Initialize the filter template */ @@ -75,7 +83,7 @@ export class MultipleSelectFilter implements Filter { this.searchTerms = args.searchTerms || []; if (!this.grid || !this.columnDef || !this.columnDef.filter || !this.columnDef.filter.collection) { - throw new Error(`[Aurelia-SlickGrid] You need to pass a "collection" for the MultipleSelect Filter to work correctly. Also each option should include a value/label pair (or value/labelKey when using Locale). For example:: { filter: type: FilterType.multipleSelect, collection: [{ value: true, label: 'True' }, { value: false, label: 'False'}] }`); + throw new Error(`[Aurelia-SlickGrid] You need to pass a "collection" for the MultipleSelect Filter to work correctly. Also each option should include a value/label pair (or value/labelKey when using Locale). For example: { filter: { model: Filters.multipleSelect, collection: [{ value: true, label: 'True' }, { value: false, label: 'False'}] } }`); } this.enableTranslateLabel = this.columnDef.filter.enableTranslateLabel || false; @@ -83,7 +91,6 @@ export class MultipleSelectFilter implements Filter { this.valueName = (this.columnDef.filter.customStructure) ? this.columnDef.filter.customStructure.value : 'value'; let newCollection = this.columnDef.filter.collection || []; - this.gridOptions = this.grid.getOptions(); // user might want to filter certain items of the collection if (this.gridOptions.params && this.columnDef.filter.collectionFilterBy) { @@ -108,16 +115,13 @@ export class MultipleSelectFilter implements Filter { /** * Clear the filter values */ - clear(triggerFilterChange = true) { + clear() { if (this.$filterElm && this.$filterElm.multipleSelect) { // reload the filter element by it's id, to make sure it's still a valid element (because of some issue in the GraphQL example) // this.$filterElm = $(`#${this.$filterElm[0].id}`); this.$filterElm.multipleSelect('setSelects', []); - - if (triggerFilterChange) { - this.$filterElm.removeClass('filled'); - this.callback(undefined, { columnDef: this.columnDef, operator: 'IN', searchTerms: [] }); - } + this.$filterElm.removeClass('filled'); + this.callback(undefined, { columnDef: this.columnDef, clearFilterTriggered: true }); } } @@ -150,7 +154,7 @@ export class MultipleSelectFilter implements Filter { let options = ''; optionCollection.forEach((option: SelectOption) => { if (!option || (option[this.labelName] === undefined && option.labelKey === undefined)) { - throw new Error(`A collection with value/label (or value/labelKey when using Locale) is required to populate the Select list, for example:: { filter: type: FilterType.multipleSelect, collection: [ { value: '1', label: 'One' } ]')`); + throw new Error(`A collection with value/label (or value/labelKey when using Locale) is required to populate the Select list, for example:: { filter: model: Filters.multipleSelect, collection: [ { value: '1', label: 'One' } ]')`); } const labelKey = (option.labelKey || option[this.labelName]) as string; const selected = (this.findValueInSearchTerms(option[this.valueName]) >= 0) ? 'selected' : ''; diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/filters/selectFilter.ts b/aurelia-slickgrid/src/aurelia-slickgrid/filters/selectFilter.ts index 71410372d..44db2cfa0 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/filters/selectFilter.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/filters/selectFilter.ts @@ -3,9 +3,10 @@ import { inject } from 'aurelia-framework'; import { Column, Filter, - FilterType, FilterArguments, FilterCallback, + OperatorString, + OperatorType, SearchTerm } from './../models/index'; import * as $ from 'jquery'; @@ -14,13 +15,16 @@ import * as $ from 'jquery'; export class SelectFilter implements Filter { $filterElm: any; grid: any; - searchTerm: SearchTerm; + searchTerms: SearchTerm[]; columnDef: Column; callback: FilterCallback; - filterType = FilterType.select; constructor(private i18n: I18N) { } + get operator(): OperatorType | OperatorString { + return OperatorType.equal; + } + /** * Initialize the Filter */ @@ -31,31 +35,41 @@ export class SelectFilter implements Filter { this.grid = args.grid; this.callback = args.callback; this.columnDef = args.columnDef; - this.searchTerm = args.searchTerm || ''; + this.searchTerms = args.searchTerms || []; + + // filter input can only have 1 search term, so we will use the 1st array index if it exist + let searchTerm = (Array.isArray(this.searchTerms) && this.searchTerms[0]) || ''; + if (typeof searchTerm === 'boolean' || typeof searchTerm === 'number') { + searchTerm = `${searchTerm}`; + } // step 1, create HTML string template const filterTemplate = this.buildTemplateHtmlString(); // step 2, create the DOM Element of the filter & initialize it if searchTerm is filled - this.$filterElm = this.createDomElement(filterTemplate); + this.$filterElm = this.createDomElement(filterTemplate, searchTerm); // step 3, subscribe to the change event and run the callback when that happens // also add/remove "filled" class for styling purposes this.$filterElm.change((e: any) => { - (e && e.target && e.target.value) ? this.$filterElm.addClass('filled') : this.$filterElm.removeClass('filled'); - this.callback(e, { columnDef: this.columnDef, operator: 'EQ' }); + const value = e && e.target && e.target.value || ''; + if (!value || value === '') { + this.callback(e, { columnDef: this.columnDef, clearFilterTriggered: true }); + this.$filterElm.removeClass('filled'); + } else { + this.$filterElm.addClass('filled'); + this.callback(e, { columnDef: this.columnDef, searchTerms: [value], operator: 'EQ' }); + } }); } /** * Clear the filter values */ - clear(triggerFilterChange = true) { + clear() { if (this.$filterElm) { this.$filterElm.val(''); - if (triggerFilterChange) { - this.$filterElm.trigger('change'); - } + this.$filterElm.trigger('change'); } } @@ -82,14 +96,11 @@ export class SelectFilter implements Filter { // ------------------ private buildTemplateHtmlString() { - if (!this.columnDef || !this.columnDef.filter || (!this.columnDef.filter.collection && !this.columnDef.filter.selectOptions)) { - throw new Error(`[Aurelia-SlickGrid] You need to pass a "collection" for the Select Filter to work correctly. Also each option should include a value/label pair (or value/labelKey when using Locale). For example:: { filter: type: FilterType.select, collection: [{ value: true, label: 'True' }, { value: false, label: 'False'}] }`); - } - if (!this.columnDef.filter.collection && this.columnDef.filter.selectOptions) { - console.warn(`[Aurelia-SlickGrid] The Select Filter "selectOptions" property will de deprecated in future version. Please use the new "collection" property which is more generic and can be used with other Filters (not just Select).`); + if (!this.columnDef || !this.columnDef.filter || !this.columnDef.filter.collection) { + throw new Error(`[Aurelia-SlickGrid] You need to pass a "collection" for the Select Filter to work correctly. Also each option should include a value/label pair (or value/labelKey when using Locale). For example: { filter: { model: Filters.select, collection: [{ value: true, label: 'True' }, { value: false, label: 'False'}] } }`); } - const optionCollection = this.columnDef.filter.collection || this.columnDef.filter.selectOptions || []; + const optionCollection = this.columnDef.filter.collection || []; const labelName = (this.columnDef.filter.customStructure) ? this.columnDef.filter.customStructure.label : 'label'; const valueName = (this.columnDef.filter.customStructure) ? this.columnDef.filter.customStructure.value : 'value'; const isEnabledTranslate = (this.columnDef.filter.enableTranslateLabel) ? this.columnDef.filter.enableTranslateLabel : false; @@ -97,7 +108,7 @@ export class SelectFilter implements Filter { let options = ''; optionCollection.forEach((option: any) => { if (!option || (option[labelName] === undefined && option.labelKey === undefined)) { - throw new Error(`A collection with value/label (or value/labelKey when using Locale) is required to populate the Select list, for example:: { filter: type: FilterType.select, collection: [ { value: '1', label: 'One' } ]')`); + throw new Error(`A collection with value/label (or value/labelKey when using Locale) is required to populate the Select list, for example: { filter: { model: Filters.select, collection: [ { value: '1', label: 'One' } ] } }`); } const labelKey = option.labelKey || option[labelName]; const textLabel = ((option.labelKey || isEnabledTranslate) && this.i18n && typeof this.i18n.tr === 'function') ? this.i18n.tr(labelKey || ' ') : labelKey; @@ -110,14 +121,15 @@ export class SelectFilter implements Filter { * From the html template string, create a DOM element * @param filterTemplate */ - private createDomElement(filterTemplate: string) { + private createDomElement(filterTemplate: string, searchTerm?: SearchTerm) { const $headerElm = this.grid.getHeaderRowColumn(this.columnDef.id); $($headerElm).empty(); // create the DOM element & add an ID and filter class const $filterElm = $(filterTemplate); - const searchTerm = (typeof this.searchTerm === 'boolean') ? `${this.searchTerm}` : this.searchTerm; - $filterElm.val(searchTerm || ''); + const searchTermInput = (searchTerm || '') as string; + + $filterElm.val(searchTermInput); $filterElm.attr('id', `filter-${this.columnDef.id}`); $filterElm.data('columnId', this.columnDef.id); diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/filters/singleSelectFilter.ts b/aurelia-slickgrid/src/aurelia-slickgrid/filters/singleSelectFilter.ts index 88d66a907..ac88bd976 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/filters/singleSelectFilter.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/filters/singleSelectFilter.ts @@ -3,12 +3,13 @@ import { inject } from 'aurelia-framework'; import { Column, Filter, - FilterType, FilterArguments, FilterCallback, GridOption, HtmlElementPosition, MultipleSelectOption, + OperatorString, + OperatorType, SearchTerm, SelectOption } from './../models/index'; @@ -19,12 +20,10 @@ import * as $ from 'jquery'; export class SingleSelectFilter implements Filter { $filterElm: any; grid: any; - gridOptions: GridOption; - searchTerm: SearchTerm; + searchTerms: SearchTerm[]; columnDef: Column; callback: FilterCallback; defaultOptions: MultipleSelectOption; - filterType = FilterType.singleSelect; isFilled = false; labelName: string; valueName: string; @@ -49,11 +48,20 @@ export class SingleSelectFilter implements Filter { this.isFilled = false; this.$filterElm.removeClass('filled').siblings('div .search-filter').removeClass('filled'); } - this.callback(undefined, { columnDef: this.columnDef, operator: 'EQ', searchTerm: selectedItem }); + this.callback(undefined, { columnDef: this.columnDef, operator: 'EQ', searchTerms: [selectedItem] }); } }; } + get operator(): OperatorType | OperatorString { + return OperatorType.equal; + } + + /** Getter for the Grid Options pulled through the Grid Object */ + private get gridOptions(): GridOption { + return (this.grid && this.grid.getOptions) ? this.grid.getOptions() : {}; + } + /** * Initialize the Filter */ @@ -64,10 +72,10 @@ export class SingleSelectFilter implements Filter { this.grid = args.grid; this.callback = args.callback; this.columnDef = args.columnDef; - this.searchTerm = args.searchTerm || ''; + this.searchTerms = args.searchTerms || []; if (!this.grid || !this.columnDef || !this.columnDef.filter || !this.columnDef.filter.collection) { - throw new Error(`[Aurelia-SlickGrid] You need to pass a "collection" for the MultipleSelect Filter to work correctly. Also each option should include a value/label pair (or value/labelKey when using Locale). For example:: { filter: type: FilterType.multipleSelect, collection: [{ value: true, label: 'True' }, { value: false, label: 'False'}] }`); + throw new Error(`[Aurelia-SlickGrid] You need to pass a "collection" for the MultipleSelect Filter to work correctly. Also each option should include a value/label pair (or value/labelKey when using Locale). For example: { filter: { model: Filters.singleSelect, collection: [{ value: true, label: 'True' }, { value: false, label: 'False'}] } }`); } this.enableTranslateLabel = this.columnDef.filter.enableTranslateLabel || false; @@ -75,7 +83,6 @@ export class SingleSelectFilter implements Filter { this.valueName = (this.columnDef.filter.customStructure) ? this.columnDef.filter.customStructure.value : 'value'; let newCollection = this.columnDef.filter.collection || []; - this.gridOptions = this.grid.getOptions(); // user might want to filter certain items of the collection if (this.gridOptions.params && this.columnDef.filter.collectionFilterBy) { @@ -89,8 +96,13 @@ export class SingleSelectFilter implements Filter { newCollection = this.collectionService.sortCollection(newCollection, sortBy, this.enableTranslateLabel); } + let searchTerm = (Array.isArray(this.searchTerms) && this.searchTerms[0]) || ''; + if (typeof searchTerm === 'boolean' || typeof searchTerm === 'number') { + searchTerm = `${searchTerm}`; + } + // step 1, create HTML string template - const filterTemplate = this.buildTemplateHtmlString(newCollection || []); + const filterTemplate = this.buildTemplateHtmlString(newCollection || [], searchTerm); // step 2, create the DOM Element of the filter & pre-load search term this.createDomElement(filterTemplate); @@ -99,15 +111,12 @@ export class SingleSelectFilter implements Filter { /** * Clear the filter values */ - clear(triggerFilterChange = true) { + clear() { if (this.$filterElm && this.$filterElm.multipleSelect) { // reload the filter element by it's id, to make sure it's still a valid element (because of some issue in the GraphQL example) // this.$filterElm = $(`#${this.$filterElm[0].id}`); this.$filterElm.multipleSelect('setSelects', []); - - if (triggerFilterChange) { - this.callback(undefined, { columnDef: this.columnDef, operator: 'IN', searchTerm: undefined }); - } + this.callback(undefined, { columnDef: this.columnDef, clearFilterTriggered: true }); } } @@ -123,7 +132,7 @@ export class SingleSelectFilter implements Filter { /** * Set value(s) on the DOM element */ - setValues(values: SearchTerm | SearchTerm[]) { + setValues(values: SearchTerm[]) { if (values) { values = Array.isArray(values) ? values : [values]; this.$filterElm.multipleSelect('setSelects', values); @@ -137,15 +146,15 @@ export class SingleSelectFilter implements Filter { /** * Create the HTML template as a string */ - private buildTemplateHtmlString(optionCollection: any[]) { + private buildTemplateHtmlString(optionCollection: any[], searchTerm?: SearchTerm) { let options = ''; optionCollection.forEach((option: SelectOption) => { if (!option || (option[this.labelName] === undefined && option.labelKey === undefined)) { - throw new Error(`A collection with value/label (or value/labelKey when using Locale) is required to populate the Select list, for example:: { filter: type: FilterType.singleSelect, collection: [ { value: '1', label: 'One' } ]')`); + throw new Error(`A collection with value/label (or value/labelKey when using Locale) is required to populate the Select list, for example: { filter: { model: Filter.singleSelect, collection: [ { value: '1', label: 'One' } ] } }`); } const labelKey = (option.labelKey || option[this.labelName]) as string; - const selected = (option[this.valueName] === this.searchTerm) ? 'selected' : ''; + const selected = (option[this.valueName] === searchTerm) ? 'selected' : ''; const textLabel = ((option.labelKey || this.enableTranslateLabel) && this.i18n && typeof this.i18n.tr === 'function') ? this.i18n.tr(labelKey || ' ') : labelKey; // html text of each select option diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/formatters/collectionEditorFormatter.ts b/aurelia-slickgrid/src/aurelia-slickgrid/formatters/collectionEditorFormatter.ts new file mode 100644 index 000000000..94f692b04 --- /dev/null +++ b/aurelia-slickgrid/src/aurelia-slickgrid/formatters/collectionEditorFormatter.ts @@ -0,0 +1,27 @@ +import { arrayToCsvFormatter } from './arrayToCsvFormatter'; +import { Column, Formatter } from './../models/index'; +import { findOrDefault } from '../services/index'; + +/** + * A formatter to show the label property value of a internalColumnEditor collection + */ +export const collectionEditorFormatter: Formatter = (row: number, cell: number, value: any, columnDef: Column, dataContext: any) => { + if (!value || !columnDef || !columnDef.internalColumnEditor || !columnDef.internalColumnEditor.collection + || !columnDef.internalColumnEditor.collection.length) { + return ''; + } + + const { internalColumnEditor, internalColumnEditor: { collection } } = columnDef; + const labelName = (internalColumnEditor.customStructure) ? internalColumnEditor.customStructure.label : 'label'; + const valueName = (internalColumnEditor.customStructure) ? internalColumnEditor.customStructure.value : 'value'; + + if (Array.isArray(value)) { + return arrayToCsvFormatter(row, + cell, + value.map((v: any) => findOrDefault(collection, (c: any) => c[valueName] === v)[labelName]), + columnDef, + dataContext); + } + + return findOrDefault(collection, (c: any) => c[valueName] === value)[labelName] || ''; +}; diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/formatters/index.ts b/aurelia-slickgrid/src/aurelia-slickgrid/formatters/index.ts index 28272fa70..4c6549781 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/formatters/index.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/formatters/index.ts @@ -4,6 +4,7 @@ import { boldFormatter } from './boldFormatter'; import { checkboxFormatter } from './checkboxFormatter'; import { checkmarkFormatter } from './checkmarkFormatter'; import { collectionFormatter } from './collectionFormatter'; +import { collectionEditorFormatter } from './collectionEditorFormatter'; import { complexObjectFormatter } from './complexObjectFormatter'; import { dateIsoFormatter } from './dateIsoFormatter'; import { dateTimeIsoAmPmFormatter } from './dateTimeIsoAmPmFormatter'; @@ -57,6 +58,15 @@ export const Formatters = { */ collection: collectionFormatter, + /** + * Looks up values from the columnDefinition.editor.collection property and displays the label in CSV or string format + * @example + * // the grid will display 'foo' and 'bar' and not 1 and 2 from your dataset + * { params: { collection: [{ value: 1, label: 'foo'}, {value: 2, label: 'bar' }] }} + * const dataset = [{ value: 1 },{ value: 2 }]; + */ + collectionEditor: collectionEditorFormatter, + /** Takes a Date object and displays it as an ISO Date format */ dateIso: dateIsoFormatter, diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/formatters/translateBooleanFormatter.ts b/aurelia-slickgrid/src/aurelia-slickgrid/formatters/translateBooleanFormatter.ts index 10f8d0b2f..afa59f1c3 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/formatters/translateBooleanFormatter.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/formatters/translateBooleanFormatter.ts @@ -4,13 +4,11 @@ import { I18N } from 'aurelia-i18n'; /** Takes a boolean value, cast it to upperCase string and finally translates (i18n) it */ export const translateBooleanFormatter: Formatter = (row: number, cell: number, value: any, columnDef: Column, dataContext: any, grid: any) => { const gridOptions = (grid && typeof grid.getOptions === 'function') ? grid.getOptions() : {}; - const columnParams = columnDef.params || {}; - const gridParams = gridOptions.params || {}; - const i18n = gridParams.i18n || columnParams.i18n; + const i18n = gridOptions.i18n || (columnDef.params && columnDef.params.i18n); if (!i18n || typeof i18n.tr !== 'function') { - throw new Error(`The translate formatter requires the "I18N" Service to be provided as a Grid Options or Column Definition "params". - For example: this.gridOptions = { enableTranslate: true, params: { i18n: this.i18n }}`); + throw new Error(`The translate formatter requires the "I18N" Service to be provided as a Grid Options or Column Definition "i18n". + For example: this.gridOptions = { enableTranslate: true, i18n: this.i18n }`); } // make sure the value is a string (for example a boolean value would throw an error) diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/formatters/translateFormatter.ts b/aurelia-slickgrid/src/aurelia-slickgrid/formatters/translateFormatter.ts index 821b5c128..c9529bff0 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/formatters/translateFormatter.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/formatters/translateFormatter.ts @@ -1,15 +1,13 @@ import { Column, Formatter } from './../models/index'; -/** Takes a cell value and translates it (i18n). Requires an instance of the I18N Service:: `params: { i18n: this.i18n } */ +/** Takes a cell value and translates it (i18n). Requires an instance of the I18N Service:: `i18n: this.i18n` */ export const translateFormatter: Formatter = (row: number, cell: number, value: any, columnDef: Column, dataContext: any, grid: any) => { const gridOptions = (grid && typeof grid.getOptions === 'function') ? grid.getOptions() : {}; - const columnParams = columnDef.params || {}; - const gridParams = gridOptions.params || {}; - const i18n = gridParams.i18n || columnParams.i18n; + const i18n = gridOptions.i18n || (columnDef.params && columnDef.params.i18n); if (!i18n || typeof i18n.tr !== 'function') { - throw new Error(`The translate formatter requires the "I18N" Service to be provided as a Grid Options or Column Definition "params". - For example: this.gridOptions = { enableTranslate: true, params: { i18n: this.i18n }}`); + throw new Error(`The translate formatter requires the "I18N" Service to be provided as a Grid Options or Column Definition "i18n". + For example: this.gridOptions = { enableTranslate: true, i18n: this.i18n }`); } // make sure the value is a string (for example a boolean value would throw an error) diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/global-grid-options.ts b/aurelia-slickgrid/src/aurelia-slickgrid/global-grid-options.ts index 7a8984cb8..77e4a49ea 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/global-grid-options.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/global-grid-options.ts @@ -1,4 +1,5 @@ -import { FilterType, DelimiterType, FileType, GridOption } from './models/index'; +import { Filters } from './filters/index'; +import { DelimiterType, FileType, GridOption } from './models/index'; /** * Default Options that can be passed to the Aurelia-Slickgrid @@ -26,7 +27,7 @@ export const GlobalGridOptions: GridOption = { defaultAureliaEventPrefix: 'asg', defaultSlickgridEventPrefix: 'sg', defaultFilterPlaceholder: '🔍', // magnifying glass icon - defaultFilterType: FilterType.input, + defaultFilter: Filters.input, enableAutoResize: true, enableHeaderMenu: true, enableRowSelection: true, @@ -51,7 +52,13 @@ export const GlobalGridOptions: GridOption = { }, forceFitColumns: false, gridMenu: { + hideClearAllFiltersCommand: false, + hideClearAllSortingCommand: false, + hideExportCsvCommand: false, + hideExportTextDelimitedCommand: true, hideForceFitButton: false, + hideRefreshDatasetCommand: false, + hideToggleFilterCommand: false, hideSyncResizeButton: true, iconCssClass: 'fa fa-bars', iconClearAllFiltersCommand: 'fa fa-filter text-danger', @@ -62,11 +69,6 @@ export const GlobalGridOptions: GridOption = { iconToggleFilterCommand: 'fa fa-random', menuWidth: 16, resizeOnShowHeaderRow: true, - showClearAllFiltersCommand: true, - showClearAllSortingCommand: true, - showExportCsvCommand: true, - showRefreshDatasetCommand: true, - showToggleFilterCommand: true }, headerMenu: { autoAlign: true, diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/index.ts b/aurelia-slickgrid/src/aurelia-slickgrid/index.ts index 6c3b2a665..1088975ea 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/index.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/index.ts @@ -2,12 +2,28 @@ import { PLATFORM } from 'aurelia-pal'; import { AureliaSlickgridCustomElement } from './aurelia-slickgrid'; import { SlickPaginationCustomElement } from './slick-pagination'; import { SlickgridConfig } from './slickgrid-config'; -import { Filters, PLUGIN_NAME as FILTER_PLUGIN_NAME } from './filters/index'; +import { Filters } from './filters/index'; + +// import all Services separately +import { + CollectionService, + ControlAndPluginService, + ExportService, + FilterService, + GraphqlService, + GridEventService, + GridService, + GridStateService, + GridOdataService, + GroupingAndColspanService, + OdataService, + ResizerService, + SortService, +} from './services/index'; // expose all public classes // aggregators, editors, formatters, services... export * from './models/index'; -export * from './services/index'; export * from './formatters/index'; export * from './grouping-formatters/index'; export * from './sorters/index'; @@ -17,17 +33,20 @@ export * from './editors/index'; export * from './filter-conditions/index'; export * from './filters/index'; +// export the Backend Services +export { GraphqlService, GridOdataService } from './services/index'; + export function configure(aurelia: any, callback: any) { aurelia.globalResources(PLATFORM.moduleName('./aurelia-slickgrid')); aurelia.globalResources(PLATFORM.moduleName('./slick-pagination')); // must register a transient so the container will get a new instance everytime - aurelia.container.registerTransient(FILTER_PLUGIN_NAME, Filters.compoundDate); - aurelia.container.registerTransient(FILTER_PLUGIN_NAME, Filters.compoundInput); - aurelia.container.registerTransient(FILTER_PLUGIN_NAME, Filters.input); - aurelia.container.registerTransient(FILTER_PLUGIN_NAME, Filters.multipleSelect); - aurelia.container.registerTransient(FILTER_PLUGIN_NAME, Filters.singleSelect); - aurelia.container.registerTransient(FILTER_PLUGIN_NAME, Filters.select); + aurelia.container.registerTransient(Filters.compoundDate); + aurelia.container.registerTransient(Filters.compoundInput); + aurelia.container.registerTransient(Filters.input); + aurelia.container.registerTransient(Filters.multipleSelect); + aurelia.container.registerTransient(Filters.singleSelect); + aurelia.container.registerTransient(Filters.select); const config = new SlickgridConfig(); diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/models/aureliaGridInstance.interface.ts b/aurelia-slickgrid/src/aurelia-slickgrid/models/aureliaGridInstance.interface.ts new file mode 100644 index 000000000..2d8652985 --- /dev/null +++ b/aurelia-slickgrid/src/aurelia-slickgrid/models/aureliaGridInstance.interface.ts @@ -0,0 +1,50 @@ +import { BackendService } from './../models'; +import { + ControlAndPluginService, + ExportService, + FilterService, + GridService, + GridEventService, + GridStateService, + GroupingAndColspanService, + ResizerService, + SortService +} from '../services'; + +export interface AureliaGridInstance { + /** Slick DataView object */ + dataView: any; + + /** Slick Grid object */ + slickGrid: any; + + /** Backend Service, when available */ + backendService?: BackendService; + + /** Plugin (and Control) Service */ + pluginService: ControlAndPluginService; + + /** Export Service */ + exportService: ExportService; + + /** Filter Service */ + filterService: FilterService; + + /** Grid Service (grid extra functionalities) */ + gridService: GridService; + + /** Grid Events Service */ + gridEventService: GridEventService; + + /** Grid State Service */ + gridStateService: GridStateService; + + /** Grouping (and colspan) Service */ + groupingService: GroupingAndColspanService; + + /** Resizer Service (including auto-resize) */ + resizerService: ResizerService; + + /** Sort Service */ + sortService: SortService; +} diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/models/backendService.interface.ts b/aurelia-slickgrid/src/aurelia-slickgrid/models/backendService.interface.ts index 1b27b1bf4..e5b579a9d 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/models/backendService.interface.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/models/backendService.interface.ts @@ -24,9 +24,6 @@ export interface BackendService { /** initialize the backend service with certain options */ init?: (serviceOptions?: BackendServiceOption, pagination?: Pagination, grid?: any) => void; - /** DEPRECATED, please use "init()" instead */ - initOptions?: (serviceOptions?: BackendServiceOption, pagination?: Pagination, gridOptions?: GridOption, columnDefinitions?: Column[]) => void; - /** Get the dataset name */ getDatasetName?: () => string; @@ -37,7 +34,7 @@ export interface BackendService { getCurrentPagination?: () => CurrentPagination; /** Get the Sorters that are currently used by the grid */ - getCurrentSorters?: () => ColumnFilters | CurrentFilter[]; + getCurrentSorters?: () => CurrentSorter[]; /** Reset the pagination options */ resetPaginationOptions: () => void; @@ -59,11 +56,11 @@ export interface BackendService { // ----------------- /** Execute when any of the filters changed */ - onFilterChanged: (event: Event, args: FilterChangedArgs) => Promise; + processOnFilterChanged: (event: Event, args: FilterChangedArgs) => Promise; /** Execute when the pagination changed */ - onPaginationChanged: (event: Event | undefined, args: PaginationChangedArgs) => string; + processOnPaginationChanged: (event: Event | undefined, args: PaginationChangedArgs) => string; /** Execute when any of the sorters changed */ - onSortChanged: (event: Event | null, args: SortChangedArgs) => string; + processOnSortChanged: (event: Event | null, args: SortChangedArgs) => string; } diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/models/backendServiceOption.interface.ts b/aurelia-slickgrid/src/aurelia-slickgrid/models/backendServiceOption.interface.ts index f2524d81a..ecd5bfffd 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/models/backendServiceOption.interface.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/models/backendServiceOption.interface.ts @@ -23,7 +23,4 @@ export interface BackendServiceOption { * users (first: 20, offset: 10, userId: 123) { ... } */ extraQueryArguments?: QueryArgument[]; - - /** Backend Service API callback definitions */ - onBackendEventApi?: BackendEventChanged; } diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/models/column.interface.ts b/aurelia-slickgrid/src/aurelia-slickgrid/models/column.interface.ts index a997fd34f..34d590552 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/models/column.interface.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/models/column.interface.ts @@ -1,5 +1,5 @@ +import { ColumnEditor } from './columnEditor.interface'; import { ColumnFilter } from './columnFilter.interface'; -import { Editor } from './editor.interface'; import { FieldType } from './fieldType.enum'; import { Formatter } from './formatter.interface'; import { GroupTotalsFormatter } from './groupTotalsFormatter.interface'; @@ -27,8 +27,8 @@ export interface Column { /** Do we want default sort to be ascending? True by default */ defaultSortAsc?: boolean; - /** Inline editor for the cell value */ - editor?: Editor | any; + /** Any inline editor function that implements Editor for the cell value or ColumnEditor */ + editor?: any | ColumnEditor; /** Default to false, which leads to exclude the column from the export? */ excludeFromExport?: boolean; @@ -108,20 +108,10 @@ export interface Column { /** ID of the column, each row have to be unique or SlickGrid will throw an error. */ id: number | string; - /** is the column editable? Goes with grid option "editable: true". */ - isEditable?: boolean; - - /** is the field hidden? (part of the dataset but not shown in the grid/UI) */ - isHidden?: boolean; - - /** catchall for meta info */ - json?: any; - - /** a column key */ - key?: string; - - /** is the column manually sizable? */ - manuallySized?: boolean; + /** + * @internal used internally by Aurelia-Slickgrid, to copy over the Column Editor Options + */ + internalColumnEditor?: any; /** Maximum Width of the column in pixels (number only). */ maxWidth?: number; @@ -133,10 +123,10 @@ export interface Column { name?: string; /** an event that can be used for triggering an action after a cell change */ - onCellChange?: (args: OnEventArgs) => void; + onCellChange?: (e: Event, args: OnEventArgs) => void; /** an event that can be used for triggering an action after a cell click */ - onCellClick?: (args: OnEventArgs) => void; + onCellClick?: (e: Event, args: OnEventArgs) => void; /** column output type */ outputType?: FieldType; @@ -168,9 +158,6 @@ export interface Column { /** Is the column selectable? Goes with grid option "enableCellNavigation: true". */ selectable?: boolean; - /** do we want to show hidden column? */ - showHidden?: boolean; - /** Is the column sortable? Goes with grid option "enableSorting: true". */ sortable?: boolean; diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/models/columnEditor.interface.ts b/aurelia-slickgrid/src/aurelia-slickgrid/models/columnEditor.interface.ts new file mode 100644 index 000000000..c8f082db2 --- /dev/null +++ b/aurelia-slickgrid/src/aurelia-slickgrid/models/columnEditor.interface.ts @@ -0,0 +1,38 @@ +import { + CollectionFilterBy, + CollectionSortBy, + MultipleSelectOption +} from './../models/index'; + +export interface ColumnEditor { + /** Any inline editor function that implements Editor for the cell */ + model?: any; + + collection?: any[]; + + /** We could filter some items from the collection */ + collectionFilterBy?: CollectionFilterBy; + + /** We could sort the collection by their value, or by translated value when enableTranslateLabel is True */ + collectionSortBy?: CollectionSortBy; + + /** Options that could be provided to the Editor, example: { container: 'body', maxHeight: 250} */ + editorOptions?: MultipleSelectOption | any; + + /** Do we want the Editor to handle translation (localization)? */ + enableTranslateLabel?: boolean; + + /** A custom structure can be used instead of the default label/value pair. Commonly used with Select/Multi-Select Editor */ + customStructure?: { + label: string; + value: string; + }; + + /** + * Use "params" to pass any type of arguments to your Custom Editor + * or regular Editor like the Editors.float + * for example, to pass the option collection to a select Filter we can type this: + * params: { decimalPlaces: 2 } + */ + params?: any; +} diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/models/columnFilter.interface.ts b/aurelia-slickgrid/src/aurelia-slickgrid/models/columnFilter.interface.ts index 600c9a028..c78ab08da 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/models/columnFilter.interface.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/models/columnFilter.interface.ts @@ -2,9 +2,6 @@ import { CollectionFilterBy, CollectionSortBy, Column, - Filter, - FilterType, - FormElementType, MultipleSelectOption, OperatorString, OperatorType, @@ -21,20 +18,14 @@ export interface ColumnFilter { /** Column Definition */ columnDef?: Column; - /** Custom Filter */ - customFilter?: Filter; - - /** Search term (singular) */ - searchTerm?: SearchTerm; - /** Search terms (collection) */ searchTerms?: SearchTerm[]; /** Operator to use when filtering (>, >=, EQ, IN, ...) */ operator?: OperatorType | OperatorString; - /** Filter Type to use (input, multipleSelect, singleSelect, select, custom) */ - type?: FilterType | FormElementType | string; + /** Filter to use (input, multipleSelect, singleSelect, select, custom) */ + model?: any; /** A collection of items/options (commonly used with a Select/Multi-Select Filter) */ collection?: any[]; @@ -48,9 +39,6 @@ export interface ColumnFilter { /** Options that could be provided to the Filter, example: { container: 'body', maxHeight: 250} */ filterOptions?: MultipleSelectOption | any; - /** DEPRECATED, please use "collection" instead which is more generic and not specific to a Select Filter. Refer to the Select Filter Wiki page for more info */ - selectOptions?: any[]; - /** Do we want the Filter to handle translation (localization)? */ enableTranslateLabel?: boolean; @@ -61,8 +49,8 @@ export interface ColumnFilter { }; /** - * Use "params" to pass any type of arguments to your Custom Filter (type: FilterType.custom) - * for example, to pass the option collection to a select Filter we can type this: + * Use "params" to pass any type of arguments to your Custom Filter + * for example, to pass a second collection to a select Filter we can type this: * params: { options: [{ value: true, label: 'True' }, { value: true, label: 'True'} ]} */ params?: any; diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/models/currentColumn.interface.ts b/aurelia-slickgrid/src/aurelia-slickgrid/models/currentColumn.interface.ts new file mode 100644 index 000000000..b6e5892cb --- /dev/null +++ b/aurelia-slickgrid/src/aurelia-slickgrid/models/currentColumn.interface.ts @@ -0,0 +1,15 @@ +import { OperatorString, OperatorType, SearchTerm } from './../models/index'; + +export interface CurrentColumn { + /** Column id (in the column definitions) */ + columnId: string; + + /** Column CSS Class */ + cssClass?: string; + + /** Header CSS Class */ + headerCssClass?: string; + + /** Column width */ + width?: number; +} diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/models/currentFilter.interface.ts b/aurelia-slickgrid/src/aurelia-slickgrid/models/currentFilter.interface.ts index 607ba7f0e..ede122196 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/models/currentFilter.interface.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/models/currentFilter.interface.ts @@ -1,8 +1,12 @@ import { OperatorString, OperatorType, SearchTerm } from './../models/index'; export interface CurrentFilter { + /** Column id (in the column definitions) */ columnId: string; + + /** Fitler operator or use default operator when not provided */ operator?: OperatorType | OperatorString; - searchTerm?: SearchTerm; + + /** Filter search terms */ searchTerms?: SearchTerm[]; } diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/models/currentPagination.interface.ts b/aurelia-slickgrid/src/aurelia-slickgrid/models/currentPagination.interface.ts index edcb44831..8b81f0cd5 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/models/currentPagination.interface.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/models/currentPagination.interface.ts @@ -1,4 +1,7 @@ export interface CurrentPagination { + /** Grid page number */ pageNumber: number; + + /** Grid page size */ pageSize: number; } diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/models/currentSorter.interface.ts b/aurelia-slickgrid/src/aurelia-slickgrid/models/currentSorter.interface.ts index dc3620ee4..a73230111 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/models/currentSorter.interface.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/models/currentSorter.interface.ts @@ -1,6 +1,6 @@ import { SortDirection, SortDirectionString } from './../models/index'; export interface CurrentSorter { - columnId: string; + columnId: string | number; direction: SortDirection | SortDirectionString; } diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/models/extension.interface.ts b/aurelia-slickgrid/src/aurelia-slickgrid/models/extension.interface.ts new file mode 100644 index 000000000..923ecf57f --- /dev/null +++ b/aurelia-slickgrid/src/aurelia-slickgrid/models/extension.interface.ts @@ -0,0 +1,4 @@ +export interface Extension { + name: string; + service: any; +} diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/models/filter.interface.ts b/aurelia-slickgrid/src/aurelia-slickgrid/models/filter.interface.ts index 0497b3327..14c386b45 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/models/filter.interface.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/models/filter.interface.ts @@ -1,8 +1,9 @@ import { - FilterType, FilterCallback, Column, FilterArguments, + OperatorType, + OperatorString, SearchTerm } from './../models/index'; @@ -16,18 +17,15 @@ export interface Filter { /** Callback that will be run after the filter triggers */ callback: FilterCallback; - /** the type of filter used */ - filterType?: FilterType | string; - /** SlickGrid grid object */ grid: any; - /** Defined search term to pre-load */ - searchTerm?: SearchTerm; - /** Array of defined search terms to pre-load */ searchTerms?: SearchTerm[]; + /** The search operator for the filter */ + operator: OperatorType | OperatorString; + /** You can use "params" to pass any types of arguments to your Filter */ params?: any | any[]; diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/models/filterArguments.interface.ts b/aurelia-slickgrid/src/aurelia-slickgrid/models/filterArguments.interface.ts index 01932c012..9ecb9b821 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/models/filterArguments.interface.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/models/filterArguments.interface.ts @@ -12,7 +12,6 @@ export interface FilterArguments { columnDef: Column; callback: FilterCallback; operator?: OperatorType | OperatorString; - searchTerm?: SearchTerm; searchTerms?: SearchTerm[]; i18n?: I18N; params?: any | any[]; diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/models/filterCallback.interface.ts b/aurelia-slickgrid/src/aurelia-slickgrid/models/filterCallback.interface.ts index a9c8fde66..1b1827ce7 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/models/filterCallback.interface.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/models/filterCallback.interface.ts @@ -1,9 +1,9 @@ import { Column, OperatorString, SearchTerm } from './../models/index'; export interface FilterCallbackArg { + clearFilterTriggered?: boolean; columnDef: Column; operator?: OperatorString; - searchTerm?: SearchTerm; searchTerms?: SearchTerm[]; } diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/models/filterChangedArgs.interface.ts b/aurelia-slickgrid/src/aurelia-slickgrid/models/filterChangedArgs.interface.ts index 2ef091cfb..348c5ae23 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/models/filterChangedArgs.interface.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/models/filterChangedArgs.interface.ts @@ -4,6 +4,5 @@ import { SearchTerm } from './searchTerm.type'; export interface FilterChangedArgs { columnFilters: ColumnFilters; grid: any; - searchTerm: SearchTerm; searchTerms: SearchTerm[]; } diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/models/filterConditionOption.interface.ts b/aurelia-slickgrid/src/aurelia-slickgrid/models/filterConditionOption.interface.ts index 7bb279280..5979465ae 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/models/filterConditionOption.interface.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/models/filterConditionOption.interface.ts @@ -6,6 +6,5 @@ export interface FilterConditionOption { cellValueLastChar?: string; fieldType: FieldType; filterSearchType?: FieldType; - searchTerm?: string | number; searchTerms?: string[] | number[]; } diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/models/filterType.enum.ts b/aurelia-slickgrid/src/aurelia-slickgrid/models/filterType.enum.ts deleted file mode 100644 index 1201c547f..000000000 --- a/aurelia-slickgrid/src/aurelia-slickgrid/models/filterType.enum.ts +++ /dev/null @@ -1,22 +0,0 @@ -export enum FilterType { - /** Input Filter type, with a magnifying glass as placeholder */ - input, - - /** Select Filter type, just a regular select dropdown. You might want to try "singleSelect" which has a nicer look and feel. */ - select, - - /** Multiple-Select Filter type */ - multipleSelect, - - /** Single Filter type */ - singleSelect, - - /** Custom Filter type */ - custom, - - /** Compound Date Filter (compound of Operator + Date picker) */ - compoundDate, - - /** Compound Input Filter (compound of Operator + Input) */ - compoundInput, -} diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/models/formElementType.ts b/aurelia-slickgrid/src/aurelia-slickgrid/models/formElementType.ts deleted file mode 100644 index 0d4ab130b..000000000 --- a/aurelia-slickgrid/src/aurelia-slickgrid/models/formElementType.ts +++ /dev/null @@ -1,19 +0,0 @@ -export enum FormElementType { - /** Input Filter type */ - input, - - /** Select Filter type, just a regular select dropdown. You might want to try "singleSelect" which has a nicer look and feel. */ - select, - - /** Multiple-Select Filter type */ - multipleSelect, - - /** Single Filter type */ - singleSelect, - - /** Custom Filter type */ - custom, - - /** TextArea element type */ - textarea -} diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/models/graphqlServiceOption.interface.ts b/aurelia-slickgrid/src/aurelia-slickgrid/models/graphqlServiceOption.interface.ts index c7d95642c..63a7dcdcb 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/models/graphqlServiceOption.interface.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/models/graphqlServiceOption.interface.ts @@ -15,9 +15,6 @@ export interface GraphqlServiceOption extends BackendServiceOption { /** Array of column ids that are included in the column definitions */ columnIds?: string[]; - /** DEPRECATED, please use "columnIds" or "columnDefinitions" instead */ - dataFilters?: string[]; - /** What is the dataset, this is required for the GraphQL query to be built */ datasetName?: string; diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/models/gridMenu.interface.ts b/aurelia-slickgrid/src/aurelia-slickgrid/models/gridMenu.interface.ts index 539204d68..ff725e602 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/models/gridMenu.interface.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/models/gridMenu.interface.ts @@ -10,47 +10,35 @@ export interface GridMenu { /** Defaults to "Columns" which is the title that shows up over the columns */ columnTitle?: string; - /** Link for the displaying the Grid menu icon image (basically the hamburger menu) */ - iconImage?: string; - - /** CSS class for the displaying the Grid menu icon image (basically the hamburger menu) */ - iconCssClass?: string; - - /** Defaults to False, which leads to leaving the menu open after a click */ - leaveOpen?: boolean; - - /** Defaults to 16 pixels (only the number), which is the width in pixels of the Grid Menu icon */ - menuWidth?: number; - /** Defaults to "Force fit columns" which is 1 of the last 2 checkbox title shown at the end of the picker list */ forceFitTitle?: string; - /** Defaults to True, show/hide 1 of the last 2 checkbox at the end of the picker list */ - hideForceFitButton?: boolean; + /** Defaults to false, which will hide the "Clear All Filters" command in the Grid Menu (Grid Option "enableFiltering: true" has to be enabled) */ + hideClearAllFiltersCommand?: boolean; - /** Defaults to True, show/hide 1 of the last 2 checkbox at the end of the picker list */ - hideSyncResizeButton?: boolean; + /** Defaults to false, which will hide the "Clear All Sorting" command in the Grid Menu (Grid Option "enableSorting: true" has to be enabled) */ + hideClearAllSortingCommand?: boolean; - /** Defaults to False, which will resize the Header Row and remove the width of the Grid Menu icon from it's total width. */ - resizeOnShowHeaderRow?: boolean; + /** Defaults to false, which will hide the "Export to CSV" command in the Grid Menu (Grid Option "enableExport: true" has to be enabled) */ + hideExportCsvCommand?: boolean; - /** Defaults to True, which will show the "Clear All Filters" command in the Grid Menu (Grid Option "enableFiltering: true" has to be enabled) */ - showClearAllFiltersCommand?: boolean; + /** Defaults to false, which will hide the "Export to Text Delimited" command in the Grid Menu (Grid Option "enableExport: true" has to be enabled) */ + hideExportTextDelimitedCommand?: boolean; - /** Defaults to True, which will show the "Clear All Sorting" command in the Grid Menu (Grid Option "enableSorting: true" has to be enabled) */ - showClearAllSortingCommand?: boolean; + /** Defaults to false, show/hide 1 of the last 2 checkbox at the end of the picker list */ + hideForceFitButton?: boolean; - /** Defaults to True, which will show the "Export to CSV" command in the Grid Menu (Grid Option "enableExport: true" has to be enabled) */ - showExportCsvCommand?: boolean; + /** Defaults to false, which will hide the "Refresh Dataset" command in the Grid Menu (only works with a Backend Service API) */ + hideRefreshDatasetCommand?: boolean; - /** Defaults to True, which will show the "Export to Text Delimited" command in the Grid Menu (Grid Option "enableExport: true" has to be enabled) */ - showExportTextDelimitedCommand?: boolean; + /** Defaults to false, show/hide 1 of the last 2 checkbox at the end of the picker list */ + hideSyncResizeButton?: boolean; - /** Defaults to True, which will show the "Refresh Dataset" command in the Grid Menu (only works with a Backend Service API) */ - showRefreshDatasetCommand?: boolean; + /** Defaults to false, which will hide the "Toggle Filter Row" command in the Grid Menu (Grid Option "enableFiltering: true" has to be enabled) */ + hideToggleFilterCommand?: boolean; - /** Defaults to True, which will show the "Toggle Filter Row" command in the Grid Menu (Grid Option "enableFiltering: true" has to be enabled) */ - showToggleFilterCommand?: boolean; + /** CSS class for the displaying the Grid menu icon image (basically the hamburger menu) */ + iconCssClass?: string; /** icon for the "Clear All Filters" command */ iconClearAllFiltersCommand?: string; @@ -64,15 +52,30 @@ export interface GridMenu { /** icon for the "Export to Text Delimited" command */ iconExportTextDelimitedCommand?: string; + /** Link for the displaying the Grid menu icon image (basically the hamburger menu) */ + iconImage?: string; + /** icon for the "Refresh Dataset" command */ iconRefreshDatasetCommand?: string; /** icon for the "Toggle Filter Row" command */ iconToggleFilterCommand?: string; + /** Defaults to False, which leads to leaving the menu open after a click */ + leaveOpen?: boolean; + + /** Defaults to 16 pixels (only the number), which is the width in pixels of the Grid Menu icon */ + menuWidth?: number; + + /** Defaults to False, which will resize the Header Row and remove the width of the Grid Menu icon from it's total width. */ + resizeOnShowHeaderRow?: boolean; + /** Defaults to "Synchronous resize" which is 1 of the last 2 checkbox title shown at the end of the picker list */ syncResizeTitle?: string; + // + // Events + /** SlickGrid Event fired before the menu is shown. */ onBeforeMenuShow?: (e: Event, args: any) => void; diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/models/gridOption.interface.ts b/aurelia-slickgrid/src/aurelia-slickgrid/models/gridOption.interface.ts index 6948b2e41..30f265f9f 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/models/gridOption.interface.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/models/gridOption.interface.ts @@ -1,3 +1,4 @@ +import { I18N } from 'aurelia-i18n'; import { AutoResizeOption, BackendEventChanged, @@ -7,7 +8,7 @@ import { CheckboxSelector, EditCommand, ExportOption, - FilterType, + Filter, GridMenu, GridState, HeaderButton, @@ -58,7 +59,7 @@ export interface GridOption { maxToolTipLength: number; }; - /** Backend Service API definition (GraphQL/OData Services), also goes with onBackendEventApi */ + /** Backend Service API definition (GraphQL/OData Services) */ backendServiceApi?: BackendServiceApi; /** CSS class name used to simulate cell flashing */ @@ -100,8 +101,8 @@ export interface GridOption { /** Default placeholder to use in Filters that support placeholder (input, flatpickr) */ defaultFilterPlaceholder?: string; - /** The default filter type to use when none is specified */ - defaultFilterType?: FilterType | string; + /** The default filter model to use when none is specified */ + defaultFilter?: any; /** The default Formatter used */ defaultFormatter?: any; @@ -196,9 +197,6 @@ export interface GridOption { /** Some default options to set for the export service */ exportOptions?: ExportOption; - /** @deprecated Defaults to false, which leads to all Formatters of the grid being evaluated on export. You can also override a column by changing the propery on the column itself */ - exportWithFormatter?: boolean; - /** Defaults to 25, which is the grid footer row panel height */ footerRowHeight?: number; @@ -232,6 +230,9 @@ export interface GridOption { /** Header menu options */ headerMenu?: HeaderMenu; + /** i18n translation service instance */ + i18n?: I18N; + /** Do we leave space for new rows in the DOM visible buffer */ leaveSpaceForNewRows?: boolean; @@ -247,9 +248,6 @@ export interface GridOption { /** Defaults to true, which will display numbers indicating column sort precedence are displayed in the columns when multiple columns selected */ numberedMultiColumnSort?: boolean; - /** DEPRECATED, Please use "backendServiceApi" instead */ - onBackendEventApi?: BackendEventChanged; - /** Pagination options, these are used ONLY with a Backend Service API (GraphQL/OData Services) */ pagination?: Pagination; diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/models/gridState.interface.ts b/aurelia-slickgrid/src/aurelia-slickgrid/models/gridState.interface.ts index 014ecc6b3..d042a89b0 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/models/gridState.interface.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/models/gridState.interface.ts @@ -1,6 +1,9 @@ -import { CurrentFilter, CurrentSorter } from './../models/index'; +import { CurrentColumn, CurrentFilter, CurrentSorter } from './../models/index'; export interface GridState { + /** Columns (and their state: visibility/position) that are currently applied in the grid */ + columns?: CurrentColumn[] | null; + /** Filters (and their state, columnId, searchTerm(s)) that are currently applied in the grid */ filters?: CurrentFilter[] | null; diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/models/gridStateChange.interface.ts b/aurelia-slickgrid/src/aurelia-slickgrid/models/gridStateChange.interface.ts index ca2e9c3d7..1223478af 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/models/gridStateChange.interface.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/models/gridStateChange.interface.ts @@ -1,9 +1,9 @@ -import { CurrentFilter, CurrentSorter, GridState, GridStateType, Pagination } from './../models/index'; +import { Column, CurrentFilter, CurrentSorter, GridState, GridStateType, Pagination } from './../models/index'; export interface GridStateChange { /** Changes that were triggered */ change?: { - newValues: CurrentFilter[] | CurrentSorter[] | Pagination; + newValues: Column[] | CurrentFilter[] | CurrentSorter[] | Pagination; type: GridStateType; }; diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/models/gridStateType.enum.ts b/aurelia-slickgrid/src/aurelia-slickgrid/models/gridStateType.enum.ts index ba1983765..3cb3f2c37 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/models/gridStateType.enum.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/models/gridStateType.enum.ts @@ -1,4 +1,5 @@ export enum GridStateType { + columns = 'columns', filter = 'filter', pagination = 'pagination', sorter = 'sorter' diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/models/index.ts b/aurelia-slickgrid/src/aurelia-slickgrid/models/index.ts index 5c65a10e8..351bd8175 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/models/index.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/models/index.ts @@ -1,4 +1,5 @@ export * from './aggregator.interface'; +export * from './aureliaGridInstance.interface'; export * from './autoResizeOption.interface'; export * from './backendService.interface'; export * from './backendEventChanged.interface'; @@ -10,11 +11,13 @@ export * from './checkboxSelector.interface'; export * from './collectionFilterBy.interface'; export * from './collectionSortBy.interface'; export * from './column.interface'; +export * from './columnEditor.interface'; export * from './columnFilter.interface'; export * from './columnFilters.interface'; export * from './columnPicker.interface'; export * from './columnSort.interface'; export * from './customGridMenu.interface'; +export * from './currentColumn.interface'; export * from './currentFilter.interface'; export * from './currentPagination.interface'; export * from './currentSorter.interface'; @@ -22,6 +25,7 @@ export * from './delimiterType.enum'; export * from './editor.interface'; export * from './editCommand.interface'; export * from './exportOption.interface'; +export * from './extension.interface'; export * from './fieldType.enum'; export * from './fileType.enum'; export * from './filter.interface'; @@ -30,9 +34,7 @@ export * from './filterCallback.interface'; export * from './filterChangedArgs.interface'; export * from './filterCondition.interface'; export * from './filterConditionOption.interface'; -export * from './filterType.enum'; export * from './formatter.interface'; -export * from './formElementType'; export * from './graphqlDatasetFilter.interface'; export * from './graphqlCursorPaginationOption.interface'; export * from './graphqlFilteringOption.interface'; diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/services/controlAndPlugin.service.ts b/aurelia-slickgrid/src/aurelia-slickgrid/services/controlAndPlugin.service.ts index ff4c664de..d10310781 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/services/controlAndPlugin.service.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/services/controlAndPlugin.service.ts @@ -1,4 +1,4 @@ -import { inject } from 'aurelia-framework'; +import { singleton, inject } from 'aurelia-framework'; import { I18N } from 'aurelia-i18n'; import { CellArgs, @@ -6,6 +6,7 @@ import { ColumnSort, CustomGridMenu, DelimiterType, + Extension, FileType, GraphqlResult, GridMenu, @@ -25,13 +26,15 @@ import * as $ from 'jquery'; // using external non-typed js libraries declare var Slick: any; +@singleton(true) @inject(ExportService, FilterService, I18N, SortService) export class ControlAndPluginService { private _dataView: any; private _grid: any; + allColumns: Column[]; visibleColumns: Column[]; areVisibleColumnDifferent = false; - pluginList: { name: string; plugin: any }[] = []; + extensionList: Extension[] = []; // controls & plugins autoTooltipPlugin: any; @@ -67,11 +70,27 @@ export class ControlAndPluginService { this._grid.autosizeColumns(); } - getPlugin(name?: string) { - if (name) { - return this.pluginList.find((p) => p.name === name); - } - return this.pluginList; + /** Get all columns (includes visible and non-visible) */ + getAllColumns(): Column[] { + return this.allColumns || []; + } + + /** Get only visible columns */ + getVisibleColumns(): Column[] { + return this.visibleColumns || []; + } + + /** Get all Extensions */ + getAllExtensions(): Extension[] { + return this.extensionList; + } + + /** + * Get an Extension by it's name + * @param name + */ + getExtensionByName(name: string): Extension | undefined { + return this.extensionList.find((p) => p.name === name); } /** @@ -83,25 +102,36 @@ export class ControlAndPluginService { attachDifferentControlOrPlugins(grid: any, dataView: any, groupItemMetadataProvider: any) { this._grid = grid; this._dataView = dataView; + this.allColumns = this._columnDefinitions; this.visibleColumns = this._columnDefinitions; + // make sure all columns are translated before creating ColumnPicker/GridMenu Controls + // this is to avoid having hidden columns not being translated on first load + if (this._gridOptions.enableTranslate) { + for (const column of this.allColumns) { + if (column.headerKey) { + column.name = this.i18n.tr(column.headerKey); + } + } + } + // Column Picker Control if (this._gridOptions.enableColumnPicker) { this.columnPickerControl = this.createColumnPicker(grid, this._columnDefinitions); - this.pluginList.push({ name: 'ColumnPicker', plugin: this.columnPickerControl }); + this.extensionList.push({ name: 'ColumnPicker', service: this.columnPickerControl }); } // Grid Menu Control if (this._gridOptions.enableGridMenu) { this.gridMenuControl = this.createGridMenu(grid, this._columnDefinitions); - this.pluginList.push({ name: 'GridMenu', plugin: this.gridMenuControl }); + this.extensionList.push({ name: 'GridMenu', service: this.gridMenuControl }); } // Auto Tooltip Plugin if (this._gridOptions.enableAutoTooltip) { this.autoTooltipPlugin = new Slick.AutoTooltips(this._gridOptions.autoTooltipOptions || {}); grid.registerPlugin(this.autoTooltipPlugin); - this.pluginList.push({ name: 'AutoTooltip', plugin: this.autoTooltipPlugin }); + this.extensionList.push({ name: 'AutoTooltip', service: this.autoTooltipPlugin }); } // Grouping Plugin @@ -109,7 +139,7 @@ export class ControlAndPluginService { if (this._gridOptions.enableGrouping) { this.groupItemMetaProviderPlugin = groupItemMetadataProvider || {}; this._grid.registerPlugin(this.groupItemMetaProviderPlugin); - this.pluginList.push({ name: 'GroupItemMetaProvider', plugin: this.groupItemMetaProviderPlugin }); + this.extensionList.push({ name: 'GroupItemMetaProvider', service: this.groupItemMetaProviderPlugin }); } // Checkbox Selector Plugin @@ -117,7 +147,7 @@ export class ControlAndPluginService { // when enabling the Checkbox Selector Plugin, we need to also watch onClick events to perform certain actions // the selector column has to be created BEFORE the grid (else it behaves oddly), but we can only watch grid events AFTER the grid is created grid.registerPlugin(this.checkboxSelectorPlugin); - this.pluginList.push({ name: 'CheckboxSelector', plugin: this.checkboxSelectorPlugin }); + this.extensionList.push({ name: 'CheckboxSelector', service: this.checkboxSelectorPlugin }); // this also requires the Row Selection Model to be registered as well if (!this.rowSelectionPlugin || !grid.getSelectionModel()) { @@ -142,7 +172,7 @@ export class ControlAndPluginService { if (this._gridOptions.enableHeaderButton) { this.headerButtonsPlugin = new Slick.Plugins.HeaderButtons(this._gridOptions.headerButton || {}); grid.registerPlugin(this.headerButtonsPlugin); - this.pluginList.push({ name: 'HeaderButtons', plugin: this.headerButtonsPlugin }); + this.extensionList.push({ name: 'HeaderButtons', service: this.headerButtonsPlugin }); this.headerButtonsPlugin.onCommand.subscribe((e: Event, args: HeaderButtonOnCommandArgs) => { if (this._gridOptions.headerButton && typeof this._gridOptions.headerButton.onCommand === 'function') { @@ -168,11 +198,11 @@ export class ControlAndPluginService { if (Array.isArray(this._gridOptions.registerPlugins)) { this._gridOptions.registerPlugins.forEach((plugin) => { grid.registerPlugin(plugin); - this.pluginList.push({ name: 'generic', plugin }); + this.extensionList.push({ name: 'generic', service: plugin }); }); } else { grid.registerPlugin(this._gridOptions.registerPlugins); - this.pluginList.push({ name: 'generic', plugin: this._gridOptions.registerPlugins }); + this.extensionList.push({ name: 'generic', service: this._gridOptions.registerPlugins }); } } } @@ -235,7 +265,7 @@ export class ControlAndPluginService { grid.setSelectionModel(new Slick.CellSelectionModel()); this.cellExternalCopyManagerPlugin = new Slick.CellExternalCopyManager(pluginOptions); grid.registerPlugin(this.cellExternalCopyManagerPlugin); - this.pluginList.push({ name: 'CellExternalCopyManager', plugin: this.cellExternalCopyManagerPlugin }); + this.extensionList.push({ name: 'CellExternalCopyManager', service: this.cellExternalCopyManagerPlugin }); } /** @@ -245,8 +275,8 @@ export class ControlAndPluginService { */ createColumnPicker(grid: any, columnDefinitions: Column[]) { // localization support for the picker - const forceFitTitle = this._gridOptions.enableTranslate ? this.i18n.tr('FORCE_FIT_COLUMNS') : 'Force fit columns'; - const syncResizeTitle = this._gridOptions.enableTranslate ? this.i18n.tr('SYNCHRONOUS_RESIZE') : 'Synchronous resize'; + const forceFitTitle = this._gridOptions.enableTranslate ? this.getDefaultTranslationByKey('forcefit') : 'Force fit columns'; + const syncResizeTitle = this._gridOptions.enableTranslate ? this.getDefaultTranslationByKey('synch') : 'Synchronous resize'; this._gridOptions.columnPicker = this._gridOptions.columnPicker || {}; this._gridOptions.columnPicker.forceFitTitle = this._gridOptions.columnPicker.forceFitTitle || forceFitTitle; @@ -398,12 +428,12 @@ export class ControlAndPluginService { this.visibleColumns = []; // dispose of each control/plugin if it has a destroy method - this.pluginList.forEach((item) => { - if (item && item.plugin && item.plugin.destroy) { - item.plugin.destroy(); + this.extensionList.forEach((item) => { + if (item && item.service && item.service.destroy) { + item.service.destroy(); } }); - this.pluginList = []; + this.extensionList = []; } /** @@ -411,11 +441,11 @@ export class ControlAndPluginService { * @param grid */ private addGridMenuCustomCommands(grid: any) { - const backendApi = this._gridOptions.backendServiceApi || this._gridOptions.onBackendEventApi || null; + const backendApi = this._gridOptions.backendServiceApi || null; if (this._gridOptions && this._gridOptions.enableFiltering) { // show grid menu: clear all filters - if (this._gridOptions && this._gridOptions.gridMenu && this._gridOptions.gridMenu.showClearAllFiltersCommand && this._gridOptions.gridMenu.customItems && this._gridOptions.gridMenu.customItems.filter((item: CustomGridMenu) => item.command === 'clear-filter').length === 0) { + if (this._gridOptions && this._gridOptions.gridMenu && !this._gridOptions.gridMenu.hideClearAllFiltersCommand && this._gridOptions.gridMenu.customItems && this._gridOptions.gridMenu.customItems.filter((item: CustomGridMenu) => item.command === 'clear-filter').length === 0) { this._gridOptions.gridMenu.customItems.push( { iconCssClass: this._gridOptions.gridMenu.iconClearAllFiltersCommand || 'fa fa-filter text-danger', @@ -427,7 +457,7 @@ export class ControlAndPluginService { ); } // show grid menu: toggle filter row - if (this._gridOptions && this._gridOptions.gridMenu && this._gridOptions.gridMenu.showToggleFilterCommand && this._gridOptions.gridMenu.customItems && this._gridOptions.gridMenu.customItems.filter((item: CustomGridMenu) => item.command === 'toggle-filter').length === 0) { + if (this._gridOptions && this._gridOptions.gridMenu && !this._gridOptions.gridMenu.hideToggleFilterCommand && this._gridOptions.gridMenu.customItems && this._gridOptions.gridMenu.customItems.filter((item: CustomGridMenu) => item.command === 'toggle-filter').length === 0) { this._gridOptions.gridMenu.customItems.push( { iconCssClass: this._gridOptions.gridMenu.iconToggleFilterCommand || 'fa fa-random', @@ -440,7 +470,7 @@ export class ControlAndPluginService { } // show grid menu: refresh dataset - if (this._gridOptions && this._gridOptions.gridMenu && this._gridOptions.gridMenu.showRefreshDatasetCommand && backendApi && this._gridOptions.gridMenu.customItems && this._gridOptions.gridMenu.customItems.filter((item: CustomGridMenu) => item.command === 'refresh-dataset').length === 0) { + if (this._gridOptions && this._gridOptions.gridMenu && !this._gridOptions.gridMenu.hideRefreshDatasetCommand && backendApi && this._gridOptions.gridMenu.customItems && this._gridOptions.gridMenu.customItems.filter((item: CustomGridMenu) => item.command === 'refresh-dataset').length === 0) { this._gridOptions.gridMenu.customItems.push( { iconCssClass: this._gridOptions.gridMenu.iconRefreshDatasetCommand || 'fa fa-refresh', @@ -455,7 +485,7 @@ export class ControlAndPluginService { if (this._gridOptions.enableSorting) { // show grid menu: clear all sorting - if (this._gridOptions && this._gridOptions.gridMenu && this._gridOptions.gridMenu.showClearAllSortingCommand && this._gridOptions.gridMenu.customItems && this._gridOptions.gridMenu.customItems.filter((item: CustomGridMenu) => item.command === 'clear-sorting').length === 0) { + if (this._gridOptions && this._gridOptions.gridMenu && !this._gridOptions.gridMenu.hideClearAllSortingCommand && this._gridOptions.gridMenu.customItems && this._gridOptions.gridMenu.customItems.filter((item: CustomGridMenu) => item.command === 'clear-sorting').length === 0) { this._gridOptions.gridMenu.customItems.push( { iconCssClass: this._gridOptions.gridMenu.iconClearAllSortingCommand || 'fa fa-unsorted text-danger', @@ -469,7 +499,7 @@ export class ControlAndPluginService { } // show grid menu: export to file - if (this._gridOptions && this._gridOptions.enableExport && this._gridOptions.gridMenu && this._gridOptions.gridMenu.showExportCsvCommand && this._gridOptions.gridMenu.customItems && this._gridOptions.gridMenu.customItems.filter((item: CustomGridMenu) => item.command === 'export-csv').length === 0) { + if (this._gridOptions && this._gridOptions.enableExport && this._gridOptions.gridMenu && !this._gridOptions.gridMenu.hideExportCsvCommand && this._gridOptions.gridMenu.customItems && this._gridOptions.gridMenu.customItems.filter((item: CustomGridMenu) => item.command === 'export-csv').length === 0) { this._gridOptions.gridMenu.customItems.push( { iconCssClass: this._gridOptions.gridMenu.iconExportCsvCommand || 'fa fa-download', @@ -481,7 +511,7 @@ export class ControlAndPluginService { ); } // show grid menu: export to text file as tab delimited - if (this._gridOptions && this._gridOptions.enableExport && this._gridOptions.gridMenu && this._gridOptions.gridMenu.showExportTextDelimitedCommand && this._gridOptions.gridMenu.customItems && this._gridOptions.gridMenu.customItems.filter((item: CustomGridMenu) => item.command === 'export-text-delimited').length === 0) { + if (this._gridOptions && this._gridOptions.enableExport && this._gridOptions.gridMenu && !this._gridOptions.gridMenu.hideExportTextDelimitedCommand && this._gridOptions.gridMenu.customItems && this._gridOptions.gridMenu.customItems.filter((item: CustomGridMenu) => item.command === 'export-text-delimited').length === 0) { this._gridOptions.gridMenu.customItems.push( { iconCssClass: this._gridOptions.gridMenu.iconExportTextDelimitedCommand || 'fa fa-download', @@ -557,7 +587,7 @@ export class ControlAndPluginService { /** Call a refresh dataset with a BackendServiceApi */ refreshBackendDataset() { let query; - const backendApi = this._gridOptions.backendServiceApi || this._gridOptions.onBackendEventApi; + const backendApi = this._gridOptions.backendServiceApi; if (!backendApi || !backendApi.service || !backendApi.process) { throw new Error(`BackendServiceApi requires at least a "process" function and a "service" defined`); } @@ -599,43 +629,44 @@ export class ControlAndPluginService { }); } - /** - * Translate the Column Picker and it's last 2 checkboxes - * Note that the only way that seems to work is to destroy and re-create the Column Picker - * Changing only the columnPicker.columnTitle with i18n translate was not enough. - */ + /** Translate the Column Picker and it's last 2 checkboxes */ translateColumnPicker() { - // destroy and re-create the Column Picker which seems to be the only way to translate properly - if (this.columnPickerControl) { - this.columnPickerControl.destroy(); - this.columnPickerControl = null; + // update the properties by pointers, that is the only way to get Grid Menu Control to see the new values + if (this._gridOptions && this._gridOptions.columnPicker) { + this._gridOptions.columnPicker.columnTitle = this.getDefaultTranslationByKey('columns'); + this._gridOptions.columnPicker.forceFitTitle = this.getDefaultTranslationByKey('forcefit'); + this._gridOptions.columnPicker.syncResizeTitle = this.getDefaultTranslationByKey('synch'); } - - this._gridOptions.columnPicker = undefined; - this.createColumnPicker(this._grid, this.visibleColumns); } - /** - * Translate the Grid Menu ColumnTitle and CustomTitle. - * Note that the only way that seems to work is to destroy and re-create the Grid Menu - * Changing only the gridMenu.columnTitle with i18n translate was not enough. - */ + /** Translate the Grid Menu titles and column picker */ translateGridMenu() { - // destroy and re-create the Grid Menu which seems to be the only way to translate properly - this.gridMenuControl.destroy(); - - // reset all Grid Menu options that have translation text & then re-create the Grid Menu and also the custom items array + // update the properties by pointers, that is the only way to get Grid Menu Control to see the new values + // we also need to call the control init so that it takes the new Grid object with latest values if (this._gridOptions && this._gridOptions.gridMenu) { - this._gridOptions.gridMenu = this.resetGridMenuTranslations(this._gridOptions.gridMenu); + this._gridOptions.gridMenu.customItems = []; + this._gridOptions.gridMenu.customTitle = ''; + this._gridOptions.gridMenu.columnTitle = this.getDefaultTranslationByKey('columns'); + this._gridOptions.gridMenu.forceFitTitle = this.getDefaultTranslationByKey('forcefit'); + this._gridOptions.gridMenu.syncResizeTitle = this.getDefaultTranslationByKey('synch'); + + // translate all columns (including non-visible) + for (const column of this.allColumns) { + if (column.headerKey) { + column.name = this.i18n.tr(column.headerKey); + } + } + + // re-create the list of Custom Commands + this.addGridMenuCustomCommands(this._grid); + this.gridMenuControl.init(this._grid); } - this.createGridMenu(this._grid, this.visibleColumns); } /** * Translate the Header Menu titles, we need to loop through all column definition to re-translate them */ translateHeaderMenu() { - // reset all Grid Menu options that have translation text & then re-create the Grid Menu and also the custom items array if (this._gridOptions && this._gridOptions.headerMenu) { this.resetHeaderMenuTranslations(this.visibleColumns); } @@ -646,19 +677,20 @@ export class ControlAndPluginService { * We could optionally pass a locale (that will change currently loaded locale), else it will use current locale * @param locale to use */ - translateColumnHeaders(locale?: string) { + translateColumnHeaders(locale?: boolean | string, newColumnDefinitions?: Column[]) { if (locale) { - this.i18n.setLocale(locale); + this.i18n.setLocale(locale as string); } - for (const column of this._columnDefinitions) { + const columnDefinitions = newColumnDefinitions || this._columnDefinitions; + for (const column of columnDefinitions) { if (column.headerKey) { column.name = this.i18n.tr(column.headerKey); } } // re-render the column headers - this.renderColumnHeaders(); + this.renderColumnHeaders(columnDefinitions); } /** @@ -773,16 +805,16 @@ export class ControlAndPluginService { */ private getDefaultGridMenuOptions(): GridMenu { return { - columnTitle: this.i18n.tr('COLUMNS') || 'Columns', - forceFitTitle: this.i18n.tr('FORCE_FIT_COLUMNS') || 'Force fit columns', - syncResizeTitle: this.i18n.tr('SYNCHRONOUS_RESIZE') || 'Synchronous resize', + columnTitle: this.getDefaultTranslationByKey('columns'), + forceFitTitle: this.getDefaultTranslationByKey('forcefit'), + syncResizeTitle: this.getDefaultTranslationByKey('synch'), iconCssClass: 'fa fa-bars', menuWidth: 18, customTitle: undefined, customItems: [], - showClearAllFiltersCommand: true, - showRefreshDatasetCommand: true, - showToggleFilterCommand: true + hideClearAllFiltersCommand: false, + hideRefreshDatasetCommand: false, + hideToggleFilterCommand: false }; } @@ -798,20 +830,23 @@ export class ControlAndPluginService { }; } - /** - * Reset all the Grid Menu options which have text to translate - * @param gridMenu object - */ - private resetGridMenuTranslations(gridMenu: GridMenu): GridMenu { - // we will reset the custom items array since the commands title have to be translated too (no worries, we will re-create it later) - gridMenu.customItems = []; - delete gridMenu.customTitle; - - gridMenu.columnTitle = this.i18n.tr('COLUMNS') || 'Columns'; - gridMenu.forceFitTitle = this.i18n.tr('FORCE_FIT_COLUMNS') || 'Force fit columns'; - gridMenu.syncResizeTitle = this.i18n.tr('SYNCHRONOUS_RESIZE') || 'Synchronous resize'; - - return gridMenu; + private getDefaultTranslationByKey(key: 'commands' | 'columns' | 'forcefit' | 'synch') { + let output = ''; + switch (key) { + case 'commands': + output = this.i18n.tr('COMMANDS') || 'Commands'; + break; + case 'columns': + output = this.i18n.tr('COLUMNS') || 'Columns'; + break; + case 'forcefit': + output = this.i18n.tr('FORCE_FIT_COLUMNS') || 'Force fit columns'; + break; + case 'synch': + output = this.i18n.tr('SYNCHRONOUS_RESIZE') || 'Synchronous resize'; + break; + } + return output; } /** diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/services/export.service.ts b/aurelia-slickgrid/src/aurelia-slickgrid/services/export.service.ts index 00f56bf81..f18a80f24 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/services/export.service.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/services/export.service.ts @@ -1,5 +1,5 @@ import { EventAggregator } from 'aurelia-event-aggregator'; -import { inject } from 'aurelia-framework'; +import { singleton, inject } from 'aurelia-framework'; import { I18N } from 'aurelia-i18n'; import { CellArgs, @@ -29,6 +29,7 @@ export interface ExportColumnHeader { title: string; } +@singleton(true) @inject(I18N, EventAggregator) export class ExportService { private _lineCarriageReturn = '\n'; @@ -213,7 +214,7 @@ export class ExportService { } // does the user want to evaluate current column Formatter? - const isEvaluatingFormatter = (columnDef.exportWithFormatter !== undefined) ? columnDef.exportWithFormatter : (this._exportOptions.exportWithFormatter || this._gridOptions.exportWithFormatter); + const isEvaluatingFormatter = (columnDef.exportWithFormatter !== undefined) ? columnDef.exportWithFormatter : this._exportOptions.exportWithFormatter; let itemData = ''; diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/services/filter.service.ts b/aurelia-slickgrid/src/aurelia-slickgrid/services/filter.service.ts index e6d085504..7535890f5 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/services/filter.service.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/services/filter.service.ts @@ -1,4 +1,4 @@ -import { inject } from 'aurelia-framework'; +import { singleton, inject } from 'aurelia-framework'; import { EventAggregator } from 'aurelia-event-aggregator'; import { FilterConditions } from './../filter-conditions/index'; import { Filters, FilterFactory } from './../filters/index'; @@ -11,7 +11,6 @@ import { FilterArguments, FilterCallbackArg, FieldType, - FilterType, GridOption, OperatorType, OperatorString, @@ -23,6 +22,7 @@ import * as $ from 'jquery'; // using external non-typed js libraries declare var Slick: any; +@singleton(true) @inject(EventAggregator, FilterFactory) export class FilterService { private _eventHandler = new Slick.EventHandler(); @@ -76,7 +76,7 @@ export class FilterService { } const gridOptions: GridOption = args.grid.getOptions() || {}; - const backendApi = gridOptions.backendServiceApi || gridOptions.onBackendEventApi; + const backendApi = gridOptions.backendServiceApi; if (!backendApi || !backendApi.process || !backendApi.service) { throw new Error(`BackendServiceApi requires at least a "process" function and a "service" defined`); } @@ -87,10 +87,14 @@ export class FilterService { } // call the service to get a query back - const query = await backendApi.service.onFilterChanged(event, args); + const query = await backendApi.service.processOnFilterChanged(event, args); // emit an onFilterChanged event - this.emitFilterChanged('remote'); + if (args && !args.clearFilterTriggered) { + this.emitFilterChanged('remote'); + } else { + console.log('clear triggered', args); + } // await for the Promise to resolve the data const processResult = await backendApi.process(query); @@ -125,7 +129,9 @@ export class FilterService { if (columnId != null) { dataView.refresh(); } - this.emitFilterChanged('local'); + if (args && !args.clearFilterTriggered) { + this.emitFilterChanged('local'); + } }); // subscribe to SlickGrid onHeaderRowCellRendered event to create filter template @@ -136,10 +142,10 @@ export class FilterService { /** Clear the search filters (below the column titles) */ clearFilters() { - this._filters.forEach((filter, index) => { + this._filters.forEach((filter: Filter) => { if (filter && filter.clear) { // clear element and trigger a change - filter.clear(true); + filter.clear(); } }); @@ -150,6 +156,7 @@ export class FilterService { delete this._columnFilters[columnId]; } } + this._columnFilters = {}; // we also need to refresh the dataView and optionally the grid (it's optional since we use DataView) if (this._dataView) { @@ -157,6 +164,9 @@ export class FilterService { this._grid.invalidate(); this._grid.render(); } + + // emit an event when filters are all cleared + this.ea.publish('filterService:filterCleared', {}); } customLocalFilter(dataView: any, item: any, args: any) { @@ -172,8 +182,8 @@ export class FilterService { let cellValue = item[columnDef.queryField || columnDef.queryFieldFilter || columnDef.field]; const searchTerms = (columnFilter && columnFilter.searchTerms) ? columnFilter.searchTerms : null; - let fieldSearchValue = (columnFilter && (columnFilter.searchTerm !== undefined || columnFilter.searchTerm !== null)) ? columnFilter.searchTerm : undefined; + let fieldSearchValue = (Array.isArray(searchTerms) && searchTerms.length === 1) ? searchTerms[0] : ''; if (typeof fieldSearchValue === 'undefined') { fieldSearchValue = ''; } @@ -184,7 +194,7 @@ export class FilterService { const searchTerm = (!!matches) ? matches[2] : ''; const lastValueChar = (!!matches) ? matches[3] : (operator === '*z' ? '*' : ''); - if (searchTerms && searchTerms.length > 0) { + if (searchTerms && searchTerms.length > 1) { fieldSearchValue = searchTerms.join(','); } else if (typeof fieldSearchValue === 'string') { // escaping the search value @@ -194,24 +204,6 @@ export class FilterService { } } - // when using a Filter that is not a custom type, we want to make sure that we have a default operator type - // for example a multiple-select should always be using IN, while a single select will use an EQ - const filterType = (columnDef.filter && columnDef.filter.type) ? columnDef.filter.type : FilterType.input; - if (!operator && filterType !== FilterType.custom) { - switch (filterType) { - case FilterType.select: - case FilterType.multipleSelect: - operator = 'IN'; - break; - case FilterType.singleSelect: - operator = 'EQ'; - break; - default: - operator = operator; - break; - } - } - // no need to query if search value is empty if (searchTerm === '' && !searchTerms) { return true; @@ -240,7 +232,6 @@ export class FilterService { const conditionOptions = { fieldType, searchTerms, - searchTerm, cellValue, operator, cellValueLastChar: lastValueChar, @@ -300,13 +291,13 @@ export class FilterService { if (columnFilter && columnFilter.searchTerms) { filter.searchTerms = columnFilter.searchTerms; - } else { - filter.searchTerm = (columnFilter && (columnFilter.searchTerm !== undefined || columnFilter.searchTerm !== null)) ? columnFilter.searchTerm : undefined; } if (columnFilter.operator) { filter.operator = columnFilter.operator; } - currentFilters.push(filter); + if (Array.isArray(filter.searchTerms) && filter.searchTerms.length > 0 && filter.searchTerms[0] !== '') { + currentFilters.push(filter); + } } } return currentFilters; @@ -314,13 +305,13 @@ export class FilterService { callbackSearchEvent(e: Event | undefined, args: FilterCallbackArg) { if (args) { - const searchTerm = args.searchTerm ? args.searchTerm : ((e && e.target) ? (e.target as HTMLInputElement).value : undefined); - const searchTerms = (args.searchTerms && Array.isArray(args.searchTerms)) ? args.searchTerms : undefined; + const searchTerm = ((e && e.target) ? (e.target as HTMLInputElement).value : undefined); + const searchTerms = (args.searchTerms && Array.isArray(args.searchTerms)) ? args.searchTerms : searchTerm ? [searchTerm] : undefined; const columnDef = args.columnDef || null; const columnId = columnDef ? (columnDef.id || '') : ''; const operator = args.operator || undefined; - if (!searchTerm && (!searchTerms || (Array.isArray(searchTerms) && searchTerms.length === 0))) { + if (!searchTerms || (Array.isArray(searchTerms) && searchTerms.length === 0)) { // delete the property from the columnFilters when it becomes empty // without doing this, it would leave an incorrect state of the previous column filters when filtering on another column delete this._columnFilters[columnId]; @@ -329,7 +320,6 @@ export class FilterService { const colFilter: ColumnFilter = { columnId: colId, columnDef, - searchTerm, searchTerms, }; if (operator) { @@ -339,11 +329,11 @@ export class FilterService { } this.triggerEvent(this._slickSubscriber, { + clearFilterTriggered: args && args.clearFilterTriggered, columnId, columnDef: args.columnDef || null, columnFilters: this._columnFilters, operator, - searchTerm, searchTerms, serviceOptions: this._onFilterChangedOptions, grid: this._grid @@ -357,47 +347,28 @@ export class FilterService { if (columnDef && columnId !== 'selector' && columnDef.filterable) { let searchTerms: SearchTerm[] | undefined; - let searchTerm: SearchTerm | undefined; let operator: OperatorString | OperatorType | undefined; if (this._columnFilters[columnDef.id]) { - searchTerm = this._columnFilters[columnDef.id].searchTerm || undefined; searchTerms = this._columnFilters[columnDef.id].searchTerms || undefined; operator = this._columnFilters[columnDef.id].operator || undefined; } else if (columnDef.filter) { // when hiding/showing (with Column Picker or Grid Menu), it will try to re-create yet again the filters (since SlickGrid does a re-render) // because of that we need to first get searchTerm(s) from the columnFilters (that is what the user last entered) searchTerms = columnDef.filter.searchTerms || undefined; - searchTerm = columnDef.filter.searchTerm || undefined; operator = columnDef.filter.operator || undefined; - this.updateColumnFilters(searchTerm, searchTerms, columnDef); + this.updateColumnFilters(searchTerms, columnDef); } const filterArguments: FilterArguments = { grid: this._grid, operator, - searchTerm, searchTerms, columnDef, callback: this.callbackSearchEvent.bind(this) }; - // depending on the Filter type, we will watch the correct event - const filterType = (columnDef.filter && columnDef.filter.type) ? columnDef.filter.type : this._gridOptions.defaultFilterType; - - let filter: Filter; - switch (filterType) { - case FilterType.custom: - if (columnDef && columnDef.filter && columnDef.filter.customFilter) { - filter = columnDef.filter.customFilter; - } else { - throw new Error('[Aurelia-Slickgrid] A Filter type of "custom" must include a Filter class that is defined and instantiated.'); - } - break; - default: - filter = this.filterFactory.createFilter(filterType); - break; - } + const filter: Filter | undefined = this.filterFactory.createFilter(args.column.filter); if (filter) { filter.init(filterArguments); @@ -412,8 +383,8 @@ export class FilterService { // when hiding/showing (with Column Picker or Grid Menu), it will try to re-create yet again the filters (since SlickGrid does a re-render) // we need to also set again the values in the DOM elements if the values were set by a searchTerm(s) - if ((searchTerm || searchTerms) && filter.setValues) { - filter.setValues(searchTerm || searchTerms); + if (searchTerms && filter.setValues) { + filter.setValues(searchTerms); } } } @@ -444,21 +415,22 @@ export class FilterService { * At the end of the day, when creating the Filter (DOM Element), it will use these searchTerm(s) so we can take advantage of that without recoding each Filter type (DOM element) * @param grid */ - populateColumnFilterSearchTerms(grid: any) { - if (this._gridOptions.presets && this._gridOptions.presets.filters) { + populateColumnFilterSearchTerms() { + if (this._gridOptions.presets && Array.isArray(this._gridOptions.presets.filters) && this._gridOptions.presets.filters.length > 0) { const filters = this._gridOptions.presets.filters; this._columnDefinitions.forEach((columnDef: Column) => { + // clear any columnDef searchTerms before applying Presets + if (columnDef.filter && columnDef.filter.searchTerms) { + delete columnDef.filter.searchTerms; + } + + // from each presets, we will find the associated columnDef and apply the preset searchTerms & operator if there is const columnPreset = filters.find((presetFilter: CurrentFilter) => { return presetFilter.columnId === columnDef.id; }); - if (columnPreset && columnPreset.searchTerm) { + if (columnPreset && columnPreset.searchTerms && Array.isArray(columnPreset.searchTerms)) { columnDef.filter = columnDef.filter || {}; - columnDef.filter.operator = columnPreset.operator; - columnDef.filter.searchTerm = columnPreset.searchTerm; - } - if (columnPreset && columnPreset.searchTerms) { - columnDef.filter = columnDef.filter || {}; - columnDef.filter.operator = columnPreset.operator || columnDef.filter.operator || OperatorType.in; + columnDef.filter.operator = columnPreset.operator || columnDef.filter.operator || ''; columnDef.filter.searchTerms = columnPreset.searchTerms; } }); @@ -466,24 +438,14 @@ export class FilterService { return this._columnDefinitions; } - private updateColumnFilters(searchTerm: SearchTerm | undefined, searchTerms: SearchTerm[] | undefined, columnDef: any) { - if (searchTerm !== undefined && searchTerm !== null && searchTerm !== '') { - this._columnFilters[columnDef.id] = { - columnId: columnDef.id, - columnDef, - searchTerm, - operator: (columnDef && columnDef.filter && columnDef.filter.operator) ? columnDef.filter.operator : null, - type: (columnDef && columnDef.filter && columnDef.filter.type) ? columnDef.filter.type : FilterType.input - }; - } + private updateColumnFilters(searchTerms: SearchTerm[] | undefined, columnDef: any) { if (searchTerms) { // this._columnFilters.searchTerms = searchTerms; this._columnFilters[columnDef.id] = { columnId: columnDef.id, columnDef, searchTerms, - operator: (columnDef && columnDef.filter && columnDef.filter.operator) ? columnDef.filter.operator : null, - type: (columnDef && columnDef.filter && columnDef.filter.type) ? columnDef.filter.type : FilterType.input + operator: (columnDef && columnDef.filter && columnDef.filter.operator) ? columnDef.filter.operator : null }; } } diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/services/graphql.service.ts b/aurelia-slickgrid/src/aurelia-slickgrid/services/graphql.service.ts index 0924ba102..75e066ef9 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/services/graphql.service.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/services/graphql.service.ts @@ -1,7 +1,5 @@ -import { EventAggregator } from 'aurelia-event-aggregator'; -import { inject } from 'aurelia-framework'; -import { I18N } from 'aurelia-i18n'; -import { mapOperatorType, mapOperatorByFilterType, mapOperatorByFieldType } from './utilities'; +import { singleton } from 'aurelia-framework'; +import { mapOperatorType, mapOperatorByFieldType } from './utilities'; import { BackendService, Column, @@ -34,7 +32,7 @@ const DEFAULT_FILTER_TYPING_DEBOUNCE = 750; const DEFAULT_ITEMS_PER_PAGE = 25; const DEFAULT_PAGE_SIZE = 20; -@inject(I18N) +@singleton(true) export class GraphqlService implements BackendService { private _currentFilters: ColumnFilters | CurrentFilter[]; private _currentPagination: CurrentPagination; @@ -49,8 +47,6 @@ export class GraphqlService implements BackendService { offset: 0 }; - constructor(private i18n: I18N) { } - /** Getter for the Grid Options pulled through the Grid Object */ private get _gridOptions(): GridOption { return (this._grid && this._grid.getOptions) ? this._grid.getOptions() : {}; @@ -134,7 +130,7 @@ export class GraphqlService implements BackendService { } if (this.options.addLocaleIntoQuery) { // first: 20, ... locale: "en-CA" - datasetFilters.locale = this.i18n.getLocale() || 'en'; + datasetFilters.locale = this._gridOptions && this._gridOptions.i18n && this._gridOptions.i18n.getLocale() || 'en'; } if (this.options.extraQueryArguments) { // first: 20, ... userId: 123 @@ -254,9 +250,9 @@ export class GraphqlService implements BackendService { /* * FILTERING */ - onFilterChanged(event: Event, args: FilterChangedArgs): Promise { + processOnFilterChanged(event: Event, args: FilterChangedArgs): Promise { const gridOptions: GridOption = this._gridOptions || args.grid.getOptions(); - const backendApi = gridOptions.backendServiceApi || gridOptions.onBackendEventApi; + const backendApi = gridOptions.backendServiceApi; if (backendApi === undefined) { throw new Error('Something went wrong in the GraphqlService, "backendServiceApi" is not initialized'); @@ -314,7 +310,7 @@ export class GraphqlService implements BackendService { * } * } */ - onPaginationChanged(event: Event, args: PaginationChangedArgs) { + processOnPaginationChanged(event: Event, args: PaginationChangedArgs) { const pageSize = +(args.pageSize || ((this.pagination) ? this.pagination.pageSize : DEFAULT_PAGE_SIZE)); this.updatePagination(args.newPage, pageSize); @@ -327,7 +323,7 @@ export class GraphqlService implements BackendService { * we will use sorting as per a Facebook suggestion on a Github issue (with some small changes) * https://github.com/graphql/graphql-relay-js/issues/20#issuecomment-220494222 */ - onSortChanged(event: Event, args: SortChangedArgs) { + processOnSortChanged(event: Event, args: SortChangedArgs) { const sortColumns = (args.multiColumnSort) ? args.sortCols : new Array({ sortCol: args.sortCol, sortAsc: args.sortAsc }); // loop through all columns to inspect sorters & set the query @@ -365,7 +361,7 @@ export class GraphqlService implements BackendService { const fieldName = columnDef.queryField || columnDef.queryFieldFilter || columnDef.field || columnDef.name || ''; const searchTerms = (columnFilter ? columnFilter.searchTerms : null) || []; - let fieldSearchValue = columnFilter.searchTerm; + let fieldSearchValue = (Array.isArray(searchTerms) && searchTerms.length === 1) ? searchTerms[0] : ''; if (typeof fieldSearchValue === 'undefined') { fieldSearchValue = ''; } @@ -386,7 +382,7 @@ export class GraphqlService implements BackendService { } // when having more than 1 search term (we need to create a CSV string for GraphQL "IN" or "NOT IN" filter search) - if (searchTerms && searchTerms.length > 0) { + if (searchTerms && searchTerms.length > 1) { searchValue = searchTerms.join(','); } else if (typeof searchValue === 'string') { // escaping the search value @@ -399,7 +395,7 @@ export class GraphqlService implements BackendService { // if we didn't find an Operator but we have a Filter Type, we should use default Operator // multipleSelect is "IN", while singleSelect is "EQ", else don't map any operator if (!operator && columnDef.filter) { - operator = mapOperatorByFilterType(columnDef.filter.type || ''); + operator = columnDef.filter.operator; } // if we still don't have an operator find the proper Operator to use by it's field type @@ -561,9 +557,8 @@ export class GraphqlService implements BackendService { } if (Array.isArray(filter.searchTerms)) { tmpFilter.searchTerms = filter.searchTerms; - } else { - tmpFilter.searchTerm = filter.searchTerm; } + return tmpFilter; }); } diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/services/grid-odata.service.ts b/aurelia-slickgrid/src/aurelia-slickgrid/services/grid-odata.service.ts index c345c60fd..339b1d5dc 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/services/grid-odata.service.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/services/grid-odata.service.ts @@ -1,5 +1,5 @@ import './global-utilities'; -import { inject } from 'aurelia-framework'; +import { singleton, inject } from 'aurelia-framework'; import { parseUtcDate } from './utilities'; import { BackendService, @@ -30,6 +30,7 @@ const DEFAULT_FILTER_TYPING_DEBOUNCE = 750; const DEFAULT_ITEMS_PER_PAGE = 25; const DEFAULT_PAGE_SIZE = 20; +@singleton(true) @inject(OdataService) export class GridOdataService implements BackendService { private _columnDefinitions: Column[]; @@ -37,6 +38,7 @@ export class GridOdataService implements BackendService { private _currentPagination: CurrentPagination; private _currentSorters: CurrentSorter[]; private _grid: any; + odataService: OdataService; options: OdataOption; pagination: Pagination | undefined; defaultOptions: OdataOption = { @@ -45,7 +47,9 @@ export class GridOdataService implements BackendService { caseType: CaseType.pascalCase }; - constructor(private odataService: OdataService) { } + constructor() { + this.odataService = new OdataService(); + } /** Getter for the Grid Options pulled through the Grid Object */ private get _gridOptions(): GridOption { @@ -123,9 +127,9 @@ export class GridOdataService implements BackendService { /* * FILTERING */ - onFilterChanged(event: Event, args: FilterChangedArgs): Promise { + processOnFilterChanged(event: Event, args: FilterChangedArgs): Promise { const serviceOptions: GridOption = args.grid.getOptions(); - const backendApi = serviceOptions.backendServiceApi || serviceOptions.onBackendEventApi; + const backendApi = serviceOptions.backendServiceApi; if (backendApi === undefined) { throw new Error('Something went wrong in the GridOdataService, "backendServiceApi" is not initialized'); @@ -156,7 +160,7 @@ export class GridOdataService implements BackendService { /* * PAGINATION */ - onPaginationChanged(event: Event, args: PaginationChangedArgs) { + processOnPaginationChanged(event: Event, args: PaginationChangedArgs) { const pageSize = +(args.pageSize || DEFAULT_PAGE_SIZE); this.updatePagination(args.newPage, pageSize); @@ -167,7 +171,7 @@ export class GridOdataService implements BackendService { /* * SORTING */ - onSortChanged(event: Event, args: SortChangedArgs) { + processOnSortChanged(event: Event, args: SortChangedArgs) { const sortColumns = (args.multiColumnSort) ? args.sortCols : new Array({ sortCol: args.sortCol, sortAsc: args.sortAsc }); // loop through all columns to inspect sorters & set the query @@ -207,7 +211,7 @@ export class GridOdataService implements BackendService { let fieldName = columnDef.queryField || columnDef.queryFieldFilter || columnDef.field || columnDef.name || ''; const fieldType = columnDef.type || 'string'; const searchTerms = (columnFilter ? columnFilter.searchTerms : null) || []; - let fieldSearchValue = columnFilter.searchTerm; + let fieldSearchValue = (Array.isArray(searchTerms) && searchTerms.length === 1) ? searchTerms[0] : ''; if (typeof fieldSearchValue === 'undefined') { fieldSearchValue = ''; } @@ -224,7 +228,7 @@ export class GridOdataService implements BackendService { const bypassOdataQuery = columnFilter.bypassBackendQuery || false; // no need to query if search value is empty - if (fieldName && searchValue === '') { + if (fieldName && searchValue === '' && searchTerms.length === 0) { this.removeColumnFilter(fieldName); continue; } @@ -247,7 +251,7 @@ export class GridOdataService implements BackendService { } // when having more than 1 search term (then check if we have a "IN" or "NOT IN" filter search) - if (searchTerms && searchTerms.length > 0) { + if (searchTerms && searchTerms.length > 1) { const tmpSearchTerms = []; if (operator === 'IN') { @@ -409,9 +413,8 @@ export class GridOdataService implements BackendService { } if (Array.isArray(filter.searchTerms)) { tmpFilter.searchTerms = filter.searchTerms; - } else { - tmpFilter.searchTerm = filter.searchTerm; } + return tmpFilter; }); } diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/services/gridExtra.service.ts b/aurelia-slickgrid/src/aurelia-slickgrid/services/grid.service.ts similarity index 57% rename from aurelia-slickgrid/src/aurelia-slickgrid/services/gridExtra.service.ts rename to aurelia-slickgrid/src/aurelia-slickgrid/services/grid.service.ts index 886ff527e..87f83544c 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/services/gridExtra.service.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/services/grid.service.ts @@ -1,13 +1,26 @@ -import { Column, GridOption } from './../models/index'; +import { singleton, inject } from 'aurelia-framework'; +import { CellArgs, Column, GridOption, OnEventArgs } from './../models/index'; +import { FilterService } from './filter.service'; +import { GridStateService } from './gridState.service'; +import { SortService } from './sort.service'; import * as $ from 'jquery'; // using external non-typed js libraries declare var Slick: any; -export class GridExtraService { +@singleton(true) +@inject(FilterService, GridStateService, SortService) +export class GridService { private _grid: any; private _dataView: any; + constructor(private filterService: FilterService, private gridStateService: GridStateService, private sortService: SortService) { } + + /** Getter for the Column Definitions pulled through the Grid Object */ + private get _columnDefinitions(): Column[] { + return (this._grid && this._grid.getColumns) ? this._grid.getColumns() : []; + } + /** Getter for the Grid Options pulled through the Grid Object */ private get _gridOptions(): GridOption { return (this._grid && this._grid.getOptions) ? this._grid.getOptions() : {}; @@ -23,6 +36,30 @@ export class GridExtraService { this._dataView = dataView; } + /** + * From a SlickGrid Event triggered get the Column Definition and Item Data Context + * + * For example the SlickGrid onClick will return cell arguments when subscribing to it. + * From these cellArgs, we want to get the Column Definition and Item Data + * @param cell event args + * @return object with columnDef and dataContext + */ + getColumnFromEventArguments(args: CellArgs): OnEventArgs { + if (!args || !args.grid || !args.grid.getColumns || !args.grid.getDataItem) { + throw new Error('To get the column definition and data, we need to have these arguments passed as objects (row, cell, grid)'); + } + + return { + row: args.row, + cell: args.cell, + columnDef: args.grid.getColumns()[args.cell], + dataContext: args.grid.getDataItem(args.row), + dataView: this._dataView, + grid: this._grid, + gridDefinition: this._gridOptions + }; + } + getDataItemByRowNumber(rowNumber: number) { if (!this._grid || typeof this._grid.getDataItem !== 'function') { throw new Error('We could not find SlickGrid Grid object'); @@ -92,16 +129,22 @@ export class GridExtraService { } } + /** Get the currently selected rows */ getSelectedRows() { return this._grid.getSelectedRows(); } + + /** Select the selected row by a row index */ setSelectedRow(rowIndex: number) { this._grid.setSelectedRows([rowIndex]); } + + /** Set selected rows with provided array of row indexes */ setSelectedRows(rowIndexes: number[]) { this._grid.setSelectedRows(rowIndexes); } + /** Re-Render the Grid */ renderGrid() { if (this._grid && typeof this._grid.invalidate === 'function') { this._grid.invalidate(); @@ -109,6 +152,31 @@ export class GridExtraService { } } + /** + * Reset the grid to it's original state (clear any filters, sorting & pagination if exists) . + * The column definitions could be passed as argument to reset (this can be used after a Grid State reset) + * The reset will clear the Filters & Sort, then will reset the Columns to their original state + */ + resetGrid(columnDefinitions?: Column[]) { + if (this.filterService && this.filterService.clearFilters) { + this.filterService.clearFilters(); + } + if (this.sortService && this.sortService.clearSorting) { + this.sortService.clearSorting(); + } + + // reset columns to original states & refresh the grid + if (this._grid && this._dataView) { + const originalColumns = columnDefinitions || this._columnDefinitions; + if (Array.isArray(originalColumns) && originalColumns.length > 0) { + this._grid.setColumns(originalColumns); + this._dataView.refresh(); + this._grid.autosizeColumns(); + this.gridStateService.resetColumns(columnDefinitions); + } + } + } + /** * Add an item (data item) to the datagrid * @param object dataItem: item object holding all properties of that row @@ -137,11 +205,23 @@ export class GridExtraService { * @param object item: item object holding all properties of that row */ deleteDataGridItem(item: any) { - const row = this._dataView.getRowById(item.id); - const itemId = (!item || !item.hasOwnProperty('id')) ? -1 : item.id; + if (!item || !item.hasOwnProperty('id')) { + throw new Error(`deleteDataGridItem() requires an item object which includes the "id" property`); + } + const itemId = (!item || !item.hasOwnProperty('id')) ? undefined : item.id; + this.deleteDataGridItemById(itemId); + } - if (row === undefined || itemId === -1) { - throw new Error(`Could not find the item in the grid or it's associated "id"`); + /** + * Delete an existing item from the datagrid (dataView) by it's id + * @param itemId: item unique id + */ + deleteDataGridItemById(itemId: string | number) { + if (itemId === undefined) { + throw new Error(`Cannot delete a row without a valid "id"`); + } + if (this._dataView.getRowById(itemId) === undefined) { + throw new Error(`Could not find the item in the grid by it's associated "id"`); } // delete the item from the dataView @@ -150,30 +230,31 @@ export class GridExtraService { } /** - * Delete an existing item from the datagrid (dataView) + * Update an existing item with new properties inside the datagrid * @param object item: item object holding all properties of that row */ - deleteDataGridItemById(id: string | number) { - const row = this._dataView.getRowById(id); + updateDataGridItem(item: any) { + const itemId = (!item || !item.hasOwnProperty('id')) ? undefined : item.id; - if (row === undefined) { - throw new Error(`Could not find the item in the grid by it's associated "id"`); + if (itemId === undefined) { + throw new Error(`Could not find the item in the grid or it's associated "id"`); } - // delete the item from the dataView - this._dataView.deleteItem(id); - this._dataView.refresh(); + this.updateDataGridItemById(itemId, item); } /** - * Update an existing item with new properties inside the datagrid (dataView) + * Update an existing item in the datagrid by it's id and new properties + * @param itemId: item unique id * @param object item: item object holding all properties of that row */ - updateDataGridItem(item: any) { - const row = this._dataView.getRowById(item.id); - const itemId = (!item || !item.hasOwnProperty('id')) ? -1 : item.id; + updateDataGridItemById(itemId: number | string, item: any) { + if (itemId === undefined) { + throw new Error(`Cannot update a row without a valid "id"`); + } + const row = this._dataView.getRowById(itemId); - if (itemId === -1) { + if (!item || !row) { throw new Error(`Could not find the item in the grid or it's associated "id"`); } diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/services/gridEvent.service.ts b/aurelia-slickgrid/src/aurelia-slickgrid/services/gridEvent.service.ts index 0a346aecd..771f016da 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/services/gridEvent.service.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/services/gridEvent.service.ts @@ -1,8 +1,10 @@ +import { singleton } from 'aurelia-framework'; import { OnEventArgs, CellArgs, GridOption } from './../models/index'; // using external non-typed js libraries declare var Slick: any; +@singleton(true) export class GridEventService { private _eventHandler: any = new Slick.EventHandler(); @@ -29,7 +31,7 @@ export class GridEventService { }; // finally call up the Slick.column.onCellChanges.... function - column.onCellChange(returnedArgs); + column.onCellChange(e, returnedArgs); } }); } @@ -55,7 +57,7 @@ export class GridEventService { }; // finally call up the Slick.column.onCellClick.... function - column.onCellClick(returnedArgs); + column.onCellClick(e, returnedArgs); } }); } diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/services/gridExtraUtils.ts b/aurelia-slickgrid/src/aurelia-slickgrid/services/gridExtraUtils.ts deleted file mode 100644 index b7da5ec09..000000000 --- a/aurelia-slickgrid/src/aurelia-slickgrid/services/gridExtraUtils.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { CellArgs } from './../models/cellArgs.interface'; - -export class GridExtraUtils { - static getColumnDefinitionAndData(args: CellArgs) { - if (!args || !args.grid || !args.grid.getColumns || !args.grid.getDataItem) { - throw new Error('To get the column definition and data, we need to have these arguments passed (row, cell, grid)'); - } - return { - columnDef: args.grid.getColumns()[args.cell], - dataContext: args.grid.getDataItem(args.row) - }; - } -} diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/services/gridState.service.ts b/aurelia-slickgrid/src/aurelia-slickgrid/services/gridState.service.ts index 2f0a32071..9892e244d 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/services/gridState.service.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/services/gridState.service.ts @@ -1,5 +1,7 @@ import { inject } from 'aurelia-framework'; import { + Column, + CurrentColumn, CurrentFilter, CurrentPagination, CurrentSorter, @@ -7,18 +9,23 @@ import { GridState, GridStateType, } from './../models/index'; -import { FilterService, SortService } from './../services/index'; +import { ControlAndPluginService, FilterService, SortService } from './../services/index'; import { EventAggregator, Subscription } from 'aurelia-event-aggregator'; import * as $ from 'jquery'; +// using external non-typed js libraries +declare var Slick: any; + @inject(EventAggregator) export class GridStateService { + private _eventHandler = new Slick.EventHandler(); + private _columns: Column[] = []; + private _currentColumns: CurrentColumn[] = []; private _grid: any; - private _preset: GridState; + private controlAndPluginService: ControlAndPluginService; private filterService: FilterService; - private _filterSubcription: Subscription; - private _sorterSubcription: Subscription; private sortService: SortService; + private subscriptions: Subscription[] = []; constructor(private ea: EventAggregator) { } @@ -33,23 +40,27 @@ export class GridStateService { * @param filterService * @param sortService */ - init(grid: any, filterService: FilterService, sortService: SortService): void { + init(grid: any, controlAndPluginService: ControlAndPluginService, filterService: FilterService, sortService: SortService): void { this._grid = grid; + this.controlAndPluginService = controlAndPluginService; this.filterService = filterService; this.sortService = sortService; - // Subscribe to Event Emitter of Filter & Sort changed, go back to page 1 when that happen - this._filterSubcription = this.ea.subscribe('filterService:filterChanged', (currentFilters: CurrentFilter[]) => { - this.ea.publish('gridStateService:changed', { change: { newValues: currentFilters, type: GridStateType.filter }, gridState: this.getCurrentGridState() }); - }); - this._sorterSubcription = this.ea.subscribe('sortService:sortChanged', (currentSorters: CurrentSorter[]) => { - this.ea.publish('gridStateService:changed', { change: { newValues: currentSorters, type: GridStateType.sorter }, gridState: this.getCurrentGridState() }); - }); + this.subscribeToAllGridChanges(grid); } + /** Dispose of all the SlickGrid & Aurelia subscriptions */ dispose() { - this._filterSubcription.dispose(); - this._sorterSubcription.dispose(); + // unsubscribe all SlickGrid events + this._eventHandler.unsubscribeAll(); + + // also unsubscribe all Aurelia Subscriptions + this.subscriptions.forEach((subscription: Subscription) => { + if (subscription && subscription.dispose) { + subscription.dispose(); + } + }); + this.subscriptions = []; } /** @@ -58,6 +69,7 @@ export class GridStateService { */ getCurrentGridState(): GridState { const gridState: GridState = { + columns: this.getCurrentColumns(), filters: this.getCurrentFilters(), sorters: this.getCurrentSorters() }; @@ -69,6 +81,78 @@ export class GridStateService { return gridState; } + /** + * Get the Columns (and their state: visibility/position) that are currently applied in the grid + * @return current columns + */ + getColumns(): Column[] { + return this._columns || this._grid.getColumns(); + } + + /** + * From an array of Grid Column Definitions, get the associated Current Columns + * @param gridColumns + */ + getAssociatedCurrentColumns(gridColumns: Column[]): CurrentColumn[] { + const currentColumns: CurrentColumn[] = []; + + if (gridColumns && Array.isArray(gridColumns)) { + gridColumns.forEach((column: Column, index: number) => { + if (column && column.id) { + currentColumns.push({ + columnId: column.id as string, + cssClass: column.cssClass || '', + headerCssClass: column.headerCssClass || '', + width: column.width || 0 + }); + } + }); + } + this._currentColumns = currentColumns; + return currentColumns; + } + + /** + * From an array of Current Columns, get the associated Grid Column Definitions + * @param grid + * @param currentColumns + */ + getAssociatedGridColumns(grid: any, currentColumns: CurrentColumn[]): Column[] { + const columns: Column[] = []; + const gridColumns: Column[] = grid.getColumns(); + + if (currentColumns && Array.isArray(currentColumns)) { + currentColumns.forEach((currentColumn: CurrentColumn, index: number) => { + const gridColumn: Column | undefined = gridColumns.find((c: Column) => c.id === currentColumn.columnId); + if (gridColumn && gridColumn.id) { + columns.push({ + ...gridColumn, + cssClass: currentColumn.cssClass, + headerCssClass: currentColumn.headerCssClass, + width: currentColumn.width + }); + } + }); + } + this._columns = columns; + return columns; + } + + /** + * Get the Columns (and their state: visibility/position) that are currently applied in the grid + * @return current columns + */ + getCurrentColumns(): CurrentColumn[] { + let currentColumns: CurrentColumn[] = []; + if (this._currentColumns && Array.isArray(this._currentColumns) && this._currentColumns.length > 0) { + currentColumns = this._currentColumns; + } else { + currentColumns = this.getAssociatedCurrentColumns(this._grid.getColumns()); + } + + return currentColumns; + } + /** * Get the Filters (and their state, columnId, searchTerm(s)) that are currently applied in the grid * @return current filters @@ -116,4 +200,82 @@ export class GridStateService { } return null; } + + /** + * Hook a SlickGrid Extension Event to a Grid State change event + * @param extension name + * @param grid + */ + hookExtensionEventToGridStateChange(extensionName: string, eventName: string) { + const extension = this.controlAndPluginService && this.controlAndPluginService.getExtensionByName(extensionName); + + if (extension && extension.service && extension.service[eventName] && extension.service[eventName].subscribe) { + this._eventHandler.subscribe(extension.service[eventName], (e: Event, args: any) => { + const columns: Column[] = args && args.columns; + const currentColumns: CurrentColumn[] = this.getAssociatedCurrentColumns(columns); + this.ea.publish('gridStateService:changed', { change: { newValues: currentColumns, type: GridStateType.columns }, gridState: this.getCurrentGridState() }); + }); + } + } + + /** + * Hook a Grid Event to a Grid State change event + * @param event name + * @param grid + */ + hookSlickGridEventToGridStateChange(eventName: string, grid: any) { + if (grid && grid[eventName] && grid[eventName].subscribe) { + this._eventHandler.subscribe(grid[eventName], (e: Event, args: any) => { + const columns: Column[] = grid.getColumns(); + const currentColumns: CurrentColumn[] = this.getAssociatedCurrentColumns(columns); + this.ea.publish('gridStateService:changed', { change: { newValues: currentColumns, type: GridStateType.columns }, gridState: this.getCurrentGridState() }); + }); + } + } + + resetColumns(columnDefinitions?: Column[]) { + const columns: Column[] = columnDefinitions || this._columns; + const currentColumns: CurrentColumn[] = this.getAssociatedCurrentColumns(columns); + this.ea.publish('gridStateService:changed', { change: { newValues: currentColumns, type: GridStateType.columns }, gridState: this.getCurrentGridState() }); + } + + /** + * Subscribe to all necessary SlickGrid or Service Events that deals with a Grid change, + * when triggered, we will publish a Grid State Event with current Grid State + */ + subscribeToAllGridChanges(grid: any) { + // Subscribe to Event Emitter of Filter changed + this.subscriptions.push( + this.ea.subscribe('filterService:filterChanged', (currentFilters: CurrentFilter[]) => { + this.ea.publish('gridStateService:changed', { change: { newValues: currentFilters, type: GridStateType.filter }, gridState: this.getCurrentGridState() }); + }) + ); + // Subscribe to Event Emitter of Filter cleared + this.subscriptions.push( + this.ea.subscribe('filterService:filterCleared', (currentFilters: CurrentFilter[]) => { + this.ea.publish('gridStateService:changed', { change: { newValues: currentFilters, type: GridStateType.filter }, gridState: this.getCurrentGridState() }); + }) + ); + + // Subscribe to Event Emitter of Sort changed + this.subscriptions.push( + this.ea.subscribe('sortService:sortChanged', (currentSorters: CurrentSorter[]) => { + this.ea.publish('gridStateService:changed', { change: { newValues: currentSorters, type: GridStateType.sorter }, gridState: this.getCurrentGridState() }); + }) + ); + // Subscribe to Event Emitter of Sort cleared + this.subscriptions.push( + this.ea.subscribe('sortService:sortCleared', (currentSorters: CurrentSorter[]) => { + this.ea.publish('gridStateService:changed', { change: { newValues: currentSorters, type: GridStateType.sorter }, gridState: this.getCurrentGridState() }); + }) + ); + + // Subscribe to ColumnPicker and/or GridMenu for show/hide Columns visibility changes + this.hookExtensionEventToGridStateChange('ColumnPicker', 'onColumnsChanged'); + this.hookExtensionEventToGridStateChange('GridMenu', 'onColumnsChanged'); + + // subscribe to Column Resize & Reordering + this.hookSlickGridEventToGridStateChange('onColumnsReordered', grid); + this.hookSlickGridEventToGridStateChange('onColumnsResized', grid); + } } diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/services/groupingAndColspan.service.ts b/aurelia-slickgrid/src/aurelia-slickgrid/services/groupingAndColspan.service.ts index 9c807ad48..8c67c23a7 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/services/groupingAndColspan.service.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/services/groupingAndColspan.service.ts @@ -1,5 +1,5 @@ import { EventAggregator, Subscription } from 'aurelia-event-aggregator'; -import { inject } from 'aurelia-framework'; +import { singleton, inject } from 'aurelia-framework'; import { Column, GridOption @@ -9,6 +9,7 @@ import * as $ from 'jquery'; // using external non-typed js libraries declare var Slick: any; +@singleton(true) @inject(EventAggregator) export class GroupingAndColspanService { private _eventHandler = new Slick.EventHandler(); diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/services/index.ts b/aurelia-slickgrid/src/aurelia-slickgrid/services/index.ts index 73e9c9489..e8f1d17f3 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/services/index.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/services/index.ts @@ -4,8 +4,7 @@ export * from './export.service'; export * from './filter.service'; export * from './graphql.service'; export * from './gridEvent.service'; -export * from './gridExtra.service'; -export * from './gridExtraUtils'; +export * from './grid.service'; export * from './gridState.service'; export * from './grid-odata.service'; export * from './groupingAndColspan.service'; diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/services/odata.service.ts b/aurelia-slickgrid/src/aurelia-slickgrid/services/odata.service.ts index 76f66e7d2..0813d7f35 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/services/odata.service.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/services/odata.service.ts @@ -1,6 +1,8 @@ import './global-utilities'; +import { singleton } from 'aurelia-framework'; import { CaseType, OdataOption } from './../models/index'; +@singleton(true) export class OdataService { _columnFilters: any; _defaultSortBy: string; diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/services/resizer.service.ts b/aurelia-slickgrid/src/aurelia-slickgrid/services/resizer.service.ts index 3ca95bc3a..e9305dd35 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/services/resizer.service.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/services/resizer.service.ts @@ -1,4 +1,4 @@ -import { inject } from 'aurelia-framework'; +import { singleton, inject } from 'aurelia-framework'; import { GridOption } from './../models/index'; import * as $ from 'jquery'; import { EventAggregator } from 'aurelia-event-aggregator'; @@ -16,6 +16,7 @@ export interface GridDimension { heightWithPagination?: number; } +@singleton(true) @inject(EventAggregator) export class ResizerService { private _grid: any; diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/services/sort.service.ts b/aurelia-slickgrid/src/aurelia-slickgrid/services/sort.service.ts index c0acd9896..366bcef6d 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/services/sort.service.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/services/sort.service.ts @@ -1,4 +1,4 @@ -import { inject } from 'aurelia-framework'; +import { inject, singleton } from 'aurelia-framework'; import { EventAggregator } from 'aurelia-event-aggregator'; import { Column, @@ -18,6 +18,7 @@ import { sortByFieldType } from '../sorters/sorterUtilities'; // using external non-typed js libraries declare var Slick: any; +@singleton(true) @inject(EventAggregator) export class SortService { private _currentLocalSorters: CurrentSorter[] = []; @@ -59,7 +60,7 @@ export class SortService { throw new Error('Something went wrong when trying to attach the "onBackendSortChanged(event, args)" function, it seems that "args" is not populated correctly'); } const gridOptions: GridOption = args.grid.getOptions() || {}; - const backendApi = gridOptions.backendServiceApi || gridOptions.onBackendEventApi; + const backendApi = gridOptions.backendServiceApi; if (!backendApi || !backendApi.process || !backendApi.service) { throw new Error(`BackendServiceApi requires at least a "process" function and a "service" defined`); @@ -67,7 +68,7 @@ export class SortService { if (backendApi.preProcess) { backendApi.preProcess(); } - const query = backendApi.service.onSortChanged(event, args); + const query = backendApi.service.processOnSortChanged(event, args); this.emitSortChanged('remote'); // await for the Promise to resolve the data @@ -109,7 +110,7 @@ export class SortService { // keep current sorters this._currentLocalSorters = []; // reset current local sorters if (Array.isArray(sortColumns)) { - sortColumns.forEach((sortColumn) => { + sortColumns.forEach((sortColumn: { sortCol: Column, sortAsc: number }) => { if (sortColumn.sortCol) { this._currentLocalSorters.push({ columnId: sortColumn.sortCol.id, @@ -156,6 +157,13 @@ export class SortService { } } } + + // set current sorter to empty & emit a sort changed event + this._currentLocalSorters = []; + const sender = (this._gridOptions && this._gridOptions.backendServiceApi) ? 'remote' : 'local'; + + // emit an event when filters are all cleared + this.ea.publish('sortService:sortCleared', this._currentLocalSorters); } getCurrentLocalSorters(): CurrentSorter[] { @@ -195,28 +203,27 @@ export class SortService { this._currentLocalSorters = []; // reset current local sorters if (this._gridOptions && this._gridOptions.presets && this._gridOptions.presets.sorters) { const sorters = this._gridOptions.presets.sorters; - this._columnDefinitions.forEach((columnDef: Column) => { - const columnPreset = sorters.find((currentSorter: CurrentSorter) => { - return currentSorter.columnId === columnDef.id; - }); - if (columnPreset) { + + sorters.forEach((presetSorting: CurrentSorter) => { + const gridColumn = this._columnDefinitions.find((col: Column) => col.id === presetSorting.columnId); + if (gridColumn) { sortCols.push({ - columnId: columnDef.id, - sortAsc: ((columnPreset.direction.toUpperCase() === SortDirection.ASC) ? true : false), - sortCol: columnDef + columnId: gridColumn.id, + sortAsc: ((presetSorting.direction.toUpperCase() === SortDirection.ASC) ? true : false), + sortCol: gridColumn }); // keep current sorters this._currentLocalSorters.push({ - columnId: columnDef.id + '', - direction: columnPreset.direction.toUpperCase() as SortDirectionString + columnId: gridColumn.id + '', + direction: presetSorting.direction.toUpperCase() as SortDirectionString }); } }); if (sortCols.length > 0) { this.onLocalSortChanged(grid, dataView, sortCols); - grid.setSortColumns(sortCols); // add sort icon in UI + grid.setSortColumns(sortCols); // use this to add sort icon(s) in UI } } } diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/services/utilities.ts b/aurelia-slickgrid/src/aurelia-slickgrid/services/utilities.ts index 4cf81a1f7..900bcba6b 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/services/utilities.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/services/utilities.ts @@ -1,4 +1,5 @@ -import { FieldType, OperatorType, FilterType, FormElementType } from '../models/index'; +import { Filters } from '../filters/index'; +import { FieldType, OperatorType } from '../models/index'; import * as moment from 'moment'; /** @@ -285,29 +286,6 @@ export function mapOperatorByFieldType(fieldType: FieldType | string): OperatorT return map; } -/** - * Mapper for query operator by a Filter Type - * For example a multiple-select typically uses 'IN' operator - * @param operator - * @returns string map - */ -export function mapOperatorByFilterType(filterType: FilterType | FormElementType | string): OperatorType { - let map = OperatorType.empty; - - switch (filterType) { - case FilterType.multipleSelect: - map = OperatorType.in; - break; - case FilterType.singleSelect: - map = OperatorType.equal; - break; - default: - break; - } - - return map; -} - /** * Parse a date passed as a string and return a Date object (if valid) * @param string inputDateString diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/slick-pagination.ts b/aurelia-slickgrid/src/aurelia-slickgrid/slick-pagination.ts index c7c3a726c..874e03fab 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/slick-pagination.ts +++ b/aurelia-slickgrid/src/aurelia-slickgrid/slick-pagination.ts @@ -38,6 +38,10 @@ export class SlickPaginationCustomElement { this._filterSubscriber = this.ea.subscribe('filterService:filterChanged', (data: string) => { this.refreshPagination(true); }); + // Subscribe to Filter clear and go back to page 1 when that happen + this._filterSubscriber = this.ea.subscribe('filterService:filterCleared', (data: string) => { + this.refreshPagination(true); + }); } gridPaginationOptionsChanged(newGridOptions: GridOption) { @@ -105,7 +109,7 @@ export class SlickPaginationCustomElement { } refreshPagination(isPageNumberReset: boolean = false) { - const backendApi = this._gridPaginationOptions.backendServiceApi || this._gridPaginationOptions.onBackendEventApi; + const backendApi = this._gridPaginationOptions.backendServiceApi; if (!backendApi || !backendApi.service || !backendApi.process) { throw new Error(`BackendServiceApi requires at least a "process" function and a "service" defined`); } @@ -143,7 +147,7 @@ export class SlickPaginationCustomElement { async onPageChanged(event: Event | undefined, pageNumber: number) { this.recalculateFromToIndexes(); - const backendApi = this._gridPaginationOptions.backendServiceApi || this._gridPaginationOptions.onBackendEventApi; + const backendApi = this._gridPaginationOptions.backendServiceApi; if (!backendApi || !backendApi.service || !backendApi.process) { throw new Error(`BackendServiceApi requires at least a "process" function and a "service" defined`); } @@ -160,7 +164,7 @@ export class SlickPaginationCustomElement { backendApi.preProcess(); } - const query = backendApi.service.onPaginationChanged(event, { newPage: pageNumber, pageSize: itemsPerPage }); + const query = backendApi.service.processOnPaginationChanged(event, { newPage: pageNumber, pageSize: itemsPerPage }); // await for the Promise to resolve the data const processResult = await backendApi.process(query); diff --git a/aurelia-slickgrid/src/aurelia-slickgrid/styles/_variables.scss b/aurelia-slickgrid/src/aurelia-slickgrid/styles/_variables.scss index 817919444..b71c201f9 100644 --- a/aurelia-slickgrid/src/aurelia-slickgrid/styles/_variables.scss +++ b/aurelia-slickgrid/src/aurelia-slickgrid/styles/_variables.scss @@ -39,8 +39,8 @@ $cell-odd-active-background-color: darken($grid-cell-color, 5%) !default; $cell-padding: 5px 7.5834px !default; /* row */ -$row-mouse-hover-color: rgb(245, 245, 204) !default; -$row-selected-color: #e2e2c5 !default; +$row-mouse-hover-color: #eff5fc !default; +$row-selected-color: #dae8f1 !default; $row-highlight-background-color: darken($row-selected-color, 5%) !default; $row-highlight-fade-animation: 1.5s ease 1 !default; $row-checkbox-selector-background: inherit !default; diff --git a/aurelia-slickgrid/src/examples/slickgrid/custom-inputEditor.ts b/aurelia-slickgrid/src/examples/slickgrid/custom-inputEditor.ts new file mode 100644 index 000000000..69b0af416 --- /dev/null +++ b/aurelia-slickgrid/src/examples/slickgrid/custom-inputEditor.ts @@ -0,0 +1,80 @@ +import { Editor, KeyCode } from '../../aurelia-slickgrid'; + +// using external non-typed js libraries +declare var $: any; + +/* + * An example of a 'detached' editor. + * KeyDown events are also handled to provide handling for Tab, Shift-Tab, Esc and Ctrl-Enter. + */ +export class CustomInputEditor implements Editor { + $input: any; + defaultValue: any; + + constructor(private args: any) { + this.init(); + } + + init(): void { + this.$input = $(``) + .appendTo(this.args.container) + .on('keydown.nav', (e) => { + if (e.keyCode === KeyCode.LEFT || e.keyCode === KeyCode.RIGHT) { + e.stopImmediatePropagation(); + } + }); + + setTimeout(() => { + this.$input.focus().select(); + }, 50); + } + + destroy() { + this.$input.remove(); + } + + focus() { + this.$input.focus(); + } + + getValue() { + return this.$input.val(); + } + + setValue(val: string) { + this.$input.val(val); + } + + loadValue(item: any) { + this.defaultValue = item[this.args.column.field] || ''; + this.$input.val(this.defaultValue); + this.$input[0].defaultValue = this.defaultValue; + this.$input.select(); + } + + serializeValue() { + return this.$input.val(); + } + + applyValue(item: any, state: any) { + item[this.args.column.field] = state; + } + + isValueChanged() { + return (!(this.$input.val() === '' && this.defaultValue === null)) && (this.$input.val() !== this.defaultValue); + } + + validate() { + if (this.args.column.validator) { + const validationResults = this.args.column.validator(this.$input.val()); + if (!validationResults.valid) { + return validationResults; + } + } + + return { + valid: true, + msg: null + }; + } +} diff --git a/aurelia-slickgrid/src/examples/slickgrid/custom-inputFilter.ts b/aurelia-slickgrid/src/examples/slickgrid/custom-inputFilter.ts index 9ab3f7ae0..b64dcde4a 100644 --- a/aurelia-slickgrid/src/examples/slickgrid/custom-inputFilter.ts +++ b/aurelia-slickgrid/src/examples/slickgrid/custom-inputFilter.ts @@ -1,12 +1,13 @@ -import { Column, Filter, FilterArguments, FilterCallback, SearchTerm } from '../../aurelia-slickgrid'; +import { Column, Filter, FilterArguments, FilterCallback, SearchTerm, OperatorType, OperatorString } from '../../aurelia-slickgrid'; import * as $ from 'jquery'; export class CustomInputFilter implements Filter { private $filterElm: any; grid: any; - searchTerm: SearchTerm; + searchTerms: SearchTerm[]; columnDef: Column; callback: FilterCallback; + operator: OperatorType | OperatorString = OperatorType.equal; /** * Initialize the Filter @@ -15,13 +16,16 @@ export class CustomInputFilter implements Filter { this.grid = args.grid; this.callback = args.callback; this.columnDef = args.columnDef; - this.searchTerm = args.searchTerm; + this.searchTerms = args.searchTerms || []; + + // filter input can only have 1 search term, so we will use the 1st array index if it exist + const searchTerm = (Array.isArray(this.searchTerms) && this.searchTerms[0]) || ''; // step 1, create HTML string template const filterTemplate = this.buildTemplateHtmlString(); // step 2, create the DOM Element of the filter & initialize it if searchTerm is filled - this.$filterElm = this.createDomElement(filterTemplate); + this.$filterElm = this.createDomElement(filterTemplate, searchTerm); // step 3, subscribe to the keyup event and run the callback when that happens this.$filterElm.keyup((e: any) => this.callback(e, { columnDef: this.columnDef })); @@ -30,12 +34,10 @@ export class CustomInputFilter implements Filter { /** * Clear the filter value */ - clear(triggerFilterKeyup = true) { + clear() { if (this.$filterElm) { this.$filterElm.val(''); - if (triggerFilterKeyup) { - this.$filterElm.trigger('keyup'); - } + this.$filterElm.trigger('keyup'); } } @@ -72,14 +74,15 @@ export class CustomInputFilter implements Filter { * From the html template string, create a DOM element * @param filterTemplate */ - private createDomElement(filterTemplate: string) { + private createDomElement(filterTemplate: string, searchTerm?: SearchTerm) { const $headerElm = this.grid.getHeaderRowColumn(this.columnDef.id); $($headerElm).empty(); // create the DOM element & add an ID and filter class const $filterElm = $(filterTemplate); - const searchTerm = (typeof this.searchTerm === 'boolean') ? `${this.searchTerm}` : this.searchTerm; - $filterElm.val(searchTerm); + const searchTermInput = searchTerm as string; + + $filterElm.val(searchTermInput); $filterElm.attr('id', `filter-${this.columnDef.id}`); $filterElm.data('columnId', this.columnDef.id); diff --git a/aurelia-slickgrid/src/examples/slickgrid/example1.html b/aurelia-slickgrid/src/examples/slickgrid/example1.html index fa844a358..286467357 100644 --- a/aurelia-slickgrid/src/examples/slickgrid/example1.html +++ b/aurelia-slickgrid/src/examples/slickgrid/example1.html @@ -4,8 +4,26 @@

${title}

- +

Grid 1

+ + + +
+ +

Grid 2

+ diff --git a/aurelia-slickgrid/src/examples/slickgrid/example1.ts b/aurelia-slickgrid/src/examples/slickgrid/example1.ts index 2bae7c5f1..495fc263b 100644 --- a/aurelia-slickgrid/src/examples/slickgrid/example1.ts +++ b/aurelia-slickgrid/src/examples/slickgrid/example1.ts @@ -4,23 +4,27 @@ export class Example1 { title = 'Example 1: Basic Grid'; subTitle = `Simple Grid with Fixed Sizes (800 x 400) using "grid-height" & "grid-width"`; - gridOptions: GridOption; - columnDefinitions: Column[]; - dataset: any[]; + gridOptions1: GridOption; + gridOptions2: GridOption; + columnDefinitions1: Column[]; + columnDefinitions2: Column[]; + dataset1: any[]; + dataset2: any[]; constructor() { // define the grid options & columns and then create the grid itself - this.defineGrid(); + this.defineGrids(); } attached() { - // populate the dataset once the grid is ready - this.getData(); + // mock some data (different in each dataset) + this.dataset1 = this.mockData(); + this.dataset2 = this.mockData(); } /* Define grid Options and Columns */ - defineGrid() { - this.columnDefinitions = [ + defineGrids() { + this.columnDefinitions1 = [ { id: 'title', name: 'Title', field: 'title', sortable: true, minWidth: 100 }, { id: 'duration', name: 'Duration (days)', field: 'duration', sortable: true, minWidth: 100 }, { id: '%', name: '% Complete', field: 'percentComplete', sortable: true, minWidth: 100 }, @@ -28,21 +32,26 @@ export class Example1 { { id: 'finish', name: 'Finish', field: 'finish', minWidth: 100 }, { id: 'effort-driven', name: 'Effort Driven', field: 'effortDriven', sortable: true, minWidth: 100 } ]; - this.gridOptions = { - enableAutoResize: false + this.gridOptions1 = { + enableAutoResize: false, + enableSorting: true }; + + // copy the same Grid Options and Column Definitions to 2nd grid + this.columnDefinitions2 = this.columnDefinitions1; + this.gridOptions2 = this.gridOptions1; } - getData() { + mockData() { // mock a dataset - this.dataset = []; + const mockDataset = []; for (let i = 0; i < 1000; i++) { const randomYear = 2000 + Math.floor(Math.random() * 10); const randomMonth = Math.floor(Math.random() * 11); const randomDay = Math.floor((Math.random() * 29)); const randomPercent = Math.round(Math.random() * 100); - this.dataset[i] = { + mockDataset[i] = { id: i, title: 'Task ' + i, duration: Math.round(Math.random() * 100) + '', @@ -52,5 +61,7 @@ export class Example1 { effortDriven: (i % 5 === 0) }; } + + return mockDataset; } } diff --git a/aurelia-slickgrid/src/examples/slickgrid/example10.html b/aurelia-slickgrid/src/examples/slickgrid/example10.html index a66300675..30b820e2b 100644 --- a/aurelia-slickgrid/src/examples/slickgrid/example10.html +++ b/aurelia-slickgrid/src/examples/slickgrid/example10.html @@ -3,28 +3,42 @@

${title}

-
-
- - - - - +
+
+ (single select) Selected Row: +
+
+ + + + +
+ +
-
- Selected Row(s): -
+
+ (multi-select) Selected Row(s): +
- + diff --git a/aurelia-slickgrid/src/examples/slickgrid/example10.ts b/aurelia-slickgrid/src/examples/slickgrid/example10.ts index 6d6b007af..1ad30ffe0 100644 --- a/aurelia-slickgrid/src/examples/slickgrid/example10.ts +++ b/aurelia-slickgrid/src/examples/slickgrid/example10.ts @@ -1,63 +1,80 @@ import { autoinject, bindable } from 'aurelia-framework'; -import { Column, FieldType, Formatter, Formatters, GridExtraService, GridExtraUtils, GridOption } from '../../aurelia-slickgrid'; +import { Column, FieldType, Formatter, Formatters, GridOption } from '../../aurelia-slickgrid'; @autoinject() export class Example2 { - @bindable() gridObj; title = 'Example 10: Grid with Row Selection'; subTitle = ` Row selection, single or multi-select (Wiki docs).
    +
  • Single Select, you can click on any cell to make the row active
  • +
  • Multiple Selections, you need to specifically click on the checkbox to make 1 or more selections
  • Note that "enableExcelCopyBuffer" cannot be used at the same time as Row Selection because there can exist only 1 SelectionModel at a time
`; - columnDefinitions: Column[]; - gridOptions: GridOption; - dataset: any[]; - dataviewObj: any; - isMultiSelect = true; - selectedObjects: any[] = []; + columnDefinitions1: Column[]; + columnDefinitions2: Column[]; + gridOptions1: GridOption; + gridOptions2: GridOption; + dataset1: any[]; + dataset2: any[]; + selectedTitles: any[]; + selectedTitle = ''; - constructor(private gridExtraService: GridExtraService) { + constructor() { // define the grid options & columns and then create the grid itself - this.defineGrid(); + this.defineGrids(); } attached() { // populate the dataset once the grid is ready - this.getData(); - } - - detached() { - // unsubscrible any Slick.Event you might have used - // a reminder again, these are SlickGrid Event, not Event Aggregator events - this.gridObj.onSelectedRowsChanged.unsubscribe(); + this.dataset1 = this.prepareData(); + this.dataset2 = this.prepareData(); } /* Define grid Options and Columns */ - defineGrid() { - this.columnDefinitions = [ + defineGrids() { + this.columnDefinitions1 = [ + { id: 'title', name: 'Title', field: 'title', sortable: true, type: FieldType.string }, + { id: 'duration', name: 'Duration (days)', field: 'duration', sortable: true, type: FieldType.number }, + { id: 'complete', name: '% Complete', field: 'percentComplete', formatter: Formatters.percentCompleteBar, type: FieldType.number, sortable: true }, + { id: 'start', name: 'Start', field: 'start', formatter: Formatters.dateIso, sortable: true, type: FieldType.dateIso, exportWithFormatter: true }, + { id: 'finish', name: 'Finish', field: 'finish', formatter: Formatters.dateIso, sortable: true, type: FieldType.date, exportWithFormatter: true }, + { id: 'effort-driven', name: 'Effort Driven', field: 'effortDriven', formatter: Formatters.checkmark, type: FieldType.number, sortable: true } + ]; + this.columnDefinitions2 = [ { id: 'title', name: 'Title', field: 'title', sortable: true, type: FieldType.string }, { id: 'duration', name: 'Duration (days)', field: 'duration', sortable: true, type: FieldType.number }, { id: 'complete', name: '% Complete', field: 'percentComplete', formatter: Formatters.percentCompleteBar, type: FieldType.number, sortable: true }, - { id: 'start', name: 'Start', field: 'start', formatter: Formatters.dateIso, sortable: true, type: FieldType.dateIso }, - { id: 'finish', name: 'Finish', field: 'finish', formatter: Formatters.dateIso, sortable: true, type: FieldType.date }, + { id: 'start', name: 'Start', field: 'start', formatter: Formatters.dateIso, sortable: true, type: FieldType.dateIso, exportWithFormatter: true }, + { id: 'finish', name: 'Finish', field: 'finish', formatter: Formatters.dateIso, sortable: true, type: FieldType.date, exportWithFormatter: true }, { id: 'effort-driven', name: 'Effort Driven', field: 'effortDriven', formatter: Formatters.checkmark, type: FieldType.number, sortable: true } ]; - this.gridOptions = { - autoResize: { - containerId: 'demo-container', - sidePadding: 15 + this.gridOptions1 = { + enableAutoResize: false, + enableCellNavigation: true, + enableCheckboxSelector: true, + rowSelectionOptions: { + // True (Single Selection), False (Multiple Selections) + selectActiveRow: true + }, + enableRowSelection: true + }; + this.gridOptions2 = { + enableAutoResize: false, + enableCellNavigation: true, + rowSelectionOptions: { + // True (Single Selection), False (Multiple Selections) + selectActiveRow: false }, - enableAutoResize: true, - enableCellNavigation: false, enableCheckboxSelector: true, + enableRowSelection: true, preselectedRows: [0, 2] }; } - getData() { + prepareData() { // mock a dataset const mockDataset = []; for (let i = 0; i < 500; i++) { @@ -77,31 +94,26 @@ export class Example2 { effortDriven: (i % 5 === 0) }; } - this.dataset = mockDataset; + return mockDataset; } - gridObjChanged(grid) { - this.gridObj = grid; - - grid.onSelectedRowsChanged.subscribe((e, args) => { - if (Array.isArray(args.rows)) { - this.selectedObjects = args.rows.map(idx => { - const item = grid.getDataItem(idx); - return item.title || ''; - }); - } - }); + onGrid1SelectedRowsChanged(e, args) { + const grid = args && args.grid; + if (Array.isArray(args.rows)) { + this.selectedTitle = args.rows.map(idx => { + const item = grid.getDataItem(idx); + return item.title || ''; + }); + } } - onChooseMultiSelectType(isMultiSelect) { - this.isMultiSelect = isMultiSelect; - - this.gridObj.setOptions({ - enableCellNavigation: !isMultiSelect, - enableCheckboxSelector: isMultiSelect - }); // change the grid option dynamically - this.gridExtraService.setSelectedRows([]); - - return true; + onGrid2SelectedRowsChanged(e, args) { + const grid = args && args.grid; + if (grid && Array.isArray(args.rows)) { + this.selectedTitles = args.rows.map(idx => { + const item = grid.getDataItem(idx); + return item.title || ''; + }); + } } } diff --git a/aurelia-slickgrid/src/examples/slickgrid/example11.html b/aurelia-slickgrid/src/examples/slickgrid/example11.html index 19b206184..082eb546e 100644 --- a/aurelia-slickgrid/src/examples/slickgrid/example11.html +++ b/aurelia-slickgrid/src/examples/slickgrid/example11.html @@ -11,6 +11,11 @@

${title}


- + - \ No newline at end of file + diff --git a/aurelia-slickgrid/src/examples/slickgrid/example11.ts b/aurelia-slickgrid/src/examples/slickgrid/example11.ts index ca6c8aa41..0b1340bab 100644 --- a/aurelia-slickgrid/src/examples/slickgrid/example11.ts +++ b/aurelia-slickgrid/src/examples/slickgrid/example11.ts @@ -1,5 +1,13 @@ import { autoinject } from 'aurelia-framework'; -import { Column, Editors, FieldType, Formatter, Formatters, GridExtraService, GridExtraUtils, GridOption, OnEventArgs, ResizerService } from '../../aurelia-slickgrid'; +import { + AureliaGridInstance, + Column, + Editors, + FieldType, + Formatters, + GridOption, + OnEventArgs +} from '../../aurelia-slickgrid'; @autoinject() export class Example11 { @@ -20,12 +28,13 @@ export class Example11 { `; + aureliaGrid: AureliaGridInstance; columnDefinitions: Column[]; gridOptions: GridOption; dataset: any[]; updatedObject: any; - constructor(private gridExtraService: GridExtraService, private resizer: ResizerService) { + constructor() { // define the grid options & columns and then create the grid itself this.defineGrid(); } @@ -35,21 +44,65 @@ export class Example11 { this.getData(); } + aureliaGridReady(aureliaGrid: AureliaGridInstance) { + this.aureliaGrid = aureliaGrid; + } + /* Define grid Options and Columns */ defineGrid() { this.columnDefinitions = [ - { id: 'title', name: 'Title', field: 'title', sortable: true, type: FieldType.string, editor: Editors.longText }, { - id: 'duration', name: 'Duration (days)', field: 'duration', sortable: true, type: FieldType.number, editor: Editors.text, - onCellChange: (args: OnEventArgs) => { + id: 'title', name: 'Title', field: 'title', + sortable: true, + type: FieldType.string, + editor: { + model: Editors.longText + } + }, + { + id: 'duration', name: 'Duration (days)', field: 'duration', + sortable: true, + type: FieldType.number, + editor: { + model: Editors.text + }, + onCellChange: (e: Event, args: OnEventArgs) => { alert('onCellChange directly attached to the column definition'); console.log(args); } }, - { id: 'complete', name: '% Complete', field: 'percentComplete', formatter: Formatters.percentCompleteBar, type: FieldType.number, editor: Editors.integer }, - { id: 'start', name: 'Start', field: 'start', formatter: Formatters.dateIso, sortable: true, type: FieldType.date/*, editor: Editors.date*/ }, - { id: 'finish', name: 'Finish', field: 'finish', formatter: Formatters.dateIso, sortable: true, type: FieldType.date }, - { id: 'effort-driven', name: 'Effort Driven', field: 'effortDriven', formatter: Formatters.checkmark, type: FieldType.number, editor: Editors.checkbox } + { + id: 'complete', name: '% Complete', field: 'percentComplete', + formatter: Formatters.percentCompleteBar, + type: FieldType.number, + editor: { + model: Editors.integer + } + }, + { + id: 'start', name: 'Start', field: 'start', + formatter: Formatters.dateIso, + sortable: true, + type: FieldType.date, + /* + editor: { + model: Editors.date + } + */ + }, + { + id: 'finish', name: 'Finish', field: 'finish', + formatter: Formatters.dateIso, sortable: true, + type: FieldType.date + }, + { + id: 'effort-driven', name: 'Effort Driven', field: 'effortDriven', + formatter: Formatters.checkmark, + type: FieldType.number, + editor: { + model: Editors.checkbox + } + } ]; this.gridOptions = { @@ -105,16 +158,16 @@ export class Example11 { finish: new Date(randomYear, (randomMonth + 2), randomDay), effortDriven: true }; - this.gridExtraService.addItemToDatagrid(newItem); + this.aureliaGrid.gridService.addItemToDatagrid(newItem); } highlighFifthRow() { - this.gridExtraService.highlightRow(4, 1500); + this.aureliaGrid.gridService.highlightRow(4, 1500); } updateSecondItem() { - const firstItem = this.gridExtraService.getDataItemByRowNumber(1); + const firstItem = this.aureliaGrid.gridService.getDataItemByRowNumber(1); firstItem.duration = Math.round(Math.random() * 100); - this.gridExtraService.updateDataGridItem(firstItem); + this.aureliaGrid.gridService.updateDataGridItem(firstItem); } } diff --git a/aurelia-slickgrid/src/examples/slickgrid/example12.html b/aurelia-slickgrid/src/examples/slickgrid/example12.html index b18291eb7..584ad3a2e 100644 --- a/aurelia-slickgrid/src/examples/slickgrid/example12.html +++ b/aurelia-slickgrid/src/examples/slickgrid/example12.html @@ -26,6 +26,11 @@

${title}

- + diff --git a/aurelia-slickgrid/src/examples/slickgrid/example12.ts b/aurelia-slickgrid/src/examples/slickgrid/example12.ts index a8b374f7f..f47fc6818 100644 --- a/aurelia-slickgrid/src/examples/slickgrid/example12.ts +++ b/aurelia-slickgrid/src/examples/slickgrid/example12.ts @@ -1,6 +1,16 @@ import { autoinject } from 'aurelia-framework'; import { I18N } from 'aurelia-i18n'; -import { Column, DelimiterType, ExportService, FieldType, FileType, FilterType, Formatter, Formatters, GridOption } from '../../aurelia-slickgrid'; +import { + AureliaGridInstance, + Column, + DelimiterType, + FieldType, + FileType, + Filters, + Formatter, + Formatters, + GridOption +} from '../../aurelia-slickgrid'; @autoinject() export class Example12 { @@ -32,13 +42,14 @@ export class Example12 { `; + aureliaGrid: AureliaGridInstance; gridOptions: GridOption; columnDefinitions: Column[]; dataset: any[]; selectedLanguage: string; duplicateTitleHeaderCount = 1; - constructor(private exportService: ExportService, private i18n: I18N) { + constructor(private i18n: I18N) { // define the grid options & columns and then create the grid itself this.defineGrid(); this.selectedLanguage = this.i18n.getLocale(); @@ -49,14 +60,18 @@ export class Example12 { this.getData(); } + aureliaGridReady(aureliaGrid: AureliaGridInstance) { + this.aureliaGrid = aureliaGrid; + } + /* Define grid Options and Columns */ defineGrid() { this.columnDefinitions = [ { id: 'title', name: 'Title', field: 'id', headerKey: 'TITLE', formatter: this.taskTranslateFormatter, sortable: true, minWidth: 100, filterable: true, params: { useFormatterOuputToFilter: true } }, { id: 'description', name: 'Description', field: 'description', filterable: true, sortable: true, minWidth: 80 }, { id: 'duration', name: 'Duration (days)', field: 'duration', headerKey: 'DURATION', sortable: true, minWidth: 100, filterable: true }, - { id: 'start', name: 'Start', field: 'start', headerKey: 'START', formatter: Formatters.dateIso, minWidth: 100, filterable: true }, - { id: 'finish', name: 'Finish', field: 'finish', headerKey: 'FINISH', formatter: Formatters.dateIso, minWidth: 100, filterable: true }, + { id: 'start', name: 'Start', field: 'start', headerKey: 'START', formatter: Formatters.dateIso, minWidth: 100, filterable: true, filter: { model: Filters.compoundDate } }, + { id: 'finish', name: 'Finish', field: 'finish', headerKey: 'FINISH', formatter: Formatters.dateIso, minWidth: 100, filterable: true, filter: { model: Filters.compoundDate } }, { id: 'completedBool', name: 'Completed', field: 'completedBool', headerKey: 'COMPLETED', minWidth: 100, sortable: true, @@ -65,7 +80,7 @@ export class Example12 { filterable: true, filter: { collection: [{ value: '', label: '' }, { value: true, labelKey: 'TRUE' }, { value: false, labelKey: 'FALSE' }], - type: FilterType.singleSelect, + model: Filters.singleSelect, enableTranslateLabel: true, filterOptions: { autoDropWidth: true @@ -83,7 +98,7 @@ export class Example12 { property: 'labelKey', // will sort by translated value since "enableTranslateLabel" is true sortDesc: true }, - type: FilterType.singleSelect, + model: Filters.singleSelect, enableTranslateLabel: true, filterOptions: { autoDropWidth: true @@ -103,17 +118,15 @@ export class Example12 { enableExcelCopyBuffer: true, enableFiltering: true, enableTranslate: true, + i18n: this.i18n, exportOptions: { // set at the grid option level, meaning all column will evaluate the Formatter (when it has a Formatter defined) exportWithFormatter: true, sanitizeDataExport: true }, gridMenu: { - showExportCsvCommand: true, // true by default, so it's optional - showExportTextDelimitedCommand: true // false by default, so if you want it, you will need to enable it - }, - params: { - i18n: this.i18n + hideExportCsvCommand: false, // false by default, so it's optional + hideExportTextDelimitedCommand: false // true by default, so if you want it, you will need to disable the flag } }; } @@ -145,7 +158,7 @@ export class Example12 { } exportToFile(type = 'csv') { - this.exportService.exportToFile({ + this.aureliaGrid.exportService.exportToFile({ delimiter: (type === 'csv') ? DelimiterType.comma : DelimiterType.tab, filename: 'myExport', format: (type === 'csv') ? FileType.csv : FileType.txt diff --git a/aurelia-slickgrid/src/examples/slickgrid/example13.html b/aurelia-slickgrid/src/examples/slickgrid/example13.html index 6321c8be2..17eaa7ed0 100644 --- a/aurelia-slickgrid/src/examples/slickgrid/example13.html +++ b/aurelia-slickgrid/src/examples/slickgrid/example13.html @@ -46,6 +46,6 @@

${title}

+ asg-on-dataview-created.delegate="onDataviewCreated($event.detail)"> diff --git a/aurelia-slickgrid/src/examples/slickgrid/example13.ts b/aurelia-slickgrid/src/examples/slickgrid/example13.ts index f5d853696..a5033e9ca 100644 --- a/aurelia-slickgrid/src/examples/slickgrid/example13.ts +++ b/aurelia-slickgrid/src/examples/slickgrid/example13.ts @@ -10,7 +10,7 @@ export class Example13 {
  • Wiki docs
  • Fully dynamic and interactive multi-level grouping with filtering and aggregates over 50'000 items
  • Each grouping level can have its own aggregates (over child rows, child groups, or all descendant rows)..
  • -
  • Use "Aggregators" and "GroupTotalFormatters" directly from Angular-Slickgrid
  • +
  • Use "Aggregators" and "GroupTotalFormatters" directly from Aurelia-Slickgrid
  • `; diff --git a/aurelia-slickgrid/src/examples/slickgrid/example15.html b/aurelia-slickgrid/src/examples/slickgrid/example15.html new file mode 100644 index 000000000..cbf3fc1e3 --- /dev/null +++ b/aurelia-slickgrid/src/examples/slickgrid/example15.html @@ -0,0 +1,18 @@ + diff --git a/aurelia-slickgrid/src/examples/slickgrid/example15.ts b/aurelia-slickgrid/src/examples/slickgrid/example15.ts new file mode 100644 index 000000000..b669b6727 --- /dev/null +++ b/aurelia-slickgrid/src/examples/slickgrid/example15.ts @@ -0,0 +1,208 @@ +import { autoinject } from 'aurelia-framework'; +import { AureliaGridInstance, Column, FieldType, Filters, Formatters, GridOption, GridState, GridStateChange } from '../../aurelia-slickgrid'; + +function randomBetween(min, max) { + return Math.floor(Math.random() * (max - min + 1) + min); +} +const LOCAL_STORAGE_KEY = 'gridState'; +const NB_ITEMS = 500; + +@autoinject() +export class Example15 { + title = 'Example 15: Grid State & Presets using Local Storage'; + subTitle = ` + Grid State & Preset (Wiki docs) +
    +
      +
    • Uses Local Storage to persist the Grid State and uses Grid Options "presets" to put the grid back to it's previous state
    • +
        +
      • to demo this, simply change any columns (position reorder, visibility, size, filter, sort), then refresh your browser with (F5)
      • +
      +
    • Local Storage is just one option, you can use whichever is more convenient for you (Local Storage, Session Storage, DB, ...)
    • +
    +`; + + aureliaGrid: AureliaGridInstance; + columnDefinitions: Column[]; + gridOptions: GridOption; + dataset: any[]; + + constructor() { + const presets = JSON.parse(localStorage[LOCAL_STORAGE_KEY] || null); + + // use some Grid State preset defaults if you wish + // presets = presets || this.useDefaultPresets(); + + this.defineGrid(presets); + } + + attached() { + // populate the dataset once the grid is ready + this.getData(); + } + + detached() { + this.saveCurrentGridState(); + } + + aureliaGridReady(aureliaGrid: AureliaGridInstance) { + this.aureliaGrid = aureliaGrid; + } + + /** Clear the Grid State from Local Storage and reset the grid to it's original state */ + clearGridStateFromLocalStorage() { + localStorage[LOCAL_STORAGE_KEY] = null; + this.aureliaGrid.gridService.resetGrid(this.columnDefinitions); + } + + /* Define grid Options and Columns */ + defineGrid(gridStatePresets?: GridState) { + // prepare a multiple-select array to filter with + const multiSelectFilterArray = []; + for (let i = 0; i < NB_ITEMS; i++) { + multiSelectFilterArray.push({ value: i, label: i }); + } + + this.columnDefinitions = [ + { + id: 'title', + name: 'Title', + field: 'title', + filterable: true, + sortable: true, + type: FieldType.string, + minWidth: 45, + filter: { + model: Filters.compoundInput + } + }, + { + id: 'description', name: 'Description', field: 'description', filterable: true, sortable: true, minWidth: 80, + type: FieldType.string, + filter: { + model: Filters.input + } + }, + { + id: 'duration', name: 'Duration (days)', field: 'duration', sortable: true, type: FieldType.number, exportCsvForceToKeepAsString: true, + minWidth: 55, + filterable: true, + filter: { + collection: multiSelectFilterArray, + searchTerms: [1, 33, 50], // default selection + model: Filters.multipleSelect, + // we could add certain option(s) to the "multiple-select" plugin + filterOptions: { + maxHeight: 250, + width: 175 + } + } + }, + { + id: 'complete', name: '% Complete', field: 'percentComplete', formatter: Formatters.percentCompleteBar, minWidth: 70, type: FieldType.number, sortable: true, + filterable: true, filter: { model: Filters.compoundInput } + }, + { + id: 'start', name: 'Start', field: 'start', formatter: Formatters.dateIso, sortable: true, minWidth: 75, exportWithFormatter: true, + type: FieldType.date, filterable: true, filter: { model: Filters.compoundDate } + }, + { + id: 'effort-driven', name: 'Effort Driven', field: 'effortDriven', minWidth: 85, maxWidth: 85, formatter: Formatters.checkmark, + type: FieldType.boolean, + sortable: true, + filterable: true, + filter: { + collection: [{ value: '', label: '' }, { value: true, label: 'True' }, { value: false, label: 'False' }], + model: Filters.singleSelect, + + // we could add certain option(s) to the "multiple-select" plugin + filterOptions: { + autoDropWidth: true + }, + } + } + ]; + + this.gridOptions = { + autoResize: { + containerId: 'demo-container', + sidePadding: 15 + }, + enableFiltering: true + }; + + // reload the Grid State with the grid options presets + // but make sure the colums array is part of the Grid State before using them as presets + if (gridStatePresets) { + this.gridOptions.presets = gridStatePresets; + } + } + + getData() { + // mock a dataset + this.dataset = []; + for (let i = 0; i < NB_ITEMS; i++) { + const randomDuration = Math.round(Math.random() * 100); + const randomYear = randomBetween(2000, 2025); + const randomYearShort = randomBetween(10, 25); + const randomMonth = randomBetween(1, 12); + const randomMonthStr = (randomMonth < 10) ? `0${randomMonth}` : randomMonth; + const randomDay = randomBetween(10, 28); + const randomPercent = randomBetween(0, 100); + const randomHour = randomBetween(10, 23); + const randomTime = randomBetween(10, 59); + + this.dataset[i] = { + id: i, + title: 'Task ' + i, + description: (i % 5) ? 'desc ' + i : null, // also add some random to test NULL field + duration: randomDuration, + percentComplete: randomPercent, + percentCompleteNumber: randomPercent, + start: new Date(randomYear, randomMonth, randomDay), // provide a Date format + usDateShort: `${randomMonth}/${randomDay}/${randomYearShort}`, // provide a date US Short in the dataset + utcDate: `${randomYear}-${randomMonthStr}-${randomDay}T${randomHour}:${randomTime}:${randomTime}Z`, + effortDriven: (i % 3 === 0) + }; + } + } + + /** Dispatched event of a Grid State Changed event (which contain a "change" and the "gridState") */ + gridStateChanged(gridStateChanges: GridStateChange) { + console.log('Client sample, Grid State changed:: ', gridStateChanges); + localStorage[LOCAL_STORAGE_KEY] = JSON.stringify(gridStateChanges.gridState); + } + + /** Save Grid State in LocaleStorage */ + saveCurrentGridState() { + const gridState: GridState = this.aureliaGrid.gridStateService.getCurrentGridState(); + console.log('Client sample, current Grid State:: ', gridState); + localStorage[LOCAL_STORAGE_KEY] = JSON.stringify(gridState); + } + + useDefaultPresets() { + // use columnDef searchTerms OR use presets as shown below + return { + columns: [ + { columnId: 'description', width: 170 }, // flip column position of Title/Description to Description/Title + { columnId: 'title', width: 55 }, + { columnId: 'duration' }, + { columnId: 'complete' }, + { columnId: 'start' }, + { columnId: 'usDateShort' }, + { columnId: 'utcDate' }, + // { columnId: 'effort-driven' }, // to HIDE a column, simply ommit it from the preset array + ], + filters: [ + { columnId: 'duration', searchTerms: [2, 22, 44] }, + // { columnId: 'complete', searchTerms: ['5'], operator: '>' }, + { columnId: 'usDateShort', operator: '<', searchTerms: ['4/20/25'] }, + // { columnId: 'effort-driven', searchTerms: [true] } + ], + sorters: [ + { columnId: 'duration', direction: 'DESC' }, + { columnId: 'complete', direction: 'ASC' } + ], + }; + } +} diff --git a/aurelia-slickgrid/src/examples/slickgrid/example3.html b/aurelia-slickgrid/src/examples/slickgrid/example3.html index 3d0218ff6..216ada762 100644 --- a/aurelia-slickgrid/src/examples/slickgrid/example3.html +++ b/aurelia-slickgrid/src/examples/slickgrid/example3.html @@ -28,13 +28,19 @@

    ${title}

    Updated Item: ${updatedObject | stringify}
    - ${alertWarning} + ${alertWarning}
    - +
    diff --git a/aurelia-slickgrid/src/examples/slickgrid/example3.ts b/aurelia-slickgrid/src/examples/slickgrid/example3.ts index ec84baa7b..a919f45bc 100644 --- a/aurelia-slickgrid/src/examples/slickgrid/example3.ts +++ b/aurelia-slickgrid/src/examples/slickgrid/example3.ts @@ -1,31 +1,23 @@ +import { autoinject } from 'aurelia-framework'; +import { I18N } from 'aurelia-i18n'; import { - I18N -} from 'aurelia-i18n'; -import { - autoinject, - bindable -} from 'aurelia-framework'; -import { + AureliaGridInstance, Column, Editors, FieldType, Formatters, - GridExtraService, - GridExtraUtils, GridOption, OnEventArgs, - OperatorType, - ResizerService + OperatorType } from '../../aurelia-slickgrid'; +import { CustomInputEditor } from './custom-inputEditor'; // using external non-typed js libraries declare var Slick: any; @autoinject() export class Example3 { - @bindable() gridObj: any; - @bindable() dataview: any; - title = 'Example 3: Editors'; + title = 'Example 3: Editors / Delete'; subTitle = ` Grid with Inline Editors and onCellClick actions (Wiki docs).
      @@ -39,6 +31,8 @@ export class Example3 {
    `; private _commandQueue = []; + aureliaGrid: AureliaGridInstance; + gridObj: any; gridOptions: GridOption; columnDefinitions: Column[]; dataset: any[]; @@ -47,7 +41,7 @@ export class Example3 { alertWarning: any; selectedLanguage: string; - constructor(private gridExtraService: GridExtraService, private i18n: I18N, private resizer: ResizerService) { + constructor(private i18n: I18N) { // define the grid options & columns and then create the grid itself this.defineGrid(); this.selectedLanguage = this.i18n.getLocale(); @@ -58,11 +52,9 @@ export class Example3 { this.getData(); } - detached() { - // unsubscrible any Slick.Event you might have used - // a reminder again, these are SlickGrid Event, not Event Aggregator events - this.gridObj.onCellChange.unsubscribe(); - this.gridObj.onClick.unsubscribe(); + aureliaGridReady(aureliaGrid: AureliaGridInstance) { + this.aureliaGrid = aureliaGrid; + this.gridObj = aureliaGrid && aureliaGrid.slickGrid; } /* Define grid Options and Columns */ @@ -75,11 +67,11 @@ export class Example3 { minWidth: 30, maxWidth: 30, // use onCellClick OR grid.onClick.subscribe which you can see down below - onCellClick: (args: OnEventArgs) => { + onCellClick: (e: Event, args: OnEventArgs) => { console.log(args); this.alertWarning = `Editing: ${args.dataContext.title}`; - this.gridExtraService.highlightRow(args.row, 1500); - this.gridExtraService.setSelectedRow(args.row); + this.aureliaGrid.gridService.highlightRow(args.row, 1500); + this.aureliaGrid.gridService.setSelectedRow(args.row); } }, { id: 'delete', @@ -88,32 +80,47 @@ export class Example3 { formatter: Formatters.deleteIcon, minWidth: 30, maxWidth: 30, - // use onCellClick OR grid.onClick.subscribe which you can see down below - /* - onCellClick: (args: OnEventArgs) => { - console.log(args); - this.alertWarning = `Deleting: ${args.dataContext.title}`; - } - */ + // use onCellClick OR grid.onClick.subscribe which you can see down below + /* + onCellClick: (e: Event, args: OnEventArgs) => { + console.log(args); + this.alertWarning = `Deleting: ${args.dataContext.title}`; + } + */ }, { id: 'title', name: 'Title', field: 'title', sortable: true, type: FieldType.string, - editor: Editors.longText, + editor: { + model: Editors.longText + }, minWidth: 100, - onCellChange: (args: OnEventArgs) => { + onCellChange: (e: Event, args: OnEventArgs) => { console.log(args); this.alertWarning = `Updated Title: ${args.dataContext.title}`; } + }, { + id: 'title2', + name: 'Title, Custom Editor', + field: 'title', + sortable: true, + type: FieldType.string, + editor: { + model: CustomInputEditor + }, + minWidth: 70 }, { id: 'duration', name: 'Duration (days)', field: 'duration', sortable: true, type: FieldType.number, - editor: Editors.integer, + editor: { + model: Editors.float, + params: { decimalPlaces: 2 } + }, minWidth: 100 }, { id: 'complete', @@ -121,10 +128,8 @@ export class Example3 { field: 'percentComplete', formatter: Formatters.multiple, type: FieldType.number, - editor: Editors.singleSelect, - minWidth: 100, - params: { - formatters: [Formatters.collection, Formatters.percentCompleteBar], + editor: { + model: Editors.singleSelect, collection: Array.from(Array(101).keys()).map(k => ({ value: k, label: k })), collectionSortBy: { property: 'label', @@ -135,6 +140,10 @@ export class Example3 { value: 0, operator: OperatorType.notEqual } + }, + minWidth: 100, + params: { + formatters: [Formatters.collectionEditor, Formatters.percentCompleteBar], } }, { id: 'start', @@ -144,7 +153,9 @@ export class Example3 { sortable: true, minWidth: 100, type: FieldType.date, - editor: Editors.date + editor: { + model: Editors.date + }, }, { id: 'finish', name: 'Finish', @@ -153,14 +164,18 @@ export class Example3 { sortable: true, minWidth: 100, type: FieldType.date, - editor: Editors.date + editor: { + model: Editors.date + }, }, { id: 'effort-driven', name: 'Effort Driven', field: 'effortDriven', formatter: Formatters.checkmark, type: FieldType.number, - editor: Editors.checkbox, + editor: { + model: Editors.checkbox, + }, minWidth: 70 }, { id: 'prerequisites', @@ -169,8 +184,8 @@ export class Example3 { minWidth: 100, sortable: true, type: FieldType.string, - editor: Editors.multipleSelect, - params: { + editor: { + model: Editors.multipleSelect, collection: Array.from(Array(12).keys()).map(k => ({ value: `Task ${k}`, label: `Task ${k}` })), collectionSortBy: { property: 'label', @@ -193,27 +208,15 @@ export class Example3 { }, editable: true, enableCellNavigation: true, - enableCheckboxSelector: false, - rowSelectionOptions: { - // True (Single Selection), False (Multiple Selections) - // Default to True when no "rowSelectionOptions" provided - selectActiveRow: true - }, enableExcelCopyBuffer: true, editCommandHandler: (item, column, editCommand) => { this._commandQueue.push(editCommand); editCommand.execute(); }, - params: { - i18n: this.i18n - } + i18n: this.i18n, }; } - dataviewChanged(dataview) { - this.dataview = dataview; - } - getData() { // mock a dataset const mockedDataset = []; @@ -238,42 +241,29 @@ export class Example3 { this.dataset = mockedDataset; } - gridObjChanged(grid) { - grid.onBeforeEditCell.subscribe((e, args) => { - console.log('before edit', e); - e.stopImmediatePropagation(); - }); - grid.onBeforeCellEditorDestroy.subscribe((e, args) => { - console.log('before destroy'); - e.stopPropagation(); - }); + onCellChanged(e, args) { + console.log('onCellChange', args); + this.updatedObject = args.item; + } - grid.onCellChange.subscribe((e, args) => { - console.log('onCellChange', args); - this.updatedObject = args.item; - this.resizer.resizeGrid(100); - }); + onCellClicked(e, args) { + const metadata = this.aureliaGrid.gridService.getColumnFromEventArguments(args); + console.log(metadata); - // You could also subscribe to grid.onClick - // Note that if you had already setup "onCellClick" in the column definition, you cannot use grid.onClick - grid.onClick.subscribe((e, args) => { - const column = GridExtraUtils.getColumnDefinitionAndData(args); - console.log('onClick', args, column); - if (column.columnDef.id === 'edit') { - this.alertWarning = `open a modal window to edit: ${column.dataContext.title}`; + if (metadata.columnDef.id === 'edit') { + this.alertWarning = `open a modal window to edit: ${metadata.dataContext.title}`; - // highlight the row, to customize the color, you can change the SASS variable $row-highlight-background-color - this.gridExtraService.highlightRow(args.row, 1500); + // highlight the row, to customize the color, you can change the SASS variable $row-highlight-background-color + this.aureliaGrid.gridService.highlightRow(args.row, 1500); - // you could also select the row, when using "enableCellNavigation: true", it automatically selects the row - // this.gridExtraService.setSelectedRow(args.row); + // you could also select the row, when using "enableCellNavigation: true", it automatically selects the row + // this.aureliaGrid.gridService.setSelectedRow(args.row); + } else if (metadata.columnDef.id === 'delete') { + if (confirm('Are you sure?')) { + this.aureliaGrid.gridService.deleteDataGridItemById(metadata.dataContext.id); + this.alertWarning = `Deleted: ${metadata.dataContext.title}`; } - if (column.columnDef.id === 'delete') { - if (confirm('Are you sure?')) { - this.gridExtraService.deleteDataGridItemById(column.dataContext.id); - } - } - }); + } } setAutoEdit(isAutoEdit) { diff --git a/aurelia-slickgrid/src/examples/slickgrid/example4.html b/aurelia-slickgrid/src/examples/slickgrid/example4.html index 62ba94a64..162c230e1 100644 --- a/aurelia-slickgrid/src/examples/slickgrid/example4.html +++ b/aurelia-slickgrid/src/examples/slickgrid/example4.html @@ -2,7 +2,12 @@

    ${title}

    - + diff --git a/aurelia-slickgrid/src/examples/slickgrid/example4.ts b/aurelia-slickgrid/src/examples/slickgrid/example4.ts index 5db4913d9..43c846a5a 100644 --- a/aurelia-slickgrid/src/examples/slickgrid/example4.ts +++ b/aurelia-slickgrid/src/examples/slickgrid/example4.ts @@ -1,6 +1,6 @@ import { autoinject } from 'aurelia-framework'; import { CustomInputFilter } from './custom-inputFilter'; -import { Column, FieldType, FilterType, Formatter, Formatters, GridOption, GridStateService } from '../../aurelia-slickgrid'; +import { AureliaGridInstance, Column, FieldType, Filters, Formatter, Formatters, GridOption } from '../../aurelia-slickgrid'; function randomBetween(min, max) { return Math.floor(Math.random() * (max - min + 1) + min); @@ -14,8 +14,8 @@ export class Example4 { Sort/Filter on client side only using SlickGrid DataView (Wiki docs)
      -
    • Support multi-sort (by default), hold "Shift" key and click on the next column to sort. -
    • All column types support the following operators: (>, >=, <, <=, <>, !=, =, ==, *) +
    • Support multi-sort (by default), hold "Shift" key and click on the next column to sort.
    • +
    • All column types support the following operators: (>, >=, <, <=, <>, !=, =, ==, *)
      • Example: >100 ... >=2001-01-01 ... >02/28/17
      • Note: For filters to work properly (default is string), make sure to provide a FieldType (type is against the dataset, not the Formatter)
      • @@ -25,15 +25,16 @@ export class Example4 {
      • FieldType of dateUtc/date (from dataset) can use an extra option of "filterSearchType" to let user filter more easily. For example, in the "UTC Date" field below, you can type ">02/28/2017", also when dealing with UTC you have to take the time difference in consideration.
    • On String filters, (*) can be used as startsWith (Hello* => matches "Hello Word") ... endsWith (*Doe => matches: "John Doe")
    • -
    • Custom Filter are now possible, "Description" column below, is a customized InputFilter with different placeholder. See Wiki - Custom Filter +
    • Custom Filter are now possible, "Description" column below, is a customized InputFilter with different placeholder. See Wiki - Custom Filter
    `; + aureliaGrid: AureliaGridInstance; columnDefinitions: Column[]; gridOptions: GridOption; dataset: any[]; - constructor(private gridStateService: GridStateService) { + constructor() { this.defineGrid(); } @@ -46,6 +47,10 @@ export class Example4 { this.saveCurrentGridState(); } + aureliaGridReady(aureliaGrid: AureliaGridInstance) { + this.aureliaGrid = aureliaGrid; + } + /* Define grid Options and Columns */ defineGrid() { // prepare a multiple-select array to filter with @@ -64,15 +69,14 @@ export class Example4 { type: FieldType.string, minWidth: 45, filter: { - type: FilterType.compoundInput + model: Filters.compoundInput } }, { id: 'description', name: 'Description', field: 'description', filterable: true, sortable: true, minWidth: 80, type: FieldType.string, filter: { - type: FilterType.custom, - customFilter: new CustomInputFilter() // create a new instance to make each Filter independent from each other + model: new CustomInputFilter() // create a new instance to make each Filter independent from each other customFilter } }, { @@ -86,7 +90,7 @@ export class Example4 { sortDesc: true, fieldType: FieldType.number }, - type: FilterType.multipleSelect, + model: Filters.multipleSelect, searchTerms: [1, 33, 50], // default selection // we could add certain option(s) to the "multiple-select" plugin filterOptions: { @@ -97,19 +101,19 @@ export class Example4 { }, { id: 'complete', name: '% Complete', field: 'percentComplete', formatter: Formatters.percentCompleteBar, minWidth: 70, type: FieldType.number, sortable: true, - filterable: true, filter: { type: FilterType.compoundInput } + filterable: true, filter: { model: Filters.compoundInput } }, { id: 'start', name: 'Start', field: 'start', formatter: Formatters.dateIso, sortable: true, minWidth: 75, exportWithFormatter: true, - type: FieldType.date, filterable: true, filter: { type: FilterType.compoundDate } + type: FieldType.date, filterable: true, filter: { model: Filters.compoundDate } }, { id: 'usDateShort', name: 'US Date Short', field: 'usDateShort', sortable: true, minWidth: 70, width: 70, - type: FieldType.dateUsShort, filterable: true, filter: { type: FilterType.compoundDate } + type: FieldType.dateUsShort, filterable: true, filter: { model: Filters.compoundDate } }, { id: 'utcDate', name: 'UTC Date', field: 'utcDate', formatter: Formatters.dateTimeIsoAmPm, sortable: true, minWidth: 115, - type: FieldType.dateUtc, outputType: FieldType.dateTimeIsoAmPm, filterable: true, filter: { type: FilterType.compoundDate } + type: FieldType.dateUtc, outputType: FieldType.dateTimeIsoAmPm, filterable: true, filter: { model: Filters.compoundDate } }, { id: 'effort-driven', name: 'Effort Driven', field: 'effortDriven', minWidth: 85, maxWidth: 85, formatter: Formatters.checkmark, @@ -118,7 +122,7 @@ export class Example4 { filterable: true, filter: { collection: [{ value: '', label: '' }, { value: true, label: 'True' }, { value: false, label: 'False' }], - type: FilterType.singleSelect, + model: Filters.singleSelect, // we could add certain option(s) to the "multiple-select" plugin filterOptions: { @@ -137,11 +141,21 @@ export class Example4 { // use columnDef searchTerms OR use presets as shown below presets: { + columns: [ + { columnId: 'description', width: 170 }, // flip column position of Title/Description to Description/Title + { columnId: 'title', width: 55 }, + { columnId: 'duration' }, + { columnId: 'complete' }, + { columnId: 'start' }, + { columnId: 'usDateShort' }, + { columnId: 'utcDate' }, + // { columnId: 'effort-driven' }, // to HIDE a column, simply ommit it from the preset array + ], filters: [ { columnId: 'duration', searchTerms: [2, 22, 44] }, - // { columnId: 'complete', searchTerm: '5', operator: '>' }, - { columnId: 'usDateShort', operator: '<', searchTerm: '4/20/25' }, - // { columnId: 'effort-driven', searchTerm: true } + // { columnId: 'complete', searchTerms: ['5'], operator: '>' }, + { columnId: 'usDateShort', operator: '<', searchTerms: ['4/20/25'] }, + // { columnId: 'effort-driven', searchTerms: [true] } ], sorters: [ { columnId: 'duration', direction: 'DESC' }, @@ -187,6 +201,6 @@ export class Example4 { /** Save current Filters, Sorters in LocaleStorage or DB */ saveCurrentGridState() { - console.log('Client sample, current Grid State:: ', this.gridStateService.getCurrentGridState()); + console.log('Client sample, current Grid State:: ', this.aureliaGrid.gridStateService.getCurrentGridState()); } } diff --git a/aurelia-slickgrid/src/examples/slickgrid/example5.html b/aurelia-slickgrid/src/examples/slickgrid/example5.html index 83df33974..2b1887a27 100644 --- a/aurelia-slickgrid/src/examples/slickgrid/example5.html +++ b/aurelia-slickgrid/src/examples/slickgrid/example5.html @@ -18,6 +18,11 @@

    ${title}

    - + diff --git a/aurelia-slickgrid/src/examples/slickgrid/example5.ts b/aurelia-slickgrid/src/examples/slickgrid/example5.ts index 0ce1627bd..a1b49bc2f 100644 --- a/aurelia-slickgrid/src/examples/slickgrid/example5.ts +++ b/aurelia-slickgrid/src/examples/slickgrid/example5.ts @@ -1,7 +1,7 @@ import { autoinject } from 'aurelia-framework'; import data from './sample-data/example-data'; import { HttpClient } from 'aurelia-http-client'; -import { Column, FieldType, FilterType, GridOdataService, GridOption, OperatorType } from '../../aurelia-slickgrid'; +import { AureliaGridInstance, Column, FieldType, Filters, GridOdataService, GridOption, OperatorType } from '../../aurelia-slickgrid'; const defaultPageSize = 20; const sampleDataRoot = 'src/examples/slickgrid/sample-data'; @@ -24,6 +24,7 @@ export class Example5 {
  • You can also preload a grid with certain "presets" like Filters / Sorters / Pagination Wiki - Grid Preset `; + aureliaGrid: AureliaGridInstance; columnDefinitions: Column[]; gridOptions: GridOption; dataset = []; @@ -32,24 +33,28 @@ export class Example5 { processing = false; status = { text: '', class: '' }; - constructor(private http: HttpClient, private odataService: GridOdataService) { + constructor(private http: HttpClient) { // define the grid options & columns and then create the grid itself this.defineGrid(); } + aureliaGridReady(aureliaGrid: AureliaGridInstance) { + this.aureliaGrid = aureliaGrid; + } + defineGrid() { this.columnDefinitions = [ { id: 'name', name: 'Name', field: 'name', sortable: true, type: FieldType.string, filterable: true, filter: { - type: FilterType.compoundInput + model: Filters.compoundInput } }, { id: 'gender', name: 'Gender', field: 'gender', filterable: true, sortable: true, filter: { - type: FilterType.singleSelect, + model: Filters.singleSelect, collection: [{ value: '', label: '' }, { value: 'male', label: 'male' }, { value: 'female', label: 'female' }] } }, @@ -72,7 +77,7 @@ export class Example5 { totalItems: 0 }, backendServiceApi: { - service: this.odataService, + service: new GridOdataService(), preProcess: () => this.displaySpinner(true), process: (query) => this.getCustomerApiCall(query), postProcess: (response) => { @@ -202,11 +207,19 @@ export class Example5 { filteredData = filteredData.filter(column => { const filterType = columnFilters[columnId].type; const searchTerm = columnFilters[columnId].term; - switch (filterType) { - case 'equal': return column[columnId].toLowerCase() === searchTerm; - case 'ends': return column[columnId].toLowerCase().endsWith(searchTerm); - case 'starts': return column[columnId].toLowerCase().startsWith(searchTerm); - case 'substring': return column[columnId].toLowerCase().includes(searchTerm); + let colId = columnId; + if (columnId && columnId.indexOf(' ') !== -1) { + const splitIds = columnId.split(' '); + colId = splitIds[splitIds.length - 1]; + } + const filterTerm = column[colId]; + if (filterTerm) { + switch (filterType) { + case 'equal': return filterTerm.toLowerCase() === searchTerm; + case 'ends': return filterTerm.toLowerCase().endsWith(searchTerm); + case 'starts': return filterTerm.toLowerCase().startsWith(searchTerm); + case 'substring': return filterTerm.toLowerCase().includes(searchTerm); + } } }); } diff --git a/aurelia-slickgrid/src/examples/slickgrid/example6.html b/aurelia-slickgrid/src/examples/slickgrid/example6.html index ebf7853af..c61dbc598 100644 --- a/aurelia-slickgrid/src/examples/slickgrid/example6.html +++ b/aurelia-slickgrid/src/examples/slickgrid/example6.html @@ -20,7 +20,13 @@

    ${title}

    - + diff --git a/aurelia-slickgrid/src/examples/slickgrid/example6.ts b/aurelia-slickgrid/src/examples/slickgrid/example6.ts index 933800d16..16b731606 100644 --- a/aurelia-slickgrid/src/examples/slickgrid/example6.ts +++ b/aurelia-slickgrid/src/examples/slickgrid/example6.ts @@ -2,7 +2,19 @@ import { Subscription, EventAggregator } from 'aurelia-event-aggregator'; import { autoinject } from 'aurelia-framework'; import { I18N } from 'aurelia-i18n'; import { HttpClient } from 'aurelia-http-client'; -import { Column, FieldType, FilterType, Formatters, GraphqlResult, GraphqlService, GraphqlServiceOption, GridOption, GridStateService, OperatorType, SortDirection } from '../../aurelia-slickgrid'; +import { + AureliaGridInstance, + Column, + FieldType, + Filters, + Formatters, + GraphqlResult, + GraphqlService, + GraphqlServiceOption, + GridOption, + OperatorType, + SortDirection +} from '../../aurelia-slickgrid'; const defaultPageSize = 20; const GRAPHQL_QUERY_DATASET_NAME = 'users'; @@ -24,6 +36,8 @@ export class Example6 {
  • You can also preload a grid with certain "presets" like Filters / Sorters / Pagination Wiki - Grid Preset `; + + aureliaGrid: AureliaGridInstance; columnDefinitions: Column[]; gridOptions: GridOption; dataset = []; @@ -35,7 +49,7 @@ export class Example6 { status = { text: '', class: '' }; Subscription: Subscription; - constructor(private ea: EventAggregator, private gridStateService: GridStateService, private http: HttpClient, private graphqlService: GraphqlService, private i18n: I18N) { + constructor(private ea: EventAggregator, private http: HttpClient, private i18n: I18N) { // define the grid options & columns and then create the grid itself this.defineGrid(); this.selectedLanguage = this.i18n.getLocale(); @@ -47,13 +61,17 @@ export class Example6 { this.Subscription.dispose(); } + aureliaGridReady(aureliaGrid: AureliaGridInstance) { + this.aureliaGrid = aureliaGrid; + } + defineGrid() { this.columnDefinitions = [ { id: 'name', field: 'name', headerKey: 'NAME', filterable: true, sortable: true, type: FieldType.string, width: 60 }, { id: 'gender', field: 'gender', headerKey: 'GENDER', filterable: true, sortable: true, width: 60, filter: { - type: FilterType.singleSelect, + model: Filters.singleSelect, collection: [{ value: '', label: '' }, { value: 'male', label: 'male', labelKey: 'MALE' }, { value: 'female', label: 'female', labelKey: 'FEMALE' }] } }, @@ -62,7 +80,7 @@ export class Example6 { sortable: true, filterable: true, filter: { - type: FilterType.multipleSelect, + model: Filters.multipleSelect, collection: [{ value: 'acme', label: 'Acme' }, { value: 'abc', label: 'Company ABC' }, { value: 'xyz', label: 'Company XYZ' }] } }, @@ -72,7 +90,7 @@ export class Example6 { type: FieldType.number, filterable: true, sortable: true, filter: { - type: FilterType.compoundInput + model: Filters.compoundInput }, formatter: Formatters.multiple, params: { formatters: [Formatters.complexObject, Formatters.translate] } }, @@ -86,6 +104,7 @@ export class Example6 { enableCheckboxSelector: true, enableRowSelection: true, enableTranslate: true, + i18n: this.i18n, gridMenu: { resizeOnShowHeaderRow: true, }, @@ -96,10 +115,17 @@ export class Example6 { }, presets: { + columns: [ + { columnId: 'name', width: 120 }, + { columnId: 'gender', width: 55 }, + { columnId: 'company' }, + { columnId: 'billing.address.zip' }, // flip column position of Street/Zip to Zip/Street + { columnId: 'billing.address.street', width: 120 }, + ], // you can also type operator as string, e.g.: operator: 'EQ' filters: [ - { columnId: 'gender', searchTerm: 'male', operator: OperatorType.equal }, - { columnId: 'name', searchTerm: 'John Doe', operator: OperatorType.contains }, + { columnId: 'gender', searchTerms: ['male'], operator: OperatorType.equal }, + { columnId: 'name', searchTerms: ['John Doe'], operator: OperatorType.contains }, { columnId: 'company', searchTerms: ['xyz'], operator: 'IN' } ], sorters: [ @@ -111,14 +137,14 @@ export class Example6 { }, backendServiceApi: { - service: this.graphqlService, + service: new GraphqlService(), options: this.getBackendOptions(this.isWithCursor), // you can define the onInit callback OR enable the "executeProcessCommandOnInit" flag in the service init // onInit: (query) => this.getCustomerApiCall(query) preProcess: () => this.displaySpinner(true), process: (query) => this.getCustomerApiCall(query), postProcess: (result: GraphqlResult) => this.displaySpinner(false) - } + }, }; } @@ -173,14 +199,14 @@ export class Example6 { return new Promise((resolve, reject) => { setTimeout(() => { - this.graphqlQuery = this.graphqlService.buildQuery(); + this.graphqlQuery = this.aureliaGrid.backendService.buildQuery(); resolve(mockedResult); }, 500); }); } saveCurrentGridState() { - console.log('GraphQL current grid state', this.gridStateService.getCurrentGridState()); + console.log('GraphQL current grid state', this.aureliaGrid.gridStateService.getCurrentGridState()); } switchLanguage() { diff --git a/aurelia-slickgrid/src/examples/slickgrid/example7.html b/aurelia-slickgrid/src/examples/slickgrid/example7.html index 8fd617a42..8d7d98c15 100644 --- a/aurelia-slickgrid/src/examples/slickgrid/example7.html +++ b/aurelia-slickgrid/src/examples/slickgrid/example7.html @@ -2,7 +2,11 @@

    ${title}

    - + - \ No newline at end of file + diff --git a/aurelia-slickgrid/src/examples/slickgrid/example7.ts b/aurelia-slickgrid/src/examples/slickgrid/example7.ts index f2a7b1f3f..16855d0a6 100644 --- a/aurelia-slickgrid/src/examples/slickgrid/example7.ts +++ b/aurelia-slickgrid/src/examples/slickgrid/example7.ts @@ -1,5 +1,5 @@ import { autoinject } from 'aurelia-framework'; -import { Column, GridOption, FieldType, Formatters, FormElementType } from '../../aurelia-slickgrid'; +import { AureliaGridInstance, Column, GridOption, FieldType, Formatters } from '../../aurelia-slickgrid'; let columnsWithHighlightingById = {}; @@ -28,6 +28,7 @@ export class Example7 { columnDefinitions: Column[]; gridOptions: GridOption; dataset = []; + aureliaGrid: AureliaGridInstance; gridObj: any; constructor() { @@ -41,6 +42,11 @@ export class Example7 { this.getData(); } + aureliaGridReady(aureliaGrid: AureliaGridInstance) { + this.aureliaGrid = aureliaGrid; + this.gridObj = aureliaGrid && aureliaGrid.slickGrid; + } + defineGrid() { this.columnDefinitions = []; @@ -156,8 +162,4 @@ export class Example7 { } this.dataset = mockDataset; } - - gridObjChanged(grid) { - this.gridObj = grid; - } } diff --git a/aurelia-slickgrid/src/examples/slickgrid/example8.html b/aurelia-slickgrid/src/examples/slickgrid/example8.html index daf03e57f..8d7d98c15 100644 --- a/aurelia-slickgrid/src/examples/slickgrid/example8.html +++ b/aurelia-slickgrid/src/examples/slickgrid/example8.html @@ -2,7 +2,11 @@

    ${title}

    - + diff --git a/aurelia-slickgrid/src/examples/slickgrid/example8.ts b/aurelia-slickgrid/src/examples/slickgrid/example8.ts index bb69e6542..66659be45 100644 --- a/aurelia-slickgrid/src/examples/slickgrid/example8.ts +++ b/aurelia-slickgrid/src/examples/slickgrid/example8.ts @@ -1,11 +1,9 @@ import { autoinject, bindable } from 'aurelia-framework'; -import { Column, ColumnSort, ControlAndPluginService, FieldType, Formatter, Formatters, GridOption, SortService } from '../../aurelia-slickgrid'; +import { AureliaGridInstance, Column, ColumnSort, FieldType, Formatter, Formatters, GridOption } from '../../aurelia-slickgrid'; import * as $ from 'jquery'; @autoinject() export class Example8 { - @bindable() gridObj: any; - @bindable() dataview: any; title = 'Example 8: Header Menu Plugin'; subTitle = ` This example demonstrates using the Slick.Plugins.HeaderMenu plugin to easily add menus to colum headers.
    @@ -18,12 +16,16 @@ export class Example8 {
  • Try hiding any columns (you use the "Column Picker" plugin by doing a right+click on the header to show the column back)
  • `; + + aureliaGrid: AureliaGridInstance; columnDefinitions: Column[]; gridOptions: GridOption; dataset = []; + dataView: any; + gridObj: any; visibleColumns; - constructor(private controlService: ControlAndPluginService, private sortService: SortService) { + constructor() { // define the grid options & columns and then create the grid itself this.defineGrid(); } @@ -33,6 +35,12 @@ export class Example8 { this.getData(); } + aureliaGridReady(aureliaGrid: AureliaGridInstance) { + this.aureliaGrid = aureliaGrid; + this.gridObj = aureliaGrid && aureliaGrid.slickGrid; + this.dataView = aureliaGrid && aureliaGrid.dataView; + } + detached() { // unsubscrible any Slick.Event you might have used // a reminder again, these are SlickGrid Event, not Event Aggregator events @@ -90,15 +98,15 @@ export class Example8 { headerMenu: { onCommand: (e, args) => { if (args.command === 'hide') { - this.controlService.hideColumn(args.column); - this.controlService.autoResizeColumns(); + this.aureliaGrid.pluginService.hideColumn(args.column); + this.aureliaGrid.pluginService.autoResizeColumns(); } else if (args.command === 'sort-asc' || args.command === 'sort-desc') { // get previously sorted columns - const cols: ColumnSort[] = this.sortService.getPreviousColumnSorts(args.column.id + ''); + const cols: ColumnSort[] = this.aureliaGrid.sortService.getPreviousColumnSorts(args.column.id + ''); // add to the column array, the column sorted by the header menu cols.push({ sortCol: args.column, sortAsc: (args.command === 'sort-asc') }); - this.sortService.onLocalSortChanged(this.gridObj, this.dataview, cols); + this.aureliaGrid.sortService.onLocalSortChanged(this.gridObj, this.dataView, cols); // update the this.gridObj sortColumns array which will at the same add the visual sort icon(s) on the UI const newSortColumns: ColumnSort[] = cols.map((col) => { @@ -129,12 +137,4 @@ export class Example8 { } this.dataset = mockDataset; } - - gridObjChanged(grid) { - this.gridObj = grid; - } - - dataviewChanged(dataview) { - this.dataview = dataview; - } } diff --git a/aurelia-slickgrid/src/examples/slickgrid/example9.html b/aurelia-slickgrid/src/examples/slickgrid/example9.html index c0ba9f505..8d7d98c15 100644 --- a/aurelia-slickgrid/src/examples/slickgrid/example9.html +++ b/aurelia-slickgrid/src/examples/slickgrid/example9.html @@ -2,7 +2,11 @@

    ${title}

    - + - \ No newline at end of file + diff --git a/aurelia-slickgrid/src/examples/slickgrid/example9.ts b/aurelia-slickgrid/src/examples/slickgrid/example9.ts index 477bd2308..d82a88637 100644 --- a/aurelia-slickgrid/src/examples/slickgrid/example9.ts +++ b/aurelia-slickgrid/src/examples/slickgrid/example9.ts @@ -1,10 +1,8 @@ import { autoinject, bindable } from 'aurelia-framework'; -import { Column, FieldType, FilterType, FilterService, Formatter, Formatters, GridOption, SortService } from '../../aurelia-slickgrid'; +import { AureliaGridInstance, Column, FieldType, Filters, Formatter, Formatters, GridOption } from '../../aurelia-slickgrid'; @autoinject() export class Example9 { - @bindable() gridObj: any; - @bindable() dataviewObj: any; title = 'Example 9: Grid Menu Control'; subTitle = ` This example demonstrates using the Slick.Controls.GridMenu plugin to easily add a Grid Menu (aka hamburger menu) on the top right corner of the grid.
    @@ -17,12 +15,16 @@ export class Example9 {
  • Doing a "right+click" over any column header will also provide a way to show/hide a column (via the Column Picker Plugin)
  • `; + + aureliaGrid: AureliaGridInstance; columnDefinitions: Column[]; gridOptions: GridOption; dataset = []; + dataView: any; + gridObj: any; visibleColumns; - constructor(private filterService: FilterService, private sortService: SortService) { + constructor() { // define the grid options & columns and then create the grid itself this.defineGrid(); } @@ -32,13 +34,19 @@ export class Example9 { this.getData(); } + aureliaGridReady(aureliaGrid: AureliaGridInstance) { + this.aureliaGrid = aureliaGrid; + this.gridObj = aureliaGrid && aureliaGrid.slickGrid; + this.dataView = aureliaGrid && aureliaGrid.dataView; + } + defineGrid() { this.columnDefinitions = [ { id: 'title', name: 'Title', field: 'title', filterable: true, type: FieldType.string }, { id: 'duration', name: 'Duration', field: 'duration', sortable: true, filterable: true, type: FieldType.string }, - { id: '%', name: '% Complete', field: 'percentComplete', sortable: true, filterable: true, type: FieldType.number }, - { id: 'start', name: 'Start', field: 'start', filterable: true, type: FieldType.string }, - { id: 'finish', name: 'Finish', field: 'finish', filterable: true, type: FieldType.string }, + { id: '%', name: '% Complete', field: 'percentComplete', sortable: true, filterable: true, type: FieldType.number, formatter: Formatters.percentCompleteBar }, + { id: 'start', name: 'Start', field: 'start', filterable: true, type: FieldType.dateUs, filter: { model: Filters.compoundDate } }, + { id: 'finish', name: 'Finish', field: 'finish', filterable: true, type: FieldType.dateUs, filter: { model: Filters.compoundDate } }, { id: 'effort-driven', name: 'Effort Driven', field: 'effortDriven', maxWidth: 80, formatter: Formatters.checkmark, type: FieldType.boolean, @@ -47,7 +55,7 @@ export class Example9 { filterable: true, filter: { collection: [{ value: '', label: '' }, { value: true, label: 'true' }, { value: false, label: 'false' }], - type: FilterType.singleSelect, + model: Filters.singleSelect, filterOptions: { // you can add "multiple-select" plugin options like styling the first row offsetLeft: 14, @@ -130,11 +138,11 @@ export class Example9 { } else if (args.command === 'toggle-toppanel') { this.gridObj.setTopPanelVisibility(!this.gridObj.getOptions().showTopPanel); } else if (args.command === 'clear-filter') { - this.filterService.clearFilters(); - this.dataviewObj.refresh(); + this.aureliaGrid.filterService.clearFilters(); + this.dataView.refresh(); } else if (args.command === 'clear-sorting') { - this.sortService.clearSorting(); - this.dataviewObj.refresh(); + this.aureliaGrid.sortService.clearSorting(); + this.dataView.refresh(); } else { alert('Command: ' + args.command); } @@ -162,12 +170,4 @@ export class Example9 { } this.dataset = mockDataset; } - - gridObjChanged(grid) { - this.gridObj = grid; - } - - dataviewChanged(dataview) { - this.dataviewObj = dataview; - } } diff --git a/aurelia-slickgrid/src/examples/slickgrid/index.ts b/aurelia-slickgrid/src/examples/slickgrid/index.ts index 628871d25..c542e5d43 100644 --- a/aurelia-slickgrid/src/examples/slickgrid/index.ts +++ b/aurelia-slickgrid/src/examples/slickgrid/index.ts @@ -7,20 +7,21 @@ export class Index { configureRouter(config: RouterConfiguration, router: Router) { const mapping: any = [ - { route: ['', 'example1'], moduleId: PLATFORM.moduleName('./example1'), name: 'example1', nav: true, title: '1- Basic Grid' }, + { route: ['', 'example1'], moduleId: PLATFORM.moduleName('./example1'), name: 'example1', nav: true, title: '1- Basic Grid / 2 Grids' }, { route: 'example2', moduleId: PLATFORM.moduleName('./example2'), name: 'example2', nav: true, title: '2- Formatters' }, - { route: 'example3', moduleId: PLATFORM.moduleName('./example3'), name: 'example3', nav: true, title: '3- Editors' }, + { route: 'example3', moduleId: PLATFORM.moduleName('./example3'), name: 'example3', nav: true, title: '3- Editors / Delete' }, { route: 'example4', moduleId: PLATFORM.moduleName('./example4'), name: 'example4', nav: true, title: '4- Client Side Sort/Filter' }, { route: 'example5', moduleId: PLATFORM.moduleName('./example5'), name: 'example5', nav: true, title: '5- Backend OData Service' }, { route: 'example6', moduleId: PLATFORM.moduleName('./example6'), name: 'example6', nav: true, title: '6- Backend GraphQL Service' }, { route: 'example7', moduleId: PLATFORM.moduleName('./example7'), name: 'example7', nav: true, title: '7- Header Button Plugin' }, { route: 'example8', moduleId: PLATFORM.moduleName('./example8'), name: 'example8', nav: true, title: '8- Header Menu Plugin' }, { route: 'example9', moduleId: PLATFORM.moduleName('./example9'), name: 'example9', nav: true, title: '9- Grid Menu Control' }, - { route: 'example10', moduleId: PLATFORM.moduleName('./example10'), name: 'example10', nav: true, title: '10- Row Selection' }, + { route: 'example10', moduleId: PLATFORM.moduleName('./example10'), name: 'example10', nav: true, title: '10- Row Selection / 2 Grids' }, { route: 'example11', moduleId: PLATFORM.moduleName('./example11'), name: 'example11', nav: true, title: '11- Add/Update Grid Item' }, { route: 'example12', moduleId: PLATFORM.moduleName('./example12'), name: 'example12', nav: true, title: '12- Localization (i18n)' }, { route: 'example13', moduleId: PLATFORM.moduleName('./example13'), name: 'example13', nav: true, title: '13- Grouping & Aggregators' }, { route: 'example14', moduleId: PLATFORM.moduleName('./example14'), name: 'example14', nav: true, title: '14- Column Span' }, + { route: 'example15', moduleId: PLATFORM.moduleName('./example15'), name: 'example15', nav: true, title: '15- Grid State & Local Storage' }, ]; config.map(mapping); diff --git a/client-cli/src/examples/slickgrid/custom-inputFilter.js b/client-cli/src/examples/slickgrid/custom-inputFilter.js index 21c9656f8..c42489f91 100644 --- a/client-cli/src/examples/slickgrid/custom-inputFilter.js +++ b/client-cli/src/examples/slickgrid/custom-inputFilter.js @@ -29,12 +29,10 @@ export class CustomInputFilter { /** * Clear the filter value */ - clear(triggerFilterKeyup = true) { + clear() { if (this.$filterElm) { this.$filterElm.val(''); - if (triggerFilterKeyup) { - this.$filterElm.trigger('keyup'); - } + this.$filterElm.trigger('keyup'); } } diff --git a/client-cli/src/examples/slickgrid/example12.js b/client-cli/src/examples/slickgrid/example12.js index f8484ff7f..a2c9d115c 100644 --- a/client-cli/src/examples/slickgrid/example12.js +++ b/client-cli/src/examples/slickgrid/example12.js @@ -15,7 +15,7 @@ export class Example12 {
  • For the cell values, you need to use a Formatter, there's 2 ways of doing it
    • formatter: myCustomTranslateFormatter <= "Title" column uses it
    • -
    • formatter: Formatters.translate, params: { i18n: this.i18n } <= "Completed" column uses it
    • +
    • formatter: Formatters.translate, i18n: this.i18n <= "Completed" column uses it
  • For date localization, you need to create your own custom formatter.
  • @@ -101,6 +101,7 @@ export class Example12 { enableAutoResize: true, enableFiltering: true, enableTranslate: true, + i18n: this.i18n, exportOptions: { // set at the grid option level, meaning all column will evaluate the Formatter (when it has a Formatter defined) exportWithFormatter: true @@ -109,9 +110,6 @@ export class Example12 { showExportCsvCommand: true, // true by default, so it's optional showExportTextDelimitedCommand: true // false by default, so if you want it, you will need to enable it }, - params: { - i18n: this.i18n - } }; } diff --git a/client-cli/src/examples/slickgrid/example13.html b/client-cli/src/examples/slickgrid/example13.html index 6321c8be2..17eaa7ed0 100644 --- a/client-cli/src/examples/slickgrid/example13.html +++ b/client-cli/src/examples/slickgrid/example13.html @@ -46,6 +46,6 @@

    ${title}

    + asg-on-dataview-created.delegate="onDataviewCreated($event.detail)"> diff --git a/client-cli/src/examples/slickgrid/example13.js b/client-cli/src/examples/slickgrid/example13.js index 54a7c99f7..5feefa41f 100644 --- a/client-cli/src/examples/slickgrid/example13.js +++ b/client-cli/src/examples/slickgrid/example13.js @@ -10,7 +10,7 @@ export class Example13 {
  • Wiki docs
  • Fully dynamic and interactive multi-level grouping with filtering and aggregates over 50'000 items
  • Each grouping level can have its own aggregates (over child rows, child groups, or all descendant rows)..
  • -
  • Use "Aggregators" and "GroupTotalFormatters" directly from Angular-Slickgrid
  • +
  • Use "Aggregators" and "GroupTotalFormatters" directly from Aurelia-Slickgrid
  • `; diff --git a/client-cli/src/examples/slickgrid/example3.js b/client-cli/src/examples/slickgrid/example3.js index 7b5ac901b..6a71159a6 100644 --- a/client-cli/src/examples/slickgrid/example3.js +++ b/client-cli/src/examples/slickgrid/example3.js @@ -84,13 +84,13 @@ export class Example3 { formatter: Formatters.deleteIcon, minWidth: 30, maxWidth: 30 - // use onCellClick OR grid.onClick.subscribe which you can see down below - /* - onCellClick: (args: OnEventArgs) => { - console.log(args); - this.alertWarning = `Deleting: ${args.dataContext.title}`; - } - */ + // use onCellClick OR grid.onClick.subscribe which you can see down below + /* + onCellClick: (args: OnEventArgs) => { + console.log(args); + this.alertWarning = `Deleting: ${args.dataContext.title}`; + } + */ }, { id: 'title', name: 'Title', @@ -136,10 +136,7 @@ export class Example3 { sortable: true, minWidth: 100, type: FieldType.date, - editor: Editors.date, - params: { - i18n: this.i18n - } + editor: Editors.date }, { id: 'finish', name: 'Finish', @@ -191,7 +188,8 @@ export class Example3 { editCommandHandler: (item, column, editCommand) => { this._commandQueue.push(editCommand); editCommand.execute(); - } + }, + i18n: this.i18n }; } diff --git a/client-cli/src/examples/slickgrid/example5.js b/client-cli/src/examples/slickgrid/example5.js index 10e7241aa..e739cbc05 100644 --- a/client-cli/src/examples/slickgrid/example5.js +++ b/client-cli/src/examples/slickgrid/example5.js @@ -1,6 +1,6 @@ import { inject } from 'aurelia-framework'; import { HttpClient } from 'aurelia-http-client'; -import { FieldType, FilterType, FormElementType, GridOdataService } from 'aurelia-slickgrid'; +import { FieldType, FilterType, GridOdataService } from 'aurelia-slickgrid'; const defaultPageSize = 20; const sampleDataRoot = 'src/examples/slickgrid/sample-data'; @@ -52,7 +52,7 @@ export class Example5 { id: 'gender', name: 'Gender', field: 'gender', filterable: true, sortable: true, minWidth: 100, filter: { collection: [{ value: '', label: '' }, { value: 'male', label: 'male' }, { value: 'female', label: 'female' }], - type: FormElementType.singleSelect + type: FilterType.singleSelect } }, { id: 'company', name: 'Company', field: 'company', minWidth: 100 } diff --git a/client-cli/src/examples/slickgrid/example7.js b/client-cli/src/examples/slickgrid/example7.js index 57a64617b..05afe7653 100644 --- a/client-cli/src/examples/slickgrid/example7.js +++ b/client-cli/src/examples/slickgrid/example7.js @@ -1,5 +1,5 @@ import { autoinject } from 'aurelia-framework'; -import { Column, GridOption, Formatters, FormElementType } from 'aurelia-slickgrid'; +import { Column, GridOption, Formatters } from 'aurelia-slickgrid'; let columnsWithHighlightingById = {}; diff --git a/doc/github-demo/src/examples/slickgrid/custom-inputFilter.ts b/doc/github-demo/src/examples/slickgrid/custom-inputFilter.ts index c92a74228..04b99ea54 100644 --- a/doc/github-demo/src/examples/slickgrid/custom-inputFilter.ts +++ b/doc/github-demo/src/examples/slickgrid/custom-inputFilter.ts @@ -30,12 +30,10 @@ export class CustomInputFilter implements Filter { /** * Clear the filter value */ - clear(triggerFilterKeyup = true) { + clear() { if (this.$filterElm) { this.$filterElm.val(''); - if (triggerFilterKeyup) { - this.$filterElm.trigger('keyup'); - } + this.$filterElm.trigger('keyup'); } } diff --git a/doc/github-demo/src/examples/slickgrid/example12.ts b/doc/github-demo/src/examples/slickgrid/example12.ts index 63fb5061a..16e28d68a 100644 --- a/doc/github-demo/src/examples/slickgrid/example12.ts +++ b/doc/github-demo/src/examples/slickgrid/example12.ts @@ -102,6 +102,7 @@ export class Example12 { enableExcelCopyBuffer: true, enableFiltering: true, enableTranslate: true, + i18n: this.i18n, exportOptions: { // set at the grid option level, meaning all column will evaluate the Formatter (when it has a Formatter defined) exportWithFormatter: true, @@ -111,9 +112,6 @@ export class Example12 { showExportCsvCommand: true, // true by default, so it's optional showExportTextDelimitedCommand: true // false by default, so if you want it, you will need to enable it }, - params: { - i18n: this.i18n - } }; } diff --git a/doc/github-demo/src/examples/slickgrid/example13.html b/doc/github-demo/src/examples/slickgrid/example13.html index 62000ec6b..9ffaf8877 100644 --- a/doc/github-demo/src/examples/slickgrid/example13.html +++ b/doc/github-demo/src/examples/slickgrid/example13.html @@ -45,7 +45,12 @@

    ${title}

    - + diff --git a/doc/github-demo/src/examples/slickgrid/example13.ts b/doc/github-demo/src/examples/slickgrid/example13.ts index ecc37ca91..e884048cf 100644 --- a/doc/github-demo/src/examples/slickgrid/example13.ts +++ b/doc/github-demo/src/examples/slickgrid/example13.ts @@ -10,7 +10,7 @@ export class Example13 {
  • Wiki docs
  • Fully dynamic and interactive multi-level grouping with filtering and aggregates over 50'000 items
  • Each grouping level can have its own aggregates (over child rows, child groups, or all descendant rows)..
  • -
  • Use "Aggregators" and "GroupTotalFormatters" directly from Angular-Slickgrid
  • +
  • Use "Aggregators" and "GroupTotalFormatters" directly from Aurelia-Slickgrid
  • `; diff --git a/doc/github-demo/src/examples/slickgrid/example3.ts b/doc/github-demo/src/examples/slickgrid/example3.ts index e7f56bfff..b92af9943 100644 --- a/doc/github-demo/src/examples/slickgrid/example3.ts +++ b/doc/github-demo/src/examples/slickgrid/example3.ts @@ -88,13 +88,13 @@ export class Example3 { formatter: Formatters.deleteIcon, minWidth: 30, maxWidth: 30, - // use onCellClick OR grid.onClick.subscribe which you can see down below - /* - onCellClick: (args: OnEventArgs) => { - console.log(args); - this.alertWarning = `Deleting: ${args.dataContext.title}`; - } - */ + // use onCellClick OR grid.onClick.subscribe which you can see down below + /* + onCellClick: (args: OnEventArgs) => { + console.log(args); + this.alertWarning = `Deleting: ${args.dataContext.title}`; + } + */ }, { id: 'title', name: 'Title', @@ -194,9 +194,7 @@ export class Example3 { this._commandQueue.push(editCommand); editCommand.execute(); }, - params: { - i18n: this.i18n - } + i18n: this.i18n, }; } diff --git a/doc/github-demo/src/examples/slickgrid/example5.ts b/doc/github-demo/src/examples/slickgrid/example5.ts index 10f3bf009..17520b596 100644 --- a/doc/github-demo/src/examples/slickgrid/example5.ts +++ b/doc/github-demo/src/examples/slickgrid/example5.ts @@ -1,6 +1,6 @@ import { autoinject } from 'aurelia-framework'; import { HttpClient } from 'aurelia-http-client'; -import { CaseType, Column, GridOption, FieldType, FilterType, Formatters, FormElementType, GridOdataService, OperatorType } from 'aurelia-slickgrid'; +import { CaseType, Column, GridOption, FieldType, FilterType, Formatters, GridOdataService, OperatorType } from 'aurelia-slickgrid'; const defaultPageSize = 20; const sampleDataRoot = 'assets/data'; diff --git a/doc/github-demo/src/examples/slickgrid/example7.ts b/doc/github-demo/src/examples/slickgrid/example7.ts index d11244885..593e376ec 100644 --- a/doc/github-demo/src/examples/slickgrid/example7.ts +++ b/doc/github-demo/src/examples/slickgrid/example7.ts @@ -1,5 +1,5 @@ import { autoinject } from 'aurelia-framework'; -import { Column, GridOption, FieldType, Formatters, FormElementType } from 'aurelia-slickgrid'; +import { Column, GridOption, FieldType, Formatters } from 'aurelia-slickgrid'; let columnsWithHighlightingById = {}; diff --git a/package.json b/package.json index 71808f0cc..f883cf7ab 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,7 @@ "jquery-ui-dist": "^1.12.1", "moment": ">=2.18.1", "moment-mini": ">=2.18.1", - "slickgrid": "~2.3.18", + "slickgrid": "~2.3.19", "text-encoding-utf-8": "^1.0.2" }, "devDependencies": {