js/domreplay/eventbaseclass.js

import Storage from './storage';
import { ProgrammingError } from './error';
import { trail, tracker } from './domhound';
import { stateIsRecord } from './state'
import Logger from './logger';

/**
 * Extend this when implementing custom events that should be recorded/replayed.
 * @access public
 */
export default class EventBaseClass {
	static DOM_REPLAY_BORDER_CLASS = 'dom-replay-border';

	constructor() {
		if (new.target === EventBaseClass) {
			throw new TypeError('Cannot construct EventBaseClass instances directly');
		}
		this._trailFunc = trail;
		this._trackerFunc = tracker;
		this._replayTiming = 1000;
		this._replaySpeed = 1.0;
	}

	/**
	 * Replay speed.
	 * 2.0 is twice as fast.
	 * @param divider		- higher is faster, lower is slower
	 */
	set replaySpeed(divider) {
		this._replaySpeed = divider;
	}

	/**
	 * Sets the replay timing variable.
	 * @param {Number} ms 		- time in milliseconds.
	 */
	set timing(ms) {
		this._replayTiming = ms;
	}

	/**
	 * Gets the timing variable
	 * @returns {number} gets the timing.
	 */
	get timing() {
		return this._replayTiming / this._replaySpeed;
	}

	/**
	 * Executes function after timeout
	 * @param func 							- function to call
	 * @param {float} ratio 		- relative to timing.
	 * @param args 							- arguments to be passed to the passed function.
	 * @returns {Promise} 			- resolves with function return value after function has been executed.
	 */
	executeTimingRelative(func, ratio = 1.0, ...args) {
		return new Promise(resolve => {
			setTimeout(() => {
				const retval = func(...args);
				resolve(retval);
			}, this.timing * ratio);
		});
	}

	/**
	 * Should return a string with event type, needs to be overridden by subclass.
	 * @abstract
	 */
	get eventType() {
		throw new ProgrammingError('Please override get eventType function to return a event type');
	}

	/**
	 * Should return a list og tagnames which handlers should be subscribed to.
	 * @abstract
	 */
	get tagnames() {
		throw new ProgrammingError('Please override get tagnames function to return list of tagnames');
	}

	/**
	 * add border to element.
	 * @param {HTMLElement} element 	- HTML element.
	 */
	addDomReplayBorderToElement(element) {
		element.classList.add(EventBaseClass.DOM_REPLAY_BORDER_CLASS);
	}

	/**
	 * remove border from element.
	 * @param {HTMLElement} element		- HTML element.
	 */
	removeDomReplayBorderFromElement(element) {
		element.classList.remove(EventBaseClass.DOM_REPLAY_BORDER_CLASS);
	}

	/**
	 * track element in HTML DOM by trail.
	 * @param {Object} trail
	 * @returns {*}
	 */
	trackElementOnTrail(trail) {
		return this._trackerFunc(trail);
	}

	/**
	 * make trail for element in HTML DOM.
	 * @param {HTMLElement} element
	 * @returns {*}
	 */
	makeTrailForElement(element) {
		return this._trailFunc(element);
	}

	/**
	 * Set trail function
	 * @param {function} trailFunc
	 */
	set trailFunc(trailFunc) {
		this._trailFunc = trailFunc;
	}

	/**
	 * set tracker function.
	 * @param {function} trackerFunc
	 */
	set trackerFunc(trackerFunc) {
		this._trackerFunc = trackerFunc;
	}

	/**
	 * This is handler called when event is dispatched.
	 * Should be overridden by subclass and store event to storage.
	 * @abstract
	 * @param {HTMLElement} element
	 */
	handler(element) {
		throw new ProgrammingError('Not Implemented Error, please implement handler function');
	}

	/**
	 * This is the function subscribed to the event.
	 * This ensures only to call this.handler when the framework is in record state.
	 * @param {HTMLElement} element
	 * @access private
	 */
	_handler(element) {
		if (stateIsRecord()) {
			Logger.debug(`handling ${this.eventType} event on ${element}`);
			this.handler(element);
		}
	}

	/**
	 * This should be overridden by subclass, and should do the correct
	 * replay sequence for the event.
	 * @abstract
	 * @param {Object} eventObject
	 */
	replay(eventObject) {
		throw new ProgrammingError('Not implemented Error, please implement replay function');
	}

	/**
	 * Gets the last stored event from storage.
	 * @returns {Object} an object of the last stored event.
	 */
	getLastStored() {
		return Storage.getLastStored();
	}

	/**
	 * Merge update the last stored event, useful when recording input events and such.
	 * @param {Object} updates 	- update object.
	 */
	updateLastStored(updates) {
		return Storage.updateLastStored(updates);
	}

	/**
	 * Should be called when event is about to be stored.
	 * @param {Object} eventObject
	 * @returns {*|Promise<object>}
	 */
	store(eventObject) {
		return Storage.store(this.eventType, eventObject)
			.catch(error => {
				throw error;
			});
	}

	/**
	 * waits for the promise returned by this.store to be resolved.
	 * @param {Object} eventObject
	 * @returns {Promise<object>}
	 */
	async syncStore(eventObject) {
		return await this.store(eventObject);
	}
}