var H5P = H5P || {}; /** * The Event class for the EventDispatcher. * * @class * @param {string} type * @param {*} data * @param {Object} [extras] * @param {boolean} [extras.bubbles] * @param {boolean} [extras.external] */ H5P.Event = function(type, data, extras) { this.type = type; this.data = data; var bubbles = false; // Is this an external event? var external = false; // Is this event scheduled to be sent externally? var scheduledForExternal = false; if (extras === undefined) { extras = {}; } if (extras.bubbles === true) { bubbles = true; } if (extras.external === true) { external = true; } /** * Prevent this event from bubbling up to parent */ this.preventBubbling = function() { bubbles = false; }; /** * Get bubbling status * * @returns {boolean} * true if bubbling false otherwise */ this.getBubbles = function() { return bubbles; }; /** * Try to schedule an event for externalDispatcher * * @returns {boolean} * true if external and not already scheduled, otherwise false */ this.scheduleForExternal = function() { if (external && !scheduledForExternal) { scheduledForExternal = true; return true; } return false; }; }; /** * Callback type for event listeners. * * @callback H5P.EventCallback * @param {H5P.Event} event */ H5P.EventDispatcher = (function () { /** * The base of the event system. * Inherit this class if you want your H5P to dispatch events. * * @class * @memberof H5P */ function EventDispatcher() { var self = this; /** * Keep track of listeners for each event. * * @private * @type {Object} */ var triggers = {}; /** * Add new event listener. * * @throws {TypeError} * listener must be a function * @param {string} type * Event type * @param {H5P.EventCallback} listener * Event listener * @param {Object} [thisArg] * Optionally specify the this value when calling listener. */ this.on = function (type, listener, thisArg) { if (typeof listener !== 'function') { throw TypeError('listener must be a function'); } // Trigger event before adding to avoid recursion self.trigger('newListener', {'type': type, 'listener': listener}); var trigger = {'listener': listener, 'thisArg': thisArg}; if (!triggers[type]) { // First triggers[type] = [trigger]; } else { // Append triggers[type].push(trigger); } }; /** * Add new event listener that will be fired only once. * * @throws {TypeError} * listener must be a function * @param {string} type * Event type * @param {H5P.EventCallback} listener * Event listener * @param {Object} thisArg * Optionally specify the this value when calling listener. */ this.once = function (type, listener, thisArg) { if (!(listener instanceof Function)) { throw TypeError('listener must be a function'); } var once = function (event) { self.off(event.type, once); listener.call(this, event); }; self.on(type, once, thisArg); }; /** * Remove event listener. * If no listener is specified, all listeners will be removed. * * @throws {TypeError} * listener must be a function * @param {string} type * Event type * @param {H5P.EventCallback} listener * Event listener */ this.off = function (type, listener) { if (listener !== undefined && !(listener instanceof Function)) { throw TypeError('listener must be a function'); } if (triggers[type] === undefined) { return; } if (listener === undefined) { // Remove all listeners delete triggers[type]; self.trigger('removeListener', type); return; } // Find specific listener for (var i = 0; i < triggers[type].length; i++) { if (triggers[type][i].listener === listener) { triggers[type].splice(i, 1); self.trigger('removeListener', type, {'listener': listener}); break; } } // Clean up empty arrays if (!triggers[type].length) { delete triggers[type]; } }; /** * Try to call all event listeners for the given event type. * * @private * @param {string} Event type */ var call = function (type, event) { if (triggers[type] === undefined) { return; } // Clone array (prevents triggers from being modified during the event) var handlers = triggers[type].slice(); // Call all listeners for (var i = 0; i < handlers.length; i++) { var trigger = handlers[i]; var thisArg = (trigger.thisArg ? trigger.thisArg : this); trigger.listener.call(thisArg, event); } }; /** * Dispatch event. * * @param {string|H5P.Event} event * Event object or event type as string * @param {*} [eventData] * Custom event data(used when event type as string is used as first * argument). * @param {Object} [extras] * @param {boolean} [extras.bubbles] * @param {boolean} [extras.external] */ this.trigger = function (event, eventData, extras) { if (event === undefined) { return; } if (event instanceof String || typeof event === 'string') { event = new H5P.Event(event, eventData, extras); } else if (eventData !== undefined) { event.data = eventData; } // Check to see if this event should go externally after all triggering and bubbling is done var scheduledForExternal = event.scheduleForExternal(); // Call all listeners call.call(this, event.type, event); // Call all * listeners call.call(this, '*', event); // Bubble if (event.getBubbles() && self.parent instanceof H5P.EventDispatcher && (self.parent.trigger instanceof Function || typeof self.parent.trigger === 'function')) { self.parent.trigger(event); } if (scheduledForExternal) { H5P.externalDispatcher.trigger.call(this, event); } }; } return EventDispatcher; })();