From aa12d124e3da3db1db703f5c90324c85cbd6d67d Mon Sep 17 00:00:00 2001 From: Alphability Date: Wed, 18 Aug 2021 11:13:26 +0200 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=F0=9F=93=9D=20documenting=20?= =?UTF-8?q?+=20rationalizing=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/alice/__tests__/index.test.ts | 56 ++++---- packages/alice/src/detect.ts | 59 ++++++++- packages/alice/src/index.ts | 63 ++++++++- packages/alice/src/speed.ts | 101 ++++++++++++++- packages/alice/src/tween.ts | 169 ++++++++++++++++++++++++- packages/eva/__tests__/index.test.ts | 53 +++----- packages/eva/src/index.ts | 48 +++++-- 7 files changed, 462 insertions(+), 87 deletions(-) diff --git a/packages/alice/__tests__/index.test.ts b/packages/alice/__tests__/index.test.ts index 37db175..b0fce37 100644 --- a/packages/alice/__tests__/index.test.ts +++ b/packages/alice/__tests__/index.test.ts @@ -1,7 +1,17 @@ import { Alice } from '../src/index'; +const alice = new Alice(); + beforeEach(() => { - jest.resetModules(); + // Init before each test + alice.initialize(); +}); + +afterEach(() => { + // Resetting Alice instance values before next test. + global.scrollY = 0; + global.dispatchEvent(new Event('scroll')); + alice.destroy(); }); describe('Success cases', () => { @@ -9,45 +19,33 @@ describe('Success cases', () => { // Change the scroll value to 1000px. global.scrollY = 1000; - const alice = new Alice(); - alice.initialize(); - // Trigger the window scroll event. global.dispatchEvent(new Event('scroll')); global.dispatchEvent(new Event('scroll')); expect(alice.scroll.data.scrollTop).toStrictEqual(1000); - - alice.destroy(); }); - // test('It should not take the first scroll event into consideration', async () => { - // // Change the scroll value to 1000px. - // global.scrollY = 1000; - - // const alice = new Alice2(); - // alice.initialize(); - - // // Triggering the window's first scroll event only. - // global.dispatchEvent(new Event('scroll')); + test('It should not take the first scroll event into consideration', async () => { + // Change the scroll value to 1000px. + global.scrollY = 1000; - // expect(alice.scroll.data.scrollTop).toStrictEqual(0); + // Triggering the window's first scroll event only. + global.dispatchEvent(new Event('scroll')); - // alice.destroy(); - // }); + expect(alice.scroll.data.scrollTop).toStrictEqual(0); + }); - // test('It should not take the scroll event into consideration', async () => { - // // Change the scroll value to 1000px. - // global.scrollY = 1000; + test('It should not take the scroll event into consideration', async () => { + // Change the scroll value to 1000px. + global.scrollY = 1000; - // const alice = new Alice3(); - // alice.initialize(); - // alice.destroy(); + alice.destroy(); - // // Trigger the window scroll event. - // global.dispatchEvent(new Event('scroll')); - // global.dispatchEvent(new Event('scroll')); + // Trigger the window scroll event. + global.dispatchEvent(new Event('scroll')); + global.dispatchEvent(new Event('scroll')); - // expect(alice.scroll.data.scrollTop).toStrictEqual(0); - // }); + expect(alice.scroll.data.scrollTop).toStrictEqual(0); + }); }); diff --git a/packages/alice/src/detect.ts b/packages/alice/src/detect.ts index 99fc02a..1cfccc9 100644 --- a/packages/alice/src/detect.ts +++ b/packages/alice/src/detect.ts @@ -9,10 +9,30 @@ import { Tween } from './tween'; import { isInView, getBoundings } from './utils/view'; export class Detect extends Tween { + /** + * @description Boolean ensuring that we can't initialize multiple detection plugins. + * @static + * @memberof Detect + */ + static isInitialized = false; + /** + * @description Object allowing to watch view data. + * @static + * @type {Eva} + * @memberof Detect + */ + static _eva: Eva; + /** + * @description List of detect tween objects. + * @private + * @type {TweenObject[]} + * @memberof Detect + */ + private _detectTweensList: TweenObject[] = []; /** @@ -20,10 +40,19 @@ export class Detect extends Tween { * @author Alphability * @memberof Detect */ + constructor() { super(); } + /** + * @description Computing tween in view position. + * @author Alphability + * @private + * @param {TweenObject} tween + * @memberof Detect + */ + private async _computeDetection(tween: TweenObject) { // NOTE: Early returns with if statements without curly brackets allow the browser to parse js. Thus, getBoudingClientRect was calling a style recalculation even if it as not used. @@ -53,6 +82,13 @@ export class Detect extends Tween { ); } + /** + * @description Loop through tweens to compute them. + * @author Alphability + * @private + * @memberof Detect + */ + private _handleTweenList() { this._detectTweensList.forEach((tween) => { if (!tween.options.once || !tween.state.isInView) { @@ -62,10 +98,11 @@ export class Detect extends Tween { } /** - * @description Initializing the viewport reactive data abilities when the window object is defined. + * @description Initializing the detection abilities when the window object is defined. * @author Alphability * @memberof Detect */ + public initialize(): void { // No multiple init if (Detect.isInitialized) { @@ -98,16 +135,26 @@ export class Detect extends Tween { } /** - * @description Destroying the reactive data object and listeners. + * @description Destroying the tweens. * @author Alphability * @memberof Detect */ + public destroy(): void { const ids = Object.keys(Detect._list); this.remove(ids); Detect.isInitialized = false; } + /** + * @description Adding new tweens to the detection list. + * @author Alphability + * @param {(any | any[])} elements + * @param {InputTweenOptions} options + * @returns {(string | string[])} + * @memberof Detect + */ + public add( elements: any | any[], options: InputTweenOptions @@ -125,6 +172,14 @@ export class Detect extends Tween { return ids; } + /** + * @description Removing tweens by id. + * @author Alphability + * @param {string[]} ids + * @returns {Tween} + * @memberof Detect + */ + public remove(ids: string[]): Tween { this._detectTweensList = []; return super.remove(ids); diff --git a/packages/alice/src/index.ts b/packages/alice/src/index.ts index dd7d499..28308c2 100644 --- a/packages/alice/src/index.ts +++ b/packages/alice/src/index.ts @@ -11,6 +11,7 @@ export class Alice { * @static * @memberof Alice */ + static _scrollEndDelay = 100; /** @@ -49,23 +50,45 @@ export class Alice { static _speed: Speed = new Speed(); + /** + * @description Boolean ensuring that we can't initialize multiple scroll listeners. + * @private + * @memberof Alice + */ + private _isInitialized = false; - private _optionsPluginsList: string[] = []; + /** + * @description Boolean ensuring that we prevent automatic browser scroll on refresh. + * @private + * @memberof Alice + */ private _refreshScroll = false; + private _optionsPluginsList: string[] = []; + /** * Creates an instance of Alice. * @author Alphability * @memberof Alice */ + constructor(optionsPluginsList: string[] = []) { this._scrollEventHandler = this._scrollEventHandler.bind(this); this._optionsPluginsList = optionsPluginsList; } + /** + * @description Method used to initialize the plugins only when window is loaded. + * @author Alphability + * @private + * @param {string[]} pluginsList + * @returns {void} + * @memberof Alice + */ + private _initializePlugins(pluginsList: string[]): void { if (!pluginsList.length) { return; @@ -96,6 +119,7 @@ export class Alice { * @private * @memberof Alice */ + private _collectEventValues(): void { Alice._reactor.data.scrollTop = window.scrollY || window.pageYOffset; } @@ -106,6 +130,7 @@ export class Alice { * @private * @memberof Alice */ + private _scrollEventHandler(): void { // Prevent automatic browser scroll on refresh if (!this._refreshScroll) { @@ -119,6 +144,11 @@ export class Alice { this._scrollEnd(); } + /** + * @description Scroll end value refresh. + * @private + * @memberof Alice + */ private _scrollEnd = debounce(() => { Alice._reactor.data.isScrolling = false; }, Alice._scrollEndDelay); @@ -129,6 +159,7 @@ export class Alice { * @private * @memberof Alice */ + private _attachListeners(): void { window.addEventListener('scroll', this._scrollEventHandler, { passive: true, @@ -141,16 +172,18 @@ export class Alice { * @private * @memberof Alice */ + private _detachListeners(): void { // ⚡ Avoid memory leak window.removeEventListener('scroll', this._scrollEventHandler, false); } /** - * @description Initializing the viewport reactive data abilities when the window object is defined. + * @description Initializing the scroll reactive data abilities when the window object is defined. * @author Alphability * @memberof Alice */ + public initialize(): void { // No multiple init // Avoid having multiple listeners at the same time. @@ -175,30 +208,54 @@ export class Alice { * @author Alphability * @memberof Alice */ + public destroy(): void { this._detachListeners(); + this._refreshScroll = false; this._isInitialized = false; } /** - * @description Reeactive properties object's getter. + * @description Reactive scroll properties object's getter. * @readonly * @type {Calvin} * @memberof Alice */ + get scroll(): Calvin { return Alice._reactor; } + /** + * @description Reactive viewport properties object's getter. + * @readonly + * @type {Calvin} + * @memberof Alice + */ + get view(): Calvin { return Alice._eva.view; } + /** + * @description HTMLElements in view detector. + * @readonly + * @type {Detect} + * @memberof Alice + */ + get detect(): Detect { return Alice._detect; } + /** + * @description HTMLELements speed modifier. + * @readonly + * @type {Speed} + * @memberof Alice + */ + get speed(): Speed { return Alice._speed; } diff --git a/packages/alice/src/speed.ts b/packages/alice/src/speed.ts index bb885d3..b11ca77 100644 --- a/packages/alice/src/speed.ts +++ b/packages/alice/src/speed.ts @@ -20,17 +20,55 @@ import { const fastdom = fastdomCore.extend(fastdomPromised); export class Speed extends Tween { + /** + * @description Boolean ensuring that we can't initialize multiple speed plugins. + * @static + * @memberof Speed + */ + static isInitialized = false; - // 60FPS = 60 frames per second = 16 ms per frame + /** + * @description Value used to ensure that we compute tweens only 60 times per second. + * 60FPS = 60 frames per second = 16 ms per frame. + * @static + * @memberof Speed + */ + static _sixtyFpsToMs = 16; + /** + * @description Object allowing to watch view data. + * @static + * @type {Eva} + * @memberof Speed + */ + static _eva: Eva; + /** + * @description List of speed tween objects. + * @private + * @type {TweenObject[]} + * @memberof Speed + */ + private _speedTweensList: TweenObject[] = []; + /** + * @description Ensuring that we won't process multiple speed computations at the same time. + * @private + * @memberof Speed + */ + private _ticking = false; + /** + * @description Ensuring that we'll complete lerp transformations even if the user is not scrolling anymore. + * @private + * @memberof Speed + */ + private _lerpDone = true; /** @@ -38,11 +76,21 @@ export class Speed extends Tween { * @author Alphability * @memberof Speed */ + constructor() { super(); } - private async _computeDetection(tween: TweenObject) { + /** + * @description Computing tween in view position. + * @author Alphability + * @private + * @param {TweenObject} tween + * @returns {Promise} + * @memberof Speed + */ + + private async _computeDetection(tween: TweenObject): Promise { // NOTE: Early returns with if statements without curly brackets allow the browser to parse js. Thus, getBoudingClientRect was calling a style recalculation even if it as not used. if (!tween.state.boundings) { @@ -71,6 +119,15 @@ export class Speed extends Tween { ); } + /** + * @description Computing element speed position. + * @author Alphability + * @private + * @param {TweenObject} tween + * @returns {void} + * @memberof Speed + */ + private _computeSpeed(tween: TweenObject): void { if (!tween.state.isInView) { return; @@ -101,7 +158,15 @@ export class Speed extends Tween { !lerpAmount || Math.abs(transformValue - tween.state.coordinates.y) <= 1; } - private async _applySpeed(tween: TweenObject) { + /** + * @description Applying the computed speed position to the element. + * @author Alphability + * @private + * @param {TweenObject} tween + * @returns {Promise} + * @memberof Speed + */ + private async _applySpeed(tween: TweenObject): Promise { const { element, state: { isInView, coordinates }, @@ -116,6 +181,13 @@ export class Speed extends Tween { }); } + /** + * @description Loop through tweens to compute them. + * @author Alphability + * @private + * @memberof Speed + */ + private _handleTweenList = throttle(async () => { if (this._ticking) { return; @@ -162,10 +234,11 @@ export class Speed extends Tween { } /** - * @description Initializing the viewport reactive data abilities when the window object is defined. + * @description Initializing the speed abilities when the window object is defined. * @author Alphability * @memberof Speed */ + public initialize(): void { // No multiple init if (Speed.isInitialized) { @@ -200,16 +273,26 @@ export class Speed extends Tween { } /** - * @description Destroying the reactive data object and listeners. + * @description Destroying the tweens. * @author Alphability * @memberof Speed */ + public destroy(): void { const ids = Object.keys(Speed._list); this.remove(ids); Speed.isInitialized = false; } + /** + * @description Adding new tweens to the detection list. + * @author Alphability + * @param {(any | any[])} elements + * @param {InputTweenOptions} { speed = 0, lerp = 0, ...otherOptions } + * @returns {(string | string[])} + * @memberof Speed + */ + public add( elements: any | any[], { speed = 0, lerp = 0, ...otherOptions }: InputTweenOptions @@ -230,6 +313,14 @@ export class Speed extends Tween { return ids; } + /** + * @description Removing tweens by id. + * @author Alphability + * @param {string[]} ids + * @returns {Tween} + * @memberof Speed + */ + public remove(ids: string[]): Tween { this._speedTweensList = []; return super.remove(ids); diff --git a/packages/alice/src/tween.ts b/packages/alice/src/tween.ts index 6d510ba..641f18f 100644 --- a/packages/alice/src/tween.ts +++ b/packages/alice/src/tween.ts @@ -23,12 +23,64 @@ const arrayEquals = (a: any[], b: any[]) => a.length === b.length && a.every((val, index) => val === b[index]); export abstract class Tween { + /** + * @description Namespace used to generate tweens unique ids. + * @static + * @memberof Tween + */ + static _namespace = 'tween'; + + /** + * @description Incremental number used to generate tweens unique ids. + * @static + * @memberof Tween + */ + static _idTicket = 0; + + /** + * @description Scroll reactive data. + * @static + * @type {Calvin} + * @memberof Tween + */ + + static _reactor: Calvin; + + /** + * @description List of all the tweens ordered by id. + * @static + * @type {TweenList} + * @memberof Tween + */ + static _list: TweenList = {}; + + /** + * @description List of tween's possible events. + * @static + * @memberof Tween + */ + static _events = ['enter-view', 'leave-view']; + + /** + * @description List of functions sorted by event name. + * @static + * @type {Record} + * @memberof Tween + */ + static _notifications: Record = {}; + /** + * @description Default tween options. + * @static + * @type {TweenOptions} + * @memberof Tween + */ + static _defaultOptions: TweenOptions = { addClass: true, once: false, @@ -42,6 +94,13 @@ export abstract class Tween { position: 'top', }; + /** + * @description Default tween state. + * @static + * @type {TweenState} + * @memberof Tween + */ + static _defaultState: TweenState = { itemId: null, classes: [], @@ -60,11 +119,25 @@ export abstract class Tween { collantEvent: '', }; - static _reactor: Calvin; + /** + * @description Boolean used to debounce the tweens reset during window resize. + * @private + * @memberof Tween + */ private _resizingMainHandler = false; - private _emit(propertyName: string, propertyValue: any) { + /** + * @description Emitting a notification. + * @author Alphability + * @private + * @param {string} propertyName + * @param {*} propertyValue + * @returns {void} + * @memberof Tween + */ + + private _emit(propertyName: string, propertyValue: any): void { const propertyWatchers = Tween._notifications[propertyName]; if (!propertyWatchers) { return; @@ -76,6 +149,14 @@ export abstract class Tween { } } + /** + * @description Method used to attach new functions to events. + * @author Alphability + * @private + * @param {Record} notificationsObject + * @memberof Tween + */ + private _on(notificationsObject: Record) { Object.entries(notificationsObject).forEach( ([propertyName, notification]) => { @@ -86,6 +167,16 @@ export abstract class Tween { ); } + /** + * @description Tween event subscription. + * @author Alphability + * @private + * @param {string} eventName + * @param {string} id + * @param {AnyFunction} func + * @memberof Tween + */ + private _subscribeItemToEvent( eventName: string, id: string, @@ -95,11 +186,29 @@ export abstract class Tween { this._on({ [eventId]: func }); } + /** + * @description Deleting all notifications for a specific event. + * @author Alphability + * @private + * @param {string} propertyName + * @memberof Tween + */ + private _flush(propertyName: string) { delete Tween._notifications[propertyName]; } - private _getProxyState(element: HTMLElement, itemIndex: number) { + /** + * @description Reactive fresh tween state through the use of Proxy. + * @author Alphability + * @private + * @param {HTMLElement} element + * @param {number} itemIndex + * @returns {TweenState} + * @memberof Tween + */ + + private _getProxyState(element: HTMLElement, itemIndex: number): TweenState { // ⚠️ The state needs to be declared here in order to give a fresh object to each proxy const state = JSON.parse(JSON.stringify(Tween._defaultState)); @@ -155,6 +264,17 @@ export abstract class Tween { }); } + /** + * @description Adding an element to the tween list. + * @author Alphability + * @private + * @param {HTMLElement} element + * @param {InputTweenOptions} options + * @param {number} [itemIndex=0] + * @returns {string} + * @memberof Tween + */ + private _addItem( element: HTMLElement, options: InputTweenOptions, @@ -181,6 +301,14 @@ export abstract class Tween { return itemId; } + /** + * @description Removing a tween from the tweens list. + * @author Alphability + * @private + * @param {string} id + * @memberof Tween + */ + private _removeItem(id: string) { Tween._events.forEach((eventName) => { this._flush(`${id}-${eventName}`); @@ -188,6 +316,14 @@ export abstract class Tween { delete Tween._list[id]; } + /** + * @description Handling tweens when window resizes. + * @author Alphability + * @protected + * @returns {void} + * @memberof Tween + */ + protected _handleResize(): void { if (this._resizingMainHandler || !Tween._reactor) { return; @@ -240,6 +376,15 @@ export abstract class Tween { }); } + /** + * @description Adding one or more tweens to the tweens list. + * @author Alphability + * @param {(HTMLElement | HTMLElement[])} elements + * @param {TweenOptions} options + * @returns {(string | string[])} + * @memberof Tween + */ + public add( elements: HTMLElement | HTMLElement[], options: TweenOptions @@ -254,6 +399,16 @@ export abstract class Tween { } } + /** + * @description Allowing us to hook on a specific event. + * @author Alphability + * @param {string} eventName + * @param {string[]} ids + * @param {AnyFunction} func + * @returns {Tween} + * @memberof Tween + */ + public on(eventName: string, ids: string[], func: AnyFunction): Tween { // Events name check (ensuring that every functions will have a reference in order to use removeEventListener). if (!Tween._events.includes(eventName)) @@ -272,6 +427,14 @@ export abstract class Tween { return this; } + /** + * @description Removing tweens from the tweens list by ids. + * @author Alphability + * @param {string[]} ids + * @returns {Tween} + * @memberof Tween + */ + public remove(ids: string[]): Tween { if (Array.isArray(ids)) { ids.forEach((id) => { diff --git a/packages/eva/__tests__/index.test.ts b/packages/eva/__tests__/index.test.ts index 99786de..b1b181b 100644 --- a/packages/eva/__tests__/index.test.ts +++ b/packages/eva/__tests__/index.test.ts @@ -1,58 +1,48 @@ import { Eva } from '../src/index'; -describe('Success cases', () => { - test('It should return a viewport width different from 0', () => { - // Use modern in order to run lodash debounce function - jest.useFakeTimers('modern'); +const eva = new Eva(); - // Change the viewport width to 0px. - global.innerWidth = 0; +// Use modern in order to run lodash debounce function +jest.useFakeTimers('modern'); - const eva = new Eva(); - eva.initialize(); +beforeEach(() => { + // Init before each test + eva.initialize(); +}); +afterEach(() => { + // Resetting Alice instance values before next test. + global.innerWidth = 0; + global.dispatchEvent(new Event('resize')); + eva.destroy(); +}); + +describe('Success cases', () => { + test('It should return a viewport width different from 0', () => { // Change the viewport width to 1000px. global.innerWidth = 1000; // Trigger the window resize event. global.dispatchEvent(new Event('resize')); // Run debounce function after resize - jest.runAllTimers(); + jest.runOnlyPendingTimers(); expect(eva.view.data.width).toStrictEqual(1000); }); test('It should return a viewport height different from 0', () => { - // Use modern in order to run lodash debounce function - jest.useFakeTimers('modern'); - - // Change the viewport height to 0px. - global.innerHeight = 0; - - const eva = new Eva(); - eva.initialize(); - // Change the viewport height to 1000px. global.innerHeight = 1000; // Trigger the window resize event. global.dispatchEvent(new Event('resize')); // Run debounce function after resize - jest.runAllTimers(); + jest.runOnlyPendingTimers(); expect(eva.view.data.height).toStrictEqual(1000); }); test('It should dampen the resize class addition during consecutive resize events', () => { - // Use modern in order to run lodash debounce function - jest.useFakeTimers('modern'); - - // Change the viewport width to 0px. - global.innerWidth = 0; - - const eva = new Eva(); - eva.initialize(); - // Change the viewport width to 1000px. global.innerWidth = 1000; // Trigger the window resize event. @@ -61,17 +51,12 @@ describe('Success cases', () => { global.dispatchEvent(new Event('resize')); // Run debounce function after resize - jest.runAllTimers(); + jest.runOnlyPendingTimers(); expect(eva.view.data.width).toStrictEqual(1000); }); test('It should not take the resize event into consideration', () => { - // Change the viewport width to 0px. - global.innerWidth = 0; - - const eva = new Eva(); - eva.initialize(); eva.destroy(); // Change the viewport width to 1000px. diff --git a/packages/eva/src/index.ts b/packages/eva/src/index.ts index c39093c..26231f0 100644 --- a/packages/eva/src/index.ts +++ b/packages/eva/src/index.ts @@ -2,39 +2,48 @@ import debounce from 'lodash/debounce'; import { Calvin } from '@endgame/calvin'; export class Eva { + /** + * @description The resize end delay in ms. + * @static + * @memberof Eva + */ + + static _resizeEndDelay = 200; + /** * @description Object allowing the use of reactive data. - * @private + * Storing default window values before any resize event. + * @static * @type {Calvin} * @memberof Eva */ - private _reactor: Calvin; + static _reactor: Calvin = new Calvin({ width: 0, height: 0 }); /** * @description Boolean used to allow elements resize transitions dampening. * @private * @memberof Eva */ + private _dampeningTransitions = false; /** - * @description The resize end delay in ms. + * @description Boolean ensuring that we can't initialize multiple resize listeners. * @private * @memberof Eva */ - private _resizeEndDelay = 200; + + private _isInitialized = false; /** * Creates an instance of Eva. * @author Alphability * @memberof Eva */ + constructor() { this._resizeEventHandler = this._resizeEventHandler.bind(this); - - // Store default window values before any resize event - this._reactor = new Calvin({ width: 0, height: 0 }); } /** @@ -43,9 +52,10 @@ export class Eva { * @private * @memberof Eva */ + private _collectWindowValues(): void { - this._reactor.data.width = window.innerWidth; - this._reactor.data.height = window.innerHeight; + Eva._reactor.data.width = window.innerWidth; + Eva._reactor.data.height = window.innerHeight; } /** @@ -54,6 +64,7 @@ export class Eva { * @private * @memberof Eva */ + private _resizeEventHandler(): void { this._dampTransitions(); this._resizeEnd(); @@ -66,6 +77,7 @@ export class Eva { * @returns {void} * @memberof Eva */ + private _dampTransitions(): void { if (this._dampeningTransitions) { return; @@ -80,13 +92,14 @@ export class Eva { * @private * @memberof Eva */ + private _resizeEnd = debounce(() => { this._collectWindowValues(); document.documentElement.classList.remove('resizing'); this._dampeningTransitions = false; - }, this._resizeEndDelay); + }, Eva._resizeEndDelay); /** * @description Hooks onto the resize event. @@ -94,6 +107,7 @@ export class Eva { * @private * @memberof Eva */ + private _attachListeners(): void { window.addEventListener('resize', this._resizeEventHandler, { passive: true, @@ -106,6 +120,7 @@ export class Eva { * @private * @memberof Eva */ + private _detachListeners(): void { // ⚡ Avoid memory leak window.removeEventListener('resize', this._resizeEventHandler, false); @@ -116,7 +131,16 @@ export class Eva { * @author Alphability * @memberof Eva */ + public initialize(): void { + // No multiple init + // Avoid having multiple listeners at the same time. + if (this._isInitialized) { + return; + } + + this._isInitialized = true; + // Register the resize event this._attachListeners(); @@ -131,6 +155,8 @@ export class Eva { */ public destroy(): void { this._detachListeners(); + + this._isInitialized = false; } /** @@ -140,6 +166,6 @@ export class Eva { * @memberof Eva */ get view(): Calvin { - return this._reactor; + return Eva._reactor; } }