diff --git a/package.json b/package.json index a7b4c3d0f..d68be5402 100644 --- a/package.json +++ b/package.json @@ -109,7 +109,7 @@ "lodash.isequal": "^4.5.0", "moment-mini": "^2.24.0", "rxjs": "^6.3.3", - "slickgrid": "^2.4.32", + "slickgrid": "^2.4.33", "text-encoding-utf-8": "^1.0.2" }, "peerDependencies": { diff --git a/src/app/modules/angular-slickgrid/models/slickDataView.interface.ts b/src/app/modules/angular-slickgrid/models/slickDataView.interface.ts index 509353745..ba204cee2 100644 --- a/src/app/modules/angular-slickgrid/models/slickDataView.interface.ts +++ b/src/app/modules/angular-slickgrid/models/slickDataView.interface.ts @@ -7,11 +7,17 @@ export interface SlickDataView { // -- // Available Methods - /** Add an item to the dataset */ + /** Add an item to the DataView */ addItem(item: any): void; - /** Begin Data Update Transaction */ - beginUpdate(): void; + /** Add multiple items to the DataView */ + addItems(items: any[]): void; + + /** + * Begin Data Update Transaction + * @param {Boolean} isBulkUpdate - are we doing a bulk update transactions? Defaults to false + */ + beginUpdate(isBulkUpdate?: boolean): void; /** Collapse all Groups, optionally pass a level number to only collapse that level */ collapseAllGroups(level?: number): void; @@ -23,9 +29,12 @@ export interface SlickDataView { */ collapseGroup(...args: any): void; - /** Delete an item from the dataset */ + /** Delete an item from the DataView identified by its id */ deleteItem(id: string | number): void; + /** Delete multiple items from the DataView identified by their given ids */ + deleteItems(ids: Array): void; + /** End Data Update Transaction */ endUpdate(): void; @@ -68,39 +77,45 @@ export interface SlickDataView { /** Get the DataView Id property name to use (defaults to "Id" but could be customized to something else when instantiating the DataView) */ getIdPropertyName(): string; - /** Get all Dataset Items */ + /** Get all DataView Items */ getItems: () => T[]; - /** Get dataset item at specific index */ + /** Get DataView item at specific index */ getItem: (index: number) => T; - /** Get an item in the dataset by its Id */ + /** Get an item in the DataView by its Id */ getItemById: (id: string | number) => T; - /** Get an item in the dataset by its row index */ + /** Get an item in the DataView by its row index */ getItemByIdx(idx: number): number; - /** Get row index in the dataset by its Id */ + /** Get row index in the DataView by its Id */ getIdxById(id: string | number): number | undefined; + /** Get item count, full dataset length that is defined in the DataView */ + getItemCount(): number; + /** Get item metadata at specific index */ getItemMetadata(index: number): any; - /** Get dataset length */ + /** Get DataView length */ getLength(): number; /** Get Paging Options */ getPagingInfo(): PagingInfo; - /** Get row number of an item in the dataset */ + /** Get row number of an item in the DataView */ getRowByItem(item: any): number; - /** Get row number of an item in the dataset by its Id */ + /** Get row number of an item in the DataView by its Id */ getRowById(id: string | number): number | undefined; - /** Insert an item to the dataset before a specific index */ + /** Insert an item to the DataView before a specific index */ insertItem(insertBefore: number, item: any): void; + /** Insert multiple items to the DataView before a specific index */ + insertItems(insertBefore: number, items: any[]): void; + /** From the items array provided, return the mapped rows */ mapItemsToRows(items: any[]): number[]; @@ -172,9 +187,12 @@ export interface SlickDataView { /** Update a specific Index */ updateIdxById(startingIndex: number): void; - /** Update an item in the dataset by its Id */ + /** Update an item in the DataView identified by its Id */ updateItem(id: string | number, item: T): void; + /** Update multiple items in the DataView identified by their Ids */ + updateItems(id: Array, items: T[]): void; + // --------------------------- // Available DataView Events // --------------------------- diff --git a/src/app/modules/angular-slickgrid/services/__tests__/grid.service.spec.ts b/src/app/modules/angular-slickgrid/services/__tests__/grid.service.spec.ts index 0b1441a4b..532728322 100644 --- a/src/app/modules/angular-slickgrid/services/__tests__/grid.service.spec.ts +++ b/src/app/modules/angular-slickgrid/services/__tests__/grid.service.spec.ts @@ -33,15 +33,19 @@ const sortServiceStub = { const dataviewStub = { addItem: jest.fn(), + addItems: jest.fn(), beginUpdate: jest.fn(), endUpdate: jest.fn(), deleteItem: jest.fn(), + deleteItems: jest.fn(), getIdxById: jest.fn(), getItem: jest.fn(), getRowById: jest.fn(), insertItem: jest.fn(), + insertItems: jest.fn(), reSort: jest.fn(), updateItem: jest.fn(), + updateItems: jest.fn(), }; const gridStub = { @@ -398,29 +402,6 @@ describe('Grid Service', () => { expect(rxSpy).toHaveBeenCalledWith(mockItem); }); - it('should expect the service to call the "updateItemById" multiple times when calling "updateItems" with an array of items', () => { - const mockItems = [{ id: 0, user: { firstName: 'John', lastName: 'Doe' } }, { id: 5, user: { firstName: 'Jane', lastName: 'Doe' } }]; - const getRowIdSpy = jest.spyOn(dataviewStub, 'getRowById').mockReturnValue(0).mockReturnValueOnce(0).mockReturnValueOnce(1); - const getRowIndexSpy = jest.spyOn(dataviewStub, 'getIdxById').mockReturnValue(0).mockReturnValueOnce(0).mockReturnValueOnce(1); - const serviceUpdateSpy = jest.spyOn(service, 'updateItemById'); - const serviceHighlightSpy = jest.spyOn(service, 'highlightRow'); - const beginUpdateSpy = jest.spyOn(dataviewStub, 'beginUpdate'); - const endUpdateSpy = jest.spyOn(dataviewStub, 'endUpdate'); - const rxSpy = jest.spyOn(service.onItemUpdated, 'next'); - - service.updateItems(mockItems); - - expect(beginUpdateSpy).toHaveBeenCalled(); - expect(endUpdateSpy).toHaveBeenCalled(); - expect(getRowIdSpy).toHaveBeenCalledTimes(2); - expect(getRowIndexSpy).toHaveBeenCalledTimes(2); - expect(serviceUpdateSpy).toHaveBeenCalledTimes(2); - expect(serviceUpdateSpy).toHaveBeenNthCalledWith(1, mockItems[0].id, mockItems[0], { highlightRow: false, selectRow: false, scrollRowIntoView: false, triggerEvent: false }); - expect(serviceUpdateSpy).toHaveBeenNthCalledWith(2, mockItems[1].id, mockItems[1], { highlightRow: false, selectRow: false, scrollRowIntoView: false, triggerEvent: false }); - expect(serviceHighlightSpy).toHaveBeenCalledWith([0, 1]); - expect(rxSpy).toHaveBeenCalledWith(mockItems); - }); - it('should expect the service to call the "updateItem" when calling "updateItems" with a single item which is not an array', () => { const mockItem = { id: 0, user: { firstName: 'John', lastName: 'Doe' } }; const getRowIdSpy = jest.spyOn(dataviewStub, 'getRowById'); @@ -446,7 +427,7 @@ describe('Grid Service', () => { it('should expect the row to be selected when calling "updateItems" with an item when setting the "selecRow" flag and the grid option "enableRowSelection" is set', () => { const mockItem = { id: 0, user: { firstName: 'John', lastName: 'Doe' } }; jest.spyOn(gridStub, 'getOptions').mockReturnValue({ enableAutoResize: true, enableRowSelection: true } as GridOption); - const updateSpy = jest.spyOn(service, 'updateItem'); + const updateSpy = jest.spyOn(dataviewStub, 'updateItems'); const selectSpy = jest.spyOn(service, 'setSelectedRows'); const beginUpdateSpy = jest.spyOn(dataviewStub, 'beginUpdate'); const endUpdateSpy = jest.spyOn(dataviewStub, 'endUpdate'); @@ -457,7 +438,7 @@ describe('Grid Service', () => { expect(beginUpdateSpy).toHaveBeenCalled(); expect(endUpdateSpy).toHaveBeenCalled(); expect(updateSpy).toHaveBeenCalledTimes(1); - expect(updateSpy).toHaveBeenCalledWith(mockItem, { highlightRow: false, selectRow: false, scrollRowIntoView: false, triggerEvent: false }); + expect(updateSpy).toHaveBeenCalledWith([0], [mockItem]); expect(selectSpy).toHaveBeenCalledWith([0]); expect(rxSpy).toHaveBeenCalledWith([mockItem]); }); @@ -528,6 +509,10 @@ describe('Grid Service', () => { }); describe('addItem methods', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + it('should throw an error when 1st argument for the item object is missing', () => { jest.spyOn(gridStub, 'getOptions').mockReturnValue(undefined); expect(() => service.addItem(null)).toThrowError('We could not find SlickGrid Grid, DataView objects'); @@ -585,10 +570,10 @@ describe('Grid Service', () => { expect(rxSpy).toHaveBeenCalledWith(mockItem); }); - it('should expect the service to call the "addItem" multiple times when calling "addItems" with an array of items', () => { + it('should expect to call the DataView "insertItems" once when calling the service "addItems" with an array of items and no position is provided (defaults to insert "top")', () => { const mockItems = [{ id: 0, user: { firstName: 'John', lastName: 'Doe' } }, { id: 5, user: { firstName: 'Jane', lastName: 'Doe' } }]; - jest.spyOn(dataviewStub, 'getRowById').mockReturnValue(0).mockReturnValueOnce(0).mockReturnValueOnce(1).mockReturnValueOnce(1); - const serviceAddSpy = jest.spyOn(service, 'addItem'); + jest.spyOn(dataviewStub, 'getRowById').mockReturnValueOnce(0).mockReturnValueOnce(1); + const insertItemsSpy = jest.spyOn(dataviewStub, 'insertItems'); const serviceHighlightSpy = jest.spyOn(service, 'highlightRow'); const beginUpdateSpy = jest.spyOn(dataviewStub, 'beginUpdate'); const endUpdateSpy = jest.spyOn(dataviewStub, 'endUpdate'); @@ -598,22 +583,22 @@ describe('Grid Service', () => { expect(beginUpdateSpy).toHaveBeenCalled(); expect(endUpdateSpy).toHaveBeenCalled(); - expect(serviceAddSpy).toHaveBeenCalledTimes(2); - expect(serviceAddSpy).toHaveBeenNthCalledWith(1, mockItems[0], { highlightRow: false, position: 'top', resortGrid: false, selectRow: false, triggerEvent: false }); - expect(serviceAddSpy).toHaveBeenNthCalledWith(2, mockItems[1], { highlightRow: false, position: 'top', resortGrid: false, selectRow: false, triggerEvent: false }); + expect(insertItemsSpy).toHaveBeenCalledTimes(1); + expect(insertItemsSpy).toHaveBeenCalledWith(0, [mockItems[0], mockItems[1]]); expect(serviceHighlightSpy).toHaveBeenCalledTimes(1); expect(serviceHighlightSpy).toHaveBeenCalledWith([0, 1]); expect(rxSpy).toHaveBeenCalledWith(mockItems); + jest.clearAllMocks(); }); - it('should expect the service to call the "addItem" multiple times when calling "addItems" with an array of items and the option "position" set to "bottom"', () => { + it('should expect to call the DataView "addItems" once when calling the service "addItems" with an array of items and the option "position" set to "bottom"', () => { const expectationNewRowPosition1 = 1000; const expectationNewRowPosition2 = 1001; const mockItems = [{ id: 0, user: { firstName: 'John', lastName: 'Doe' } }, { id: 5, user: { firstName: 'Jane', lastName: 'Doe' } }]; jest.spyOn(dataviewStub, 'getRowById') - .mockReturnValueOnce(expectationNewRowPosition1).mockReturnValueOnce(expectationNewRowPosition1) - .mockReturnValueOnce(expectationNewRowPosition2).mockReturnValueOnce(expectationNewRowPosition2); - const serviceAddSpy = jest.spyOn(service, 'addItem'); + .mockReturnValueOnce(expectationNewRowPosition1) + .mockReturnValueOnce(expectationNewRowPosition2); + const addItemsSpy = jest.spyOn(dataviewStub, 'addItems'); const serviceHighlightSpy = jest.spyOn(service, 'highlightRow'); const beginUpdateSpy = jest.spyOn(dataviewStub, 'beginUpdate'); const endUpdateSpy = jest.spyOn(dataviewStub, 'endUpdate'); @@ -623,9 +608,8 @@ describe('Grid Service', () => { expect(beginUpdateSpy).toHaveBeenCalled(); expect(endUpdateSpy).toHaveBeenCalled(); - expect(serviceAddSpy).toHaveBeenCalledTimes(2); - expect(serviceAddSpy).toHaveBeenNthCalledWith(1, mockItems[0], { highlightRow: false, position: 'bottom', resortGrid: false, selectRow: false, triggerEvent: false }); - expect(serviceAddSpy).toHaveBeenNthCalledWith(2, mockItems[1], { highlightRow: false, position: 'bottom', resortGrid: false, selectRow: false, triggerEvent: false }); + expect(addItemsSpy).toHaveBeenCalledTimes(1); + expect(addItemsSpy).toHaveBeenCalledWith([mockItems[0], mockItems[1]]); expect(serviceHighlightSpy).toHaveBeenCalledTimes(1); expect(serviceHighlightSpy).toHaveBeenCalledWith([expectationNewRowPosition1, expectationNewRowPosition2]); expect(rxSpy).toHaveBeenCalledWith(mockItems); @@ -667,7 +651,7 @@ describe('Grid Service', () => { it('should add a single item by calling "addItems" method and expect to call a grid resort & highlight but without triggering an event', () => { const mockItems = [{ id: 0, user: { firstName: 'John', lastName: 'Doe' } }, { id: 5, user: { firstName: 'Jane', lastName: 'Doe' } }]; - const serviceAddSpy = jest.spyOn(service, 'addItem'); + const insertItemsSpy = jest.spyOn(dataviewStub, 'insertItems'); const serviceHighlightSpy = jest.spyOn(service, 'highlightRow'); const resortSpy = jest.spyOn(dataviewStub, 'reSort'); const getRowByIdSpy = jest.spyOn(dataviewStub, 'getRowById'); @@ -679,10 +663,9 @@ describe('Grid Service', () => { expect(beginUpdateSpy).toHaveBeenCalled(); expect(endUpdateSpy).toHaveBeenCalled(); - expect(serviceAddSpy).toHaveBeenCalled(); + expect(insertItemsSpy).toHaveBeenCalledTimes(1); expect(resortSpy).toHaveBeenCalled(); - expect(serviceAddSpy).toHaveBeenNthCalledWith(1, mockItems[0], { highlightRow: false, position: 'top', resortGrid: false, selectRow: false, triggerEvent: false }); - expect(serviceAddSpy).toHaveBeenNthCalledWith(2, mockItems[1], { highlightRow: false, position: 'top', resortGrid: false, selectRow: false, triggerEvent: false }); + expect(insertItemsSpy).toHaveBeenCalledWith(0, [mockItems[0], mockItems[1]]); expect(serviceHighlightSpy).toHaveBeenCalledTimes(1); expect(getRowByIdSpy).toHaveBeenCalledTimes(2); expect(rxSpy).not.toHaveBeenCalled(); @@ -692,7 +675,7 @@ describe('Grid Service', () => { const mockItem = { id: 0, user: { firstName: 'John', lastName: 'Doe' } }; jest.spyOn(dataviewStub, 'getRowById').mockReturnValue(0); jest.spyOn(gridStub, 'getOptions').mockReturnValue({ enableAutoResize: true, enableRowSelection: true } as GridOption); - const addSpy = jest.spyOn(dataviewStub, 'insertItem'); + const insertSpy = jest.spyOn(dataviewStub, 'insertItems'); const selectSpy = jest.spyOn(service, 'setSelectedRows'); const beginUpdateSpy = jest.spyOn(dataviewStub, 'beginUpdate'); const endUpdateSpy = jest.spyOn(dataviewStub, 'endUpdate'); @@ -702,8 +685,8 @@ describe('Grid Service', () => { expect(beginUpdateSpy).toHaveBeenCalled(); expect(endUpdateSpy).toHaveBeenCalled(); - expect(addSpy).toHaveBeenCalledTimes(1); - expect(addSpy).toHaveBeenCalledWith(0, mockItem); + expect(insertSpy).toHaveBeenCalledTimes(1); + expect(insertSpy).toHaveBeenCalledWith(0, [mockItem]); expect(selectSpy).toHaveBeenCalledWith([0]); expect(rxSpy).toHaveBeenCalledWith([mockItem]); }); @@ -817,9 +800,9 @@ describe('Grid Service', () => { expect(selectionSpy).toHaveBeenCalledWith([]); }); - it('should expect the service to call the "deleteItem" multiple times when calling "deleteItems" with an array of items', () => { + it('should expect the service to call the DataView "deleteItems" once with array of item Ids when calling "deleteItems" with an array of items', () => { const mockItems = [{ id: 0, user: { firstName: 'John', lastName: 'Doe' } }, { id: 5, user: { firstName: 'Jane', lastName: 'Doe' } }]; - const serviceDeleteSpy = jest.spyOn(service, 'deleteItem'); + const deleteItemsSpy = jest.spyOn(dataviewStub, 'deleteItems'); const beginUpdateSpy = jest.spyOn(dataviewStub, 'beginUpdate'); const endUpdateSpy = jest.spyOn(dataviewStub, 'endUpdate'); const rxSpy = jest.spyOn(service.onItemDeleted, 'next'); @@ -829,9 +812,8 @@ describe('Grid Service', () => { expect(output).toEqual([0, 5]); expect(beginUpdateSpy).toHaveBeenCalled(); expect(endUpdateSpy).toHaveBeenCalled(); - expect(serviceDeleteSpy).toHaveBeenCalledTimes(2); - expect(serviceDeleteSpy).toHaveBeenNthCalledWith(1, mockItems[0], { triggerEvent: false }); - expect(serviceDeleteSpy).toHaveBeenNthCalledWith(2, mockItems[1], { triggerEvent: false }); + expect(deleteItemsSpy).toHaveBeenCalledTimes(1); + expect(deleteItemsSpy).toHaveBeenCalledWith([0, 5]); expect(rxSpy).toHaveBeenCalledWith(mockItems); }); @@ -869,23 +851,20 @@ describe('Grid Service', () => { expect(rxSpy).not.toHaveBeenCalled(); }); - it('should delete a single item by calling "deleteItems" method and expect to trigger a single an event', () => { + it('should delete multiple items by calling "deleteItems" method and expect to trigger a single an event', () => { const mockItems = [{ id: 0, user: { firstName: 'John', lastName: 'Doe' } }, { id: 5, user: { firstName: 'Jane', lastName: 'Doe' } }]; - const serviceDeleteSpy = jest.spyOn(service, 'deleteItem'); - const dataviewDeleteSpy = jest.spyOn(dataviewStub, 'deleteItem'); + const deleteItemsSpy = jest.spyOn(dataviewStub, 'deleteItems'); const beginUpdateSpy = jest.spyOn(dataviewStub, 'beginUpdate'); const endUpdateSpy = jest.spyOn(dataviewStub, 'endUpdate'); const rxSpy = jest.spyOn(service.onItemDeleted, 'next'); const output = service.deleteItems(mockItems, { triggerEvent: true }); - expect(output).toEqual([0, 5]); expect(beginUpdateSpy).toHaveBeenCalled(); expect(endUpdateSpy).toHaveBeenCalled(); - expect(serviceDeleteSpy).toHaveBeenCalled(); - expect(serviceDeleteSpy).toHaveBeenNthCalledWith(1, mockItems[0], { triggerEvent: false }); - expect(serviceDeleteSpy).toHaveBeenNthCalledWith(2, mockItems[1], { triggerEvent: false }); - expect(dataviewDeleteSpy).toHaveBeenCalledTimes(2); + expect(output).toEqual([0, 5]); + expect(deleteItemsSpy).toHaveBeenCalledTimes(1); + expect(deleteItemsSpy).toHaveBeenCalledWith([0, 5]); expect(rxSpy).toHaveBeenCalledTimes(1); }); @@ -1087,16 +1066,16 @@ describe('Grid Service', () => { it(`should return an Item Metadata object with filled "cssClasses" property including a row number in the string when the column definition has a "rowClass" property and when callback provided already returns a "cssClasses" property`, () => { - const rowNumber = 1; - const dataviewSpy = jest.spyOn(dataviewStub, 'getItem').mockReturnValue(columnDefinitions[rowNumber]); + const rowNumber = 1; + const dataviewSpy = jest.spyOn(dataviewStub, 'getItem').mockReturnValue(columnDefinitions[rowNumber]); - const callback = service.getItemRowMetadataToHighlight(mockItemMetadataFn); - const output = callback(rowNumber); // execute callback with a row number + const callback = service.getItemRowMetadataToHighlight(mockItemMetadataFn); + const output = callback(rowNumber); // execute callback with a row number - expect(dataviewSpy).toHaveBeenCalled(); - expect(typeof callback === 'function').toBe(true); - expect(output).toEqual({ cssClasses: ' red row1' }); - }); + expect(dataviewSpy).toHaveBeenCalled(); + expect(typeof callback === 'function').toBe(true); + expect(output).toEqual({ cssClasses: ' red row1' }); + }); }); describe('highlightRowByMetadata method', () => { diff --git a/src/app/modules/angular-slickgrid/services/grid.service.ts b/src/app/modules/angular-slickgrid/services/grid.service.ts index 361f696ac..f7fcd5903 100644 --- a/src/app/modules/angular-slickgrid/services/grid.service.ts +++ b/src/app/modules/angular-slickgrid/services/grid.service.ts @@ -406,6 +406,7 @@ export class GridService { */ addItem(item: T, options?: GridServiceInsertOption): number { options = { ...GridServiceInsertOptionDefaults, ...options }; + const insertPosition = options && options.position || 'top'; if (!this._grid || !this._gridOptions || !this._dataView) { throw new Error('We could not find SlickGrid Grid, DataView objects'); @@ -417,7 +418,7 @@ export class GridService { // insert position top/bottom, defaults to top // when position is top we'll call insert at index 0, else call addItem which just push to the DataView array - if (options && options.position === 'bottom') { + if (insertPosition === 'bottom') { this._dataView.addItem(item); } else { this._dataView.insertItem(0, item); // insert at index 0 @@ -435,7 +436,7 @@ export class GridService { rowNumber = this._dataView.getRowById(item[idPropName]); } else { // scroll to row index 0 when inserting on top else scroll to the bottom where it got inserted - rowNumber = (options && options.position === 'bottom') ? this._dataView.getRowById(item[idPropName]) : 0; + rowNumber = (insertPosition === 'bottom') ? this._dataView.getRowById(item[idPropName]) : 0; this._grid.scrollRowIntoView(rowNumber); } @@ -465,14 +466,25 @@ export class GridService { addItems(items: T | T[], options?: GridServiceInsertOption): number[] { options = { ...GridServiceInsertOptionDefaults, ...options }; const idPropName = this._gridOptions.datasetIdPropertyName || 'id'; + const insertPosition = options && options.position || 'top'; const rowNumbers: number[] = []; // loop through all items to add if (!Array.isArray(items)) { - return [this.addItem(items, options)]; + return [this.addItem(items, options) || 0]; // on a single item, just call addItem() } else { - this._dataView.beginUpdate(); - items.forEach((item: T) => this.addItem(item, { ...options, highlightRow: false, resortGrid: false, triggerEvent: false, selectRow: false })); + // begin bulk transaction + this._dataView.beginUpdate(true); + + // insert position top/bottom, defaults to top + // when position is top we'll call insert at index 0, else call addItem which just push to the DataView array + if (insertPosition === 'bottom') { + this._dataView.addItems(items); + } else { + this._dataView.insertItems(0, items); // insert at index 0 to the start of the dataset + } + + // end the bulk transaction since we're all done this._dataView.endUpdate(); } @@ -482,11 +494,14 @@ export class GridService { } // scroll to row index 0 when inserting on top else scroll to the bottom where it got inserted - (options && options.position === 'bottom') ? this._grid.navigateBottom() : this._grid.navigateTop(); + (insertPosition === 'bottom') ? this._grid.navigateBottom() : this._grid.navigateTop(); // get row numbers of all new inserted items // we need to do it after resort and get each row number because it possibly changed after the sort - items.forEach((item: any) => rowNumbers.push(this._dataView.getRowById(item[idPropName]))); + items.forEach((item: any) => { + const rowNumber = this._dataView.getRowById(item[idPropName]); + rowNumbers.push(rowNumber) + }); // if user wanted to see highlighted row if (options.highlightRow) { @@ -562,14 +577,20 @@ export class GridService { return [items[idPropName]]; } - this._dataView.beginUpdate(); - const itemIds = []; + // begin bulk transaction + this._dataView.beginUpdate(true); + + const itemIds: string[] = []; items.forEach((item: T) => { if (item && item[idPropName] !== undefined) { itemIds.push(item[idPropName]); } - this.deleteItem(item, { ...options, triggerEvent: false }); }); + + // delete the item from the dataView + this._dataView.deleteItems(itemIds); + + // end the bulk transaction since we're all done this._dataView.endUpdate(); // do we want to trigger an event after deleting the item @@ -618,12 +639,15 @@ export class GridService { // when it's not an array, we can call directly the single item delete if (Array.isArray(itemIds)) { - this._dataView.beginUpdate(); + // begin bulk transaction + this._dataView.beginUpdate(true); for (let i = 0; i < itemIds.length; i++) { if (itemIds[i] !== null) { this.deleteItemById(itemIds[i], { triggerEvent: false }); } } + + // end the bulk transaction since we're all done this._dataView.endUpdate(); // do we want to trigger an event after deleting the item @@ -674,34 +698,53 @@ export class GridService { /** * Update an array of existing items with new properties inside the datagrid * @param item object arrays, which must contain unique "id" property and any other suitable properties - * @param options: provide the possibility to do certain actions after or during the upsert (highlightRow, selectRow, triggerEvent) + * @param options: provide the possibility to do certain actions after or during the update (highlightRow, selectRow, triggerEvent) * @return grid row indexes */ updateItems(items: T | T[], options?: GridServiceUpdateOption): number[] { options = { ...GridServiceUpdateOptionDefaults, ...options }; + const idPropName = this._gridOptions.datasetIdPropertyName || 'id'; // when it's not an array, we can call directly the single item update if (!Array.isArray(items)) { return [this.updateItem(items, options)]; } + // begin bulk transaction + this._dataView.beginUpdate(true); - this._dataView.beginUpdate(); - const gridRowNumbers: number[] = []; - items.forEach((item: any) => { - gridRowNumbers.push(this.updateItem(item, { ...options, highlightRow: false, selectRow: false, triggerEvent: false })); + // loop through each item, get their Ids + // also call a grid render on the modified row for the item to be reflected in the UI + const rowNumbers: number[] = []; + const itemIds: Array = []; + items.forEach((item: T) => { + const itemId = (!item || !(idPropName in item)) ? undefined : item[idPropName]; + itemIds.push(itemId); // keep item id reference + + if (this._dataView.getIdxById(itemId) !== undefined) { + const rowNumber = this._dataView.getRowById(itemId); + if (rowNumber !== undefined) { + rowNumbers.push(rowNumber); + this._grid.updateRow(rowNumber); + } + } }); + + // Update the items in the dataView, note that the itemIds must be in the same order as the items + this._dataView.updateItems(itemIds, items); + + // end the bulk transaction since we're all done this._dataView.endUpdate(); // only highlight at the end, all at once // we have to do this because doing highlight 1 by 1 would only re-select the last highlighted row which is wrong behavior if (options.highlightRow) { - this.highlightRow(gridRowNumbers); + this.highlightRow(rowNumbers); } // select the row in the grid if (options.selectRow && this._gridOptions && (this._gridOptions.enableCheckboxSelector || this._gridOptions.enableRowSelection)) { - this.setSelectedRows(gridRowNumbers); + this.setSelectedRows(rowNumbers); } // do we want to trigger an event after updating the item @@ -709,7 +752,7 @@ export class GridService { this.onItemUpdated.next(items); } - return gridRowNumbers; + return rowNumbers; } /** @@ -783,16 +826,19 @@ export class GridService { */ upsertItems(items: T | T[], options?: GridServiceInsertOption): { added: number, updated: number }[] { options = { ...GridServiceInsertOptionDefaults, ...options }; - // when it's not an array, we can call directly the single item update + // when it's not an array, we can call directly the single item upsert if (!Array.isArray(items)) { return [this.upsertItem(items, options)]; } - this._dataView.beginUpdate(); + // begin bulk transaction + this._dataView.beginUpdate(true); const upsertedRows: { added: number, updated: number }[] = []; items.forEach((item: T) => { upsertedRows.push(this.upsertItem(item, { ...options, highlightRow: false, resortGrid: false, selectRow: false, triggerEvent: false })); }); + + // end the bulk transaction since we're all done this._dataView.endUpdate(); const rowNumbers = upsertedRows.map((upsertRow) => upsertRow.added !== undefined ? upsertRow.added : upsertRow.updated); @@ -833,7 +879,9 @@ export class GridService { let isItemAdded = false; options = { ...GridServiceInsertOptionDefaults, ...options }; if (itemId === undefined) { - throw new Error(`Calling Upsert of an item requires the item to include a valid and unique "id" property`); + if (!this.hasRowSelectionEnabled()) { + throw new Error(`Calling Upsert of an item requires the item to include a valid and unique "id" property`); + } } let rowNumberAdded: number; @@ -853,4 +901,15 @@ export class GridService { } return { added: rowNumberAdded, updated: rowNumberUpdated }; } + + // -- + // private functions + // ------------------- + + /** Check wether the grid has the Row Selection enabled */ + private hasRowSelectionEnabled() { + const selectionModel = this._grid.getSelectionModel(); + const isRowSelectionEnabled = this._gridOptions.enableRowSelection || this._gridOptions.enableCheckboxSelector; + return (isRowSelectionEnabled && selectionModel); + } }