import { FakeEvent } from './fake-event'; import {MultiMap} from '../utils'; import { getLogger } from '../utils/logger'; /** * A work-alike for EventTarget. Only DOM elements may be true EventTargets, * but this can be used as a base class to provide event dispatch to non-DOM * classes. Only FakeEvents should be dispatched. * * @struct * @constructor * @export */ export class FakeEventTarget { private _listeners: MultiMap boolean | void>; private readonly dispatchTarget: FakeEventTarget; private __logger: any; constructor() { /** * @private {!MultiMap.} */ this._listeners = new MultiMap(); /** * The target of all dispatched events. Defaults to |this|. * @type {FakeEventTarget} */ this.dispatchTarget = this; this.__logger = getLogger('FakeEventTarget'); } /** * Add an event listener to this object. * * @param {string} type The event type to listen for. * @param {FakeEventTarget.EventListener} listener The callback or * listener object to invoke. * @param {boolean=} opt_capturing Ignored. FakeEventTargets do not have * parents, so events neither capture nor bubble. * @override * @export */ public addEventListener(type: string, listener: typeof EventListener): void { this._listeners.push(type, listener); } /** * Remove an event listener from this object. * * @param {string} type The event type for which you wish to remove a listener. * @param {FakeEventTarget.EventListener} listener The callback or * listener object to remove. * @param {boolean=} opt_capturing Ignored. FakeEventTargets do not have * parents, so events neither capture nor bubble. * @override * @export */ public removeEventListener(type: string, listener: typeof EventListener): void { this._listeners.remove(type, listener); } /** * Dispatch an event from this object. * * @param {!Event} event The event to be dispatched from this object. * @return {boolean} True if the default action was prevented. * @override * @export */ public dispatchEvent(event: FakeEvent): boolean { // In many browsers, it is complex to overwrite properties of actual Events. // Here we expect only to dispatch FakeEvents, which are simpler. //goog.asserts.assert(event instanceof FakeEvent, // 'FakeEventTarget can only dispatch FakeEvents!'); const list = this._listeners.get(event.type) || []; for (let i = 0; i < list.length; ++i) { // Do this every time, since events can be re-dispatched from handlers. //$FlowFixMe - need to cast to event target but can't yet make EventTarget inherited event.target = this.dispatchTarget; //$FlowFixMe - need to cast to event target but can't yet make EventTarget inherited event.currentTarget = this.dispatchTarget; const listener = list[i]; try { // native DOM event handler // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore if (listener.handleEvent) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore listener.handleEvent(event); } else { listener.call(this, event); } } catch (exception) { // Exceptions during event handlers should not affect the caller, // but should appear on the console as uncaught, according to MDN: // http://goo.gl/N6Ff27 this.__logger.error(`Error occurred when handling event: ${event.type}.`, exception); } if (event.stopped) { break; } } return event.defaultPrevented; } } /** * These are the listener types defined in the closure extern for EventTarget. * @typedef {EventListener|function(!Event):(boolean|undefined)} */ declare function EventListener(event: FakeEvent): boolean | void;