diff --git a/packages/common/src/core/slickCore.ts b/packages/common/src/core/slickCore.ts index d7dbff21d..2705bfdd8 100644 --- a/packages/common/src/core/slickCore.ts +++ b/packages/common/src/core/slickCore.ts @@ -12,7 +12,7 @@ export type Handler = (e: SlickEventData, args: ArgType) export interface BasePubSub { publish(_eventName: string | any, _data?: ArgType, delay?: number, assignEventCallback?: any): any; - subscribe(_eventName: string | Function, _callback: (data: ArgType) => void): any; + subscribe(_eventName: string | string[] | Function, _callback: (data: ArgType) => void): any; } type PubSubPublishType = { args: ArgType; eventData?: SlickEventData; nativeEvent?: Event; }; diff --git a/packages/common/src/services/groupingAndColspan.service.ts b/packages/common/src/services/groupingAndColspan.service.ts index 76ff59d85..d79b2efb3 100644 --- a/packages/common/src/services/groupingAndColspan.service.ts +++ b/packages/common/src/services/groupingAndColspan.service.ts @@ -60,10 +60,11 @@ export class GroupingAndColspanService { // for both picker (columnPicker/gridMenu) we also need to re-create after hiding/showing columns this._subscriptions.push( - this.pubSubService.subscribe(`onColumnPickerColumnsChanged`, () => this.renderPreHeaderRowGroupingTitles()), + this.pubSubService.subscribe( + ['onColumnPickerColumnsChanged', 'onGridMenuColumnsChanged', 'onGridMenuMenuClose'], + () => this.renderPreHeaderRowGroupingTitles() + ), this.pubSubService.subscribe('onHeaderMenuHideColumns', () => this.delayRenderPreHeaderRowGroupingTitles(0)), - this.pubSubService.subscribe(`onGridMenuColumnsChanged`, () => this.renderPreHeaderRowGroupingTitles()), - this.pubSubService.subscribe(`onGridMenuMenuClose`, () => this.renderPreHeaderRowGroupingTitles()), ); // we also need to re-create after a grid resize diff --git a/packages/common/src/services/pagination.service.ts b/packages/common/src/services/pagination.service.ts index 9d6c7516c..d07b1eec1 100644 --- a/packages/common/src/services/pagination.service.ts +++ b/packages/common/src/services/pagination.service.ts @@ -142,8 +142,7 @@ export class PaginationService { } // Subscribe to Filter Clear & Changed and go back to page 1 when that happen - this._subscriptions.push(this.pubSubService.subscribe('onFilterChanged', () => this.resetPagination())); - this._subscriptions.push(this.pubSubService.subscribe('onFilterCleared', () => this.resetPagination())); + this._subscriptions.push(this.pubSubService.subscribe(['onFilterChanged', 'onFilterCleared'], () => this.resetPagination())); // when using Infinite Scroll (only), we also need to reset pagination when sorting if (backendServiceApi?.options?.infiniteScroll) { @@ -153,8 +152,8 @@ export class PaginationService { // Subscribe to any dataview row count changed so that when Adding/Deleting item(s) through the DataView // that would trigger a refresh of the pagination numbers if (this.dataView) { - this._subscriptions.push(this.pubSubService.subscribe(`onItemAdded`, items => this.processOnItemAddedOrRemoved(items, true))); - this._subscriptions.push(this.pubSubService.subscribe(`onItemDeleted`, items => this.processOnItemAddedOrRemoved(items, false))); + this._subscriptions.push(this.pubSubService.subscribe('onItemAdded', items => this.processOnItemAddedOrRemoved(items, true))); + this._subscriptions.push(this.pubSubService.subscribe('onItemDeleted', items => this.processOnItemAddedOrRemoved(items, false))); } this.refreshPagination(false, false, true); diff --git a/packages/event-pub-sub/src/eventPubSub.service.spec.ts b/packages/event-pub-sub/src/eventPubSub.service.spec.ts index 14195212a..c39537b60 100644 --- a/packages/event-pub-sub/src/eventPubSub.service.spec.ts +++ b/packages/event-pub-sub/src/eventPubSub.service.spec.ts @@ -216,7 +216,37 @@ describe('EventPubSub Service', () => { expect(service.subscribedEvents.length).toBe(0); }); - it('should unsubscribeAll events', () => { + it('should be able to provide an array of event to subscribe and be able to unsubscribeAll events', () => { + const removeEventSpy = vi.spyOn(divContainer, 'removeEventListener'); + const getEventNameSpy = vi.spyOn(service, 'getEventNameByNamingConvention'); + const unsubscribeSpy = vi.spyOn(service, 'unsubscribe'); + const mockCallback = vi.fn(); + + service.subscribe(['onClick', 'onDblClick'], mockCallback); + divContainer.dispatchEvent(new CustomEvent('onClick', { detail: { name: 'John' } })); + + expect(getEventNameSpy).toHaveBeenCalledWith('onClick', ''); + expect(getEventNameSpy).toHaveBeenCalledWith('onDblClick', ''); + expect(service.subscribedEventNames).toEqual(['onClick', 'onDblClick']); + expect(service.subscribedEvents.length).toBe(2); + expect(mockCallback).toHaveBeenCalledTimes(1); + expect(mockCallback).toHaveBeenCalledWith({ name: 'John' }); + + divContainer.dispatchEvent(new CustomEvent('onClick', { detail: { name: 'John' } })); + divContainer.dispatchEvent(new CustomEvent('onDblClick', { detail: { name: 'Jane' } })); + + expect(mockCallback).toHaveBeenCalledTimes(3); + expect(mockCallback).toHaveBeenCalledWith({ name: 'John' }); + expect(mockCallback).toHaveBeenCalledWith({ name: 'Jane' }); + + service.unsubscribeAll(); + expect(removeEventSpy).toHaveBeenCalledWith('onClick', mockCallback); + expect(removeEventSpy).toHaveBeenCalledWith('onDblClick', mockCallback); + expect(unsubscribeSpy).toHaveBeenCalledTimes(2); + expect(service.subscribedEvents.length).toBe(0); + }); + + it('should be able to subscribe to multiple event and be able to unsubscribeAll events', () => { const removeEventSpy = vi.spyOn(divContainer, 'removeEventListener'); const getEventNameSpy = vi.spyOn(service, 'getEventNameByNamingConvention'); const unsubscribeSpy = vi.spyOn(service, 'unsubscribe'); @@ -234,6 +264,11 @@ describe('EventPubSub Service', () => { expect(mockCallback).toHaveBeenCalledTimes(1); expect(mockCallback).toHaveBeenCalledWith({ name: 'John' }); + divContainer.dispatchEvent(new CustomEvent('onDblClick', { detail: { name: 'Jane' } })); + + expect(mockDblCallback).toHaveBeenCalledTimes(1); + expect(mockDblCallback).toHaveBeenCalledWith({ name: 'Jane' }); + service.unsubscribeAll(); expect(removeEventSpy).toHaveBeenCalledWith('onClick', mockCallback); expect(removeEventSpy).toHaveBeenCalledWith('onDblClick', mockDblCallback); diff --git a/packages/event-pub-sub/src/eventPubSub.service.ts b/packages/event-pub-sub/src/eventPubSub.service.ts index 8a84b633e..234c62afb 100644 --- a/packages/event-pub-sub/src/eventPubSub.service.ts +++ b/packages/event-pub-sub/src/eventPubSub.service.ts @@ -120,17 +120,23 @@ export class EventPubSubService implements BasePubSubService { * @param callback The callback to be invoked when the specified message is published. * @return possibly a Subscription */ - subscribe(eventName: string, callback: (data: T) => void): Subscription { - const eventNameByConvention = this.getEventNameByNamingConvention(eventName, ''); + subscribe(eventNames: string | string[], callback: (data: T) => void): Subscription { + eventNames = Array.isArray(eventNames) ? eventNames : [eventNames]; + const subscriptions: Array<() => void> = []; + + eventNames.forEach(eventName => { + const eventNameByConvention = this.getEventNameByNamingConvention(eventName, ''); - // the event listener will return the data in the "event.detail", so we need to return its content to the final callback - // basically we substitute the "data" with "event.detail" so that the user ends up with only the "data" result - this._elementSource.addEventListener(eventNameByConvention, (event: CustomEventInit) => callback.call(null, event.detail as T)); - this._subscribedEvents.push({ name: eventNameByConvention, listener: callback }); + // the event listener will return the data in the "event.detail", so we need to return its content to the final callback + // basically we substitute the "data" with "event.detail" so that the user ends up with only the "data" result + this._elementSource.addEventListener(eventNameByConvention, (event: CustomEventInit) => callback.call(null, event.detail as T)); + this._subscribedEvents.push({ name: eventNameByConvention, listener: callback }); + subscriptions.push(() => this.unsubscribe(eventNameByConvention, callback as never)); + }); - // return a subscription that we can unsubscribe + // return a subscription(s) that we can unsubscribed return { - unsubscribe: () => this.unsubscribe(eventNameByConvention, callback as never) + unsubscribe: () => subscriptions.forEach(unsub => unsub()) }; } @@ -146,7 +152,7 @@ export class EventPubSubService implements BasePubSubService { this._elementSource.addEventListener(eventNameByConvention, listener); this._subscribedEvents.push({ name: eventNameByConvention, listener }); - // return a subscription that we can unsubscribe + // return a subscription that we can unsubscribed return { unsubscribe: () => this.unsubscribe(eventNameByConvention, listener as never) }; diff --git a/packages/event-pub-sub/src/types/basePubSubService.interface.ts b/packages/event-pub-sub/src/types/basePubSubService.interface.ts index 13ca83dd0..8f898ff1f 100644 --- a/packages/event-pub-sub/src/types/basePubSubService.interface.ts +++ b/packages/event-pub-sub/src/types/basePubSubService.interface.ts @@ -16,7 +16,7 @@ export interface BasePubSubService { * @param callback The callback to be invoked when the specified message is published. * @return possibly a Subscription */ - subscribe(_eventName: string | Function, _callback: (data: T) => void): EventSubscription | any; + subscribe(_eventName: string | string[] | Function, _callback: (data: T) => void): EventSubscription | any; /** * Subscribes to a custom event message channel or message type.