From bbae36823faa3f5e1da686077f2718e406fa6951 Mon Sep 17 00:00:00 2001 From: bigopon Date: Thu, 14 Dec 2017 17:06:02 +1100 Subject: [PATCH 1/4] feat(ElementEvents): batch subscription --- dist/aurelia-templating.d.ts | 86 +++++++++++++++++++++--------------- src/element-events.js | 66 ++++++++++++++++++++++----- test/element-events.js | 47 ++++++++++++++++++++ 3 files changed, 153 insertions(+), 46 deletions(-) diff --git a/dist/aurelia-templating.d.ts b/dist/aurelia-templating.d.ts index 8207160b..181da512 100644 --- a/dist/aurelia-templating.d.ts +++ b/dist/aurelia-templating.d.ts @@ -34,12 +34,6 @@ import { import { TaskQueue } from 'aurelia-task-queue'; -export declare interface EventHandler { - eventName: string; - bubbles: boolean; - dispose: Function; - handler: Function; -} /** * Specifies how a view should be created. @@ -472,10 +466,29 @@ export declare class ViewEngineHooksResource { } export declare function viewEngineHooks(target?: any): any; -/** - * Dispatches subscribets to and publishes events in the DOM. - * @param element - */ + +export declare interface EventHandler { + eventName: string; + bubbles: boolean; + dispose: Function; + handler: Function; +} + +export declare interface SubscriptionHandlerConfig { + handler: Function + capture?: boolean + passive?: boolean + once?: boolean +} + +export declare interface BatchSubscriptionConfig { + [eventName: string]: Function | SubscriptionHandlerConfig +} + +export declare interface EventSubscriptions { + [eventName: string]: EventHandler +} + /** * Dispatches subscribets to and publishes events in the DOM. * @param element @@ -484,41 +497,44 @@ export declare class ElementEvents { constructor(element: EventTarget); /** - * Dispatches an Event on the context element. - * @param eventName - * @param detail - * @param bubbles - * @param cancelable - */ + * Dispatches an Event on the context element. + * @param eventName + * @param detail + * @param bubbles + * @param cancelable + */ publish(eventName: string, detail?: Object, bubbles?: boolean, cancelable?: boolean): any; /** - * Adds and Event Listener on the context element. - * @param eventName - * @param handler - * @param bubbles - * @return Returns the eventHandler containing a dispose method - */ - subscribe(eventName: string, handler: Function, bubbles?: boolean): EventHandler; + * Adds and Event Listener on the context element. + * @return Returns the eventHandler containing a dispose method + */ + subscribe(events: TEvents): EventSubscriptions; + subscribe(eventName: string, handler: Function, captureOrOptions?: boolean | AddEventListenerOptions = true): EventHandler; + subscribe(configOrEventName: string | BatchSubscriptionConfig, handler?: Function, bubbles?: Boolean): EventHandler | EventSubscriptions; /** - * Adds an Event Listener on the context element, that will be disposed on the first trigger. - * @param eventName - * @param handler - * @param bubbles - * @return Returns the eventHandler containing a dispose method - */ - subscribeOnce(eventName: String, handler: Function, bubbles?: Boolean): EventHandler; + * Adds an Event Listener on the context element, that will be disposed on the first trigger. + * @return Returns the eventHandler containing a dispose method + */ + subscribeOnce(events: BatchSubscriptionConfig): EventSubscriptions; + subscribeOnce(eventName: string, handler: Function, captureOrOptions?: boolean | AddEventListenerOptions = true): EventHandler; + subscribeOnce(configOrEventName: string | BatchSubscriptionConfig, handler?: Function, bubbles?: Boolean): EventHandler | EventSubscriptions; /** - * Removes all events that are listening to the specified eventName. - * @param eventName - */ + * Add multiple event listeners at once, with option to specify once over the whole set + */ + batchSubscribe(events: BatchSubscriptionConfig, once?: boolean): EventSubscriptions + + /** + * Removes all events that are listening to the specified eventName. + * @param eventName + */ dispose(eventName: string): void; /** - * Removes all event handlers. - */ + * Removes all event handlers. + */ disposeAll(): any; } diff --git a/src/element-events.js b/src/element-events.js index edd10496..f09bf06d 100644 --- a/src/element-events.js +++ b/src/element-events.js @@ -8,6 +8,22 @@ interface EventHandler { handler: Function; } +// Don't extends AddEventListenerOptions, make explicit for readability +interface SubscriptionHandlerConfig { + handler: Function; + capture?: boolean; + passive?: boolean; + once?: boolean; +} + +interface BatchSubscriptionConfig { + [eventName: string]: Function | SubscriptionHandlerConfig +} + +interface EventSubscriptions { + [eventName: string]: EventHandler +} + /** * Dispatches subscribets to and publishes events in the DOM. * @param element @@ -51,26 +67,54 @@ export class ElementEvents { * Adds and Event Listener on the context element. * @return Returns the eventHandler containing a dispose method */ - subscribe(eventName: string, handler: Function, captureOrOptions?: boolean | AddEventListenerOptions = true): EventHandler { - if (typeof handler === 'function') { - const eventHandler = new EventHandlerImpl(this, eventName, handler, captureOrOptions, false); - return eventHandler; - } + subscribe(configOrEventName: string | BatchSubscriptionConfig, handler: Function, captureOrOptions?: boolean | AddEventListenerOptions = true): EventSubscriptions | EventHandler { + if (typeof configOrEventName === 'string') { + if (typeof handler === 'function') { + const eventHandler = new EventHandlerImpl(this, configOrEventName, handler, captureOrOptions, false); + return eventHandler; + } - return undefined; + return undefined; + } else { + return this.batchSubscribe(configOrEventName, false); + } } /** * Adds an Event Listener on the context element, that will be disposed on the first trigger. * @return Returns the eventHandler containing a dispose method */ - subscribeOnce(eventName: String, handler: Function, captureOrOptions?: boolean | AddEventListenerOptions = true): EventHandler { - if (typeof handler === 'function') { - const eventHandler = new EventHandlerImpl(this, eventName, handler, captureOrOptions, true); - return eventHandler; + subscribeOnce(configOrEventName: string | BatchSubscriptionConfig, handler: Function, captureOrOptions?: boolean | AddEventListenerOptions = true): EventSubscriptions | EventHandler { + if (typeof configOrEventName === 'string') { + if (typeof handler === 'function') { + const eventHandler = new EventHandlerImpl(this, configOrEventName, handler, captureOrOptions, true); + return eventHandler; + } + + return undefined; + } else { + return this.batchSubscribe(configOrEventName, true); } + } - return undefined; + batchSubscribe(config: BatchSubscriptionConfig, once: boolean = false): EventSubscriptions { + const subscriptions: EventSubscriptions = {}; + for (let eventName in config) { + let handlerOrOptions = config[eventName]; + let handler: Function; + let listenerOptions: boolean | AddEventListenerOptions; + let _once = once; + if (typeof handlerOrOptions === 'function') { + handler = handlerOrOptions; + listenerOptions = false; + } else { + handler = handlerOrOptions.handler; + listenerOptions = handlerOrOptions; + _once = !!handlerOrOptions.once; + } + subscriptions[eventName] = new EventHandlerImpl(this, eventName, handler, listenerOptions, _once); + } + return subscriptions; } /** diff --git a/test/element-events.js b/test/element-events.js index 223cbb2f..d18abdb2 100644 --- a/test/element-events.js +++ b/test/element-events.js @@ -34,6 +34,53 @@ describe('ElementEvents', () => { expect(callCount).toBe(1); }); + it('should batch subscribe', () => { + let value; + let callCount = 0; + + const subscriptions = elementEvents.subscribe({ + input: () => { + callCount++; + value = input.value; + }, + blur: () => { + callCount++; + }, + focus: { + handler: () => { + callCount++; + }, + once: true + } + }); + + const newValue = 1234; + input.value = newValue; + input.dispatchEvent(new CustomEvent('input')); + + expect(value === newValue.toString()).toBe(true); + expect(callCount).toBe(1); + + subscriptions.input.dispose(); + + input.dispatchEvent(new CustomEvent('input')); + expect(callCount).toBe(1); + + input.dispatchEvent(new CustomEvent('blur')); + expect(callCount).toBe(2); + + input.dispatchEvent(new CustomEvent('focus')); + expect(callCount).toBe(3); + + input.dispatchEvent(new CustomEvent('focus')); + expect(callCount).toBe(3); + + elementEvents.disposeAll(); + + input.dispatchEvent(new CustomEvent('blur')); + expect(callCount).toBe(3); + }) + it('should subscribe once', () => { let value; let callCount = 0; From 671a48e7e2f468938c858cb55f3da218f9d992cc Mon Sep 17 00:00:00 2001 From: bigopon Date: Thu, 14 Dec 2017 17:12:01 +1100 Subject: [PATCH 2/4] once = false when undefined --- src/element-events.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/element-events.js b/src/element-events.js index f09bf06d..4bc02792 100644 --- a/src/element-events.js +++ b/src/element-events.js @@ -110,7 +110,7 @@ export class ElementEvents { } else { handler = handlerOrOptions.handler; listenerOptions = handlerOrOptions; - _once = !!handlerOrOptions.once; + _once = handlerOrOptions.once === true; } subscriptions[eventName] = new EventHandlerImpl(this, eventName, handler, listenerOptions, _once); } From b0606f09ceafe385ee4c5553c92714ecc455da9e Mon Sep 17 00:00:00 2001 From: bigopon Date: Thu, 14 Dec 2017 17:16:50 +1100 Subject: [PATCH 3/4] remove default value from interface --- dist/aurelia-templating.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/aurelia-templating.d.ts b/dist/aurelia-templating.d.ts index 181da512..295919de 100644 --- a/dist/aurelia-templating.d.ts +++ b/dist/aurelia-templating.d.ts @@ -510,7 +510,7 @@ export declare class ElementEvents { * @return Returns the eventHandler containing a dispose method */ subscribe(events: TEvents): EventSubscriptions; - subscribe(eventName: string, handler: Function, captureOrOptions?: boolean | AddEventListenerOptions = true): EventHandler; + subscribe(eventName: string, handler: Function, captureOrOptions?: boolean | AddEventListenerOptions): EventHandler; subscribe(configOrEventName: string | BatchSubscriptionConfig, handler?: Function, bubbles?: Boolean): EventHandler | EventSubscriptions; /** @@ -518,7 +518,7 @@ export declare class ElementEvents { * @return Returns the eventHandler containing a dispose method */ subscribeOnce(events: BatchSubscriptionConfig): EventSubscriptions; - subscribeOnce(eventName: string, handler: Function, captureOrOptions?: boolean | AddEventListenerOptions = true): EventHandler; + subscribeOnce(eventName: string, handler: Function, captureOrOptions?: boolean | AddEventListenerOptions): EventHandler; subscribeOnce(configOrEventName: string | BatchSubscriptionConfig, handler?: Function, bubbles?: Boolean): EventHandler | EventSubscriptions; /** From b3edea5807eb69de45665946acf76206b9552a8e Mon Sep 17 00:00:00 2001 From: bigopon Date: Thu, 14 Dec 2017 23:01:34 +1100 Subject: [PATCH 4/4] once / undefined --- src/element-events.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/element-events.js b/src/element-events.js index 4bc02792..40ab1b0b 100644 --- a/src/element-events.js +++ b/src/element-events.js @@ -110,7 +110,7 @@ export class ElementEvents { } else { handler = handlerOrOptions.handler; listenerOptions = handlerOrOptions; - _once = handlerOrOptions.once === true; + _once = handlerOrOptions.once === undefined ? _once : !!handlerOrOptions.once; } subscriptions[eventName] = new EventHandlerImpl(this, eventName, handler, listenerOptions, _once); }