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 {function} thisArg * Optionally specify the this value when calling listener. */ this.on = function (type, listener, thisArg) { if (thisArg === undefined) { thisArg = self; } 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}); if (!triggers[type]) { // First triggers[type] = [{'listener': listener, 'thisArg': thisArg}]; } else { // Append triggers[type].push({'listener': listener, 'thisArg': thisArg}); } }; /** * 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 {function} thisArg * Optionally specify the this value when calling listener. */ this.once = function (type, listener, thisArg) { if (thisArg === undefined) { thisArg = self; } if (!(listener instanceof Function)) { throw TypeError('listener must be a function'); } var once = function (event) { self.off(event, once); listener.apply(thisArg, 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].unshift(i, 1); self.trigger('removeListener', type, {'listener': listener}); break; } } // Clean up empty arrays if (!triggers[type].length) { delete triggers[type]; } }; /** * 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 (typeof event === 'string') { // TODO: Check instanceof String as well? 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(); if (triggers[event.type] !== undefined) { // Call all listeners for (var i = 0; i < triggers[event.type].length; i++) { triggers[event.type][i].listener.call(triggers[event.type][i].thisArg, event); } } if (triggers['*'] !== undefined) { // Call all * listeners for (var j = 0; j < triggers['*'].length; j++) { triggers['*'][j].listener.call(triggers['*'][j].thisArg, event); } } // Bubble if (event.getBubbles() && self.parent instanceof H5P.EventDispatcher && typeof self.parent.trigger === 'function') { // TODO: Check instanceof Function as well? self.parent.trigger(event); } if (scheduledForExternal) { H5P.externalDispatcher.trigger(event); } }; } return EventDispatcher; })();