From 18a3ed4a8f4c63d6cac45c055836190d833b6c32 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Mon, 9 Feb 2015 10:22:14 +0100 Subject: [PATCH 01/75] Simplified integration code to avoid to much duplication. Added resizing script for new embed code. Added iframe communicator when embedding. --- js/h5p-api.js | 0 js/h5p-resizer.js | 155 ++++++++++++++++++++++++++++ js/h5p.js | 252 +++++++++++++++++++++++++++++++++------------- 3 files changed, 337 insertions(+), 70 deletions(-) create mode 100644 js/h5p-api.js create mode 100644 js/h5p-resizer.js diff --git a/js/h5p-api.js b/js/h5p-api.js new file mode 100644 index 0000000..e69de29 diff --git a/js/h5p-resizer.js b/js/h5p-resizer.js new file mode 100644 index 0000000..86cb280 --- /dev/null +++ b/js/h5p-resizer.js @@ -0,0 +1,155 @@ +// H5P iframe Resizer +(function () { + if (!window.postMessage || !window.addEventListener) { + return; // Not supported + } + + // Map actions to handlers + var actionHandlers = {}; + + /** + * Prepare iframe resize. + * + * @private + * @param {Object} iframe Element + * @param {Object} data Payload + * @param {Function} respond Send a response to the iframe + */ + actionHandlers.hello = function (iframe, data, respond) { + // Make iframe responsive + iframe.style.width = '100%'; + + // Tell iframe that it needs to resize when our window resizes + var resize = function (event) { + if (iframe.contentWindow) { + // Limit resize calls to avoid flickering + respond('resize'); + } + else { + // Frame is gone, unregister. + window.removeEventListener('resize', resize); + } + }; + window.addEventListener('resize', resize, false); + + // Respond to let the iframe know we can resize it + respond('hello'); + }; + + /** + * Prepare iframe resize. + * + * @private + * @param {Object} iframe Element + * @param {Object} data Payload + * @param {Function} respond Send a response to the iframe + */ + actionHandlers.prepareResize = function (iframe, data, respond) { + responseData = {}; + + // Retain parent size to avoid jumping/scrolling + responseData.parentHeight = iframe.parentElement.style.height; + //iframe.parentElement.style.height = iframe.parentElement.clientHeight + 'px'; + + // Reset iframe height, in case content has shrinked. + iframe.style.height = '1px'; + + respond('resizePrepared', responseData); + }; + + /** + * Resize parent and iframe to desired height. + * + * @private + * @param {Object} iframe Element + * @param {Object} data Payload + * @param {Function} respond Send a response to the iframe + */ + actionHandlers.resize = function (iframe, data, respond) { + // Resize iframe so all content is visible. + iframe.style.height = data.height + 'px'; + + // Free parent + //iframe.parentElement.style.height = data.parentHeight; + }; + + /** + * Keyup event handler. Exits full screen on escape. + * + * @param {Event} event + */ + var escape = function (event) { + if (event.keyCode === 27) { + exitFullScreen(); + } + }; + + /** + * Enter semi full screen. + * Expands the iframe so that it covers the whole page. + * + * @private + * @param {Object} iframe Element + * @param {Object} data Payload + * @param {Function} respond Send a response to the iframe + */ + actionHandlers.fullScreen = function (iframe, data, respond) { + iframe.style.position = 'fixed'; + iframe.style.top = iframe.style.left = 0; + iframe.style.zIndex = 101; + iframe.style.width = iframe.style.height = '100%'; + document.body.addEventListener('keyup', escape, false); + respond('fullScreen'); + }; + + /** + * Exit semi full screen. + * + * @private + * @param {Object} iframe Element + * @param {Object} data Payload + * @param {Function} respond Send a response to the iframe + */ + actionHandlers.exitFullScreen = function (iframe, data, respond) { + iframe.style.position = ''; + iframe.style.top = iframe.style.left = ''; + iframe.style.zIndex = ''; + iframe.style.width = '100%'; + iframe.style.height = ''; + document.body.removeEventListener('keyup', escape, false); + respond('exitFullScreen'); + }; + + + // Listen for messages from iframes + window.addEventListener('message', function receiveMessage(event) { + if (event.data.context !== 'h5p') { + return; // Only handle h5p requests. + } + + // Find out who sent the message + var iframe, iframes = document.getElementsByTagName('iframe'); + for (var i = 0; i < iframes.length; i++) { + if (iframes[i].contentWindow === event.source) { + iframe = iframes[i]; + break; + } + } + + if (!iframe) { + return; // Cannot find sender + } + + // Find action handler handler + if (actionHandlers[event.data.action]) { + actionHandlers[event.data.action](iframe, event.data, function (action, data) { + if (data === undefined) { + data = {}; + } + data.action = action; + data.context = 'h5p'; + event.source.postMessage(data, event.origin); + }); + } + }, false); +})(); diff --git a/js/h5p.js b/js/h5p.js index d53f8e2..8ff166d 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -31,6 +31,8 @@ else if (document.documentElement.msRequestFullscreen) { // Keep track of when the H5Ps where started H5P.opened = {}; +H5P.canHasFullScreen = (H5P.isFramed && H5P.externalEmbed !== false) ? (document.fullscreenEnabled || document.webkitFullscreenEnabled || document.mozFullScreenEnabled || document.msFullscreenEnabled) : true; + /** * Initialize H5P content. * Scans for ".h5p-content" in the document and initializes H5P instances where found. @@ -39,15 +41,12 @@ H5P.init = function () { // Useful jQuery object. H5P.$body = H5P.jQuery(document.body); - // Prepare internal resizer for content. - var $window = H5P.jQuery(window.parent); - // H5Ps added in normal DIV. var $containers = H5P.jQuery(".h5p-content").each(function () { var $element = H5P.jQuery(this); var $container = H5P.jQuery('
').appendTo($element); var contentId = $element.data('content-id'); - var contentData = H5PIntegration.getContentData(contentId); + var contentData = H5P.contentDatas['cid-' + contentId]; if (contentData === undefined) { return H5P.error('No data for content id ' + contentId + '. Perhaps the library is gone?'); } @@ -85,7 +84,7 @@ H5P.init = function () { H5P.openEmbedDialog($actions, contentData.embedCode); }); } - if (H5PIntegration.showH5PIconInActionBar()) { + if (contentData.showH5PIconInActionBar) { H5P.jQuery('
  • ').appendTo($actions); } $actions.insertAfter($container); @@ -100,48 +99,98 @@ H5P.init = function () { } }); - if (H5P.isFramed) { - // Make it possible to resize the iframe when the content changes size. This way we get no scrollbars. - var iframe = window.parent.document.getElementById('h5p-iframe-' + contentId); - var resizeIframe = function () { - if (window.parent.H5P.isFullscreen) { - return; // Skip if full screen. - } + if (H5P.isFramed) + var resizeDelay;{ + if (H5P.externalEmbed === false) { + // Internal embed + // Make it possible to resize the iframe when the content changes size. This way we get no scrollbars. + var iframe = window.parent.document.getElementById('h5p-iframe-' + contentId); + var resizeIframe = function () { + if (window.parent.H5P.isFullscreen) { + return; // Skip if full screen. + } - // Retain parent size to avoid jumping/scrolling - var parentHeight = iframe.parentElement.style.height; - iframe.parentElement.style.height = iframe.parentElement.clientHeight + 'px'; + // Retain parent size to avoid jumping/scrolling + var parentHeight = iframe.parentElement.style.height; + iframe.parentElement.style.height = iframe.parentElement.clientHeight + 'px'; - // Reset iframe height, in case content has shrinked. - iframe.style.height = '1px'; + // Reset iframe height, in case content has shrinked. + iframe.style.height = '1px'; - // Resize iframe so all content is visible. - iframe.style.height = (iframe.contentDocument.body.scrollHeight) + 'px'; + // Resize iframe so all content is visible. + iframe.style.height = (iframe.contentDocument.body.scrollHeight) + 'px'; - // Free parent - iframe.parentElement.style.height = parentHeight; - }; + // Free parent + iframe.parentElement.style.height = parentHeight; + }; - var resizeDelay; - instance.$.on('resize', function () { - // Use a delay to make sure iframe is resized to the correct size. - clearTimeout(resizeDelay); - resizeDelay = setTimeout(function () { - resizeIframe(); - }, 1); - }); + instance.$.on('resize', function () { + // Use a delay to make sure iframe is resized to the correct size. + clearTimeout(resizeDelay); + resizeDelay = setTimeout(function () { + resizeIframe(); + }, 1); + }); + } + else if (H5P.communicator) { + // External embed + var parentIsFriendly = false; + + // Handle hello message from our parent window + H5P.communicator.on('hello', function () { + // Initial setup/handshake is done + parentIsFriendly = true; + + // Hide scrollbars for correct size + document.body.style.overflow = 'hidden'; + + H5P.communicator.send('prepareResize'); + }); + + // When resize has been prepared tell parent window to resize + H5P.communicator.on('resizePrepared', function (data) { + H5P.communicator.send('resize', { + height: document.body.scrollHeight, + parentHeight: data.parentHeight + }); + }); + + H5P.communicator.on('resize', function () { + instance.$.trigger('resize'); + }); + + instance.$.on('resize', function () { + if (H5P.isFullscreen) { + return; // Skip iframe resize + } + + // Use a delay to make sure iframe is resized to the correct size. + clearTimeout(resizeDelay); + resizeDelay = setTimeout(function () { + // Only resize if the iframe can be resized + if (parentIsFriendly) { + H5P.communicator.send('prepareResize'); + } + else { + H5P.communicator.send('hello'); + } + }, 0); + }); + } } - // Resize everything when window is resized. - $window.resize(function () { - if (window.parent.H5P.isFullscreen) { - // Use timeout to avoid bug in certain browsers when exiting fullscreen. Some browser will trigger resize before the fullscreenchange event. + if (!H5P.isFramed || H5P.externalEmbed === false) { + // Resize everything when window is resized. + H5P.jQuery(window.top).resize(function () { + if (window.parent.H5P.isFullscreen) { + // Use timeout to avoid bug in certain browsers when exiting fullscreen. Some browser will trigger resize before the fullscreenchange event. + instance.$.trigger('resize'); + } + else { instance.$.trigger('resize'); - } - else { - instance.$.trigger('resize'); - } - }); + } + }); + } // Resize content. instance.$.trigger('resize'); @@ -151,11 +200,69 @@ H5P.init = function () { H5P.jQuery("iframe.h5p-iframe").each(function () { var contentId = H5P.jQuery(this).data('content-id'); this.contentDocument.open(); - this.contentDocument.write('' + H5PIntegration.getHeadTags(contentId) + '
    '); + this.contentDocument.write('' + H5P.getHeadTags(contentId) + '
    '); this.contentDocument.close(); + this.contentWindow.H5P = { + externalEmbed: false + }; }); }; +H5P.communicator = (function () { + /** + * @class + */ + function Communicator() { + var self = this; + + // Maps actions to functions + var actionHandlers = {}; + + // Register message listener + window.addEventListener('message', function receiveMessage(event) { + if (window.parent !== event.source || event.data.context !== 'h5p') { + return; // Only handle messages from parent and in the correct context + } + + if (actionHandlers[event.data.action] !== undefined) { + actionHandlers[event.data.action](event.data); + } + } , false); + + + /** + * Register action listener. + * + * @public + * @param {String} action What you are waiting for + * @param {Function} handler What you want done + */ + self.on = function (action, handler) { + actionHandlers[action] = handler; + }; + + /** + * Send a message to the all mighty father. + * + * @public + * @param {String} action + * @param {Object} [data] payload + */ + self.send = function (action, data) { + if (data === undefined) { + data = {}; + } + data.context = 'h5p'; + data.action = action; + + // Parent origin can be anything + window.parent.postMessage(data, '*'); + }; + } + + return (window.postMessage && window.addEventListener ? new Communicator() : undefined); +})(); + /** * Enable full screen for the given h5p. * @@ -166,9 +273,19 @@ H5P.init = function () { * @returns {undefined} */ H5P.fullScreen = function ($element, instance, exitCallback, body) { - if (H5P.isFramed) { + if (H5P.exitFullScreen !== undefined) { + return; // Cannot enter new fullscreen until previous is over + } + + if (H5P.isFramed && H5P.externalEmbed === false) { // Trigger resize on wrapper in parent window. - window.parent.H5P.fullScreen($element, instance, exitCallback, H5P.$body.get()); + window.top.H5P.fullScreen($element, instance, exitCallback, H5P.$body.get()); + H5P.isFullscreen = true; + H5P.exitFullScreen = function () { + window.top.H5P.exitFullScreen(); + H5P.isFullscreen = false; + H5P.exitFullScreen = undefined; + }; return; } @@ -226,6 +343,7 @@ H5P.fullScreen = function ($element, instance, exitCallback, body) { instance.$.trigger('resize'); instance.$.trigger('focus'); + H5P.exitFullScreen = undefined; if (exitCallback !== undefined) { exitCallback(); } @@ -235,6 +353,10 @@ H5P.fullScreen = function ($element, instance, exitCallback, body) { if (H5P.fullScreenBrowserPrefix === undefined) { // Create semi fullscreen. + if (H5P.isFramed) { + return; // TODO: Ask parent for iframe + } + before('h5p-semi-fullscreen'); var $disable = H5P.jQuery('
    ').appendTo($container.find('.h5p-content-controls')); var keyup, disableSemiFullscreen = function () { @@ -277,6 +399,19 @@ H5P.fullScreen = function ($element, instance, exitCallback, body) { var params = (H5P.fullScreenBrowserPrefix === 'webkit' && H5P.safariBrowser === 0 ? Element.ALLOW_KEYBOARD_INPUT : undefined); $element[0][method](params); } + + // Allows everone to exit + H5P.exitFullScreen = function () { + if (H5P.fullScreenBrowserPrefix === '') { + document.exitFullscreen(); + } + else if (H5P.fullScreenBrowserPrefix === 'moz') { + document.mozCancelFullScreen(); + } + else { + document[H5P.fullScreenBrowserPrefix + 'ExitFullscreen'](); + } + }; } }; @@ -300,7 +435,7 @@ H5P.getPath = function (path, contentId) { } if (contentId !== undefined) { - prefix = H5PIntegration.getContentPath(contentId); + prefix = H5P.url + '/content/' + contentId; } else if (window.H5PEditor !== undefined) { prefix = H5PEditor.filesPath; @@ -316,18 +451,6 @@ H5P.getPath = function (path, contentId) { return prefix + '/' + path; }; -/** - * THIS FUNCTION IS DEPRECATED, USE getPath INSTEAD - * - * Find the path to the content files folder based on the id of the content - * - * @param contentId - * Id of the content requesting a path - */ -H5P.getContentPath = function (contentId) { - return H5PIntegration.getContentPath(contentId); -}; - /** * Get library class constructor from H5P by classname. * Note that this class will only work for resolve "H5P.NameWithoutDot". @@ -428,15 +551,15 @@ H5P.t = function (key, vars, ns) { ns = 'H5P'; } - if (H5PIntegration.i18n[ns] === undefined) { + if (H5P.l10n[ns] === undefined) { return '[Missing translation namespace "' + ns + '"]'; } - if (H5PIntegration.i18n[ns][key] === undefined) { + if (H5P.l10n[ns][key] === undefined) { return '[Missing translation "' + key + '" in "' + ns + '"]'; } - var translation = H5PIntegration.i18n[ns][key]; + var translation = H5P.l10n[ns][key]; if (vars !== undefined) { // Replace placeholder with variables. @@ -946,7 +1069,7 @@ H5P.libraryFromString = function (library) { * @returns {String} The full path to the library. */ H5P.getLibraryPath = function (library) { - return H5PIntegration.getLibraryPath(library); + return H5P.url + '/libraries/' + library; }; /** @@ -1088,14 +1211,3 @@ if (String.prototype.trim === undefined) { return H5P.trim(this); }; } - -// Finally, we want to run init when document is ready. -// TODO: Move to integration. Systems like Moodle using YUI cannot get its translations set before this starts! -if (H5P.jQuery) { - H5P.jQuery(document).ready(function () { - if (!H5P.initialized) { - H5P.initialized = true; - H5P.init(); - } - }); -} From 11d9cb2f35d44e7b759dc40b44bdb6ce41c9b37e Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Mon, 9 Feb 2015 11:47:33 +0100 Subject: [PATCH 02/75] Trigger events. --- js/h5p.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/js/h5p.js b/js/h5p.js index 8ff166d..9567fee 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -327,6 +327,7 @@ H5P.fullScreen = function ($element, instance, exitCallback, body) { // Do not rely on window resize events. instance.$.trigger('resize'); instance.$.trigger('focus'); + instance.$.trigger('enterFullScreen'); }; /** @@ -347,6 +348,8 @@ H5P.fullScreen = function ($element, instance, exitCallback, body) { if (exitCallback !== undefined) { exitCallback(); } + + instance.$.trigger('exitFullScreen'); }; H5P.isFullscreen = true; From a0c5fe0e1808ff97e96b5e553b8999502419f04b Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Mon, 9 Feb 2015 11:51:39 +0100 Subject: [PATCH 03/75] Comment out semi full screen. --- js/h5p-resizer.js | 70 +++++++++++++++++++++++------------------------ js/h5p.js | 2 +- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/js/h5p-resizer.js b/js/h5p-resizer.js index 86cb280..54ec5d8 100644 --- a/js/h5p-resizer.js +++ b/js/h5p-resizer.js @@ -84,41 +84,41 @@ } }; - /** - * Enter semi full screen. - * Expands the iframe so that it covers the whole page. - * - * @private - * @param {Object} iframe Element - * @param {Object} data Payload - * @param {Function} respond Send a response to the iframe - */ - actionHandlers.fullScreen = function (iframe, data, respond) { - iframe.style.position = 'fixed'; - iframe.style.top = iframe.style.left = 0; - iframe.style.zIndex = 101; - iframe.style.width = iframe.style.height = '100%'; - document.body.addEventListener('keyup', escape, false); - respond('fullScreen'); - }; - - /** - * Exit semi full screen. - * - * @private - * @param {Object} iframe Element - * @param {Object} data Payload - * @param {Function} respond Send a response to the iframe - */ - actionHandlers.exitFullScreen = function (iframe, data, respond) { - iframe.style.position = ''; - iframe.style.top = iframe.style.left = ''; - iframe.style.zIndex = ''; - iframe.style.width = '100%'; - iframe.style.height = ''; - document.body.removeEventListener('keyup', escape, false); - respond('exitFullScreen'); - }; + // /** + // * Enter semi full screen. + // * Expands the iframe so that it covers the whole page. + // * + // * @private + // * @param {Object} iframe Element + // * @param {Object} data Payload + // * @param {Function} respond Send a response to the iframe + // */ + // actionHandlers.fullScreen = function (iframe, data, respond) { + // iframe.style.position = 'fixed'; + // iframe.style.top = iframe.style.left = 0; + // iframe.style.zIndex = 101; + // iframe.style.width = iframe.style.height = '100%'; + // document.body.addEventListener('keyup', escape, false); + // respond('fullScreen'); + // }; + // + // /** + // * Exit semi full screen. + // * + // * @private + // * @param {Object} iframe Element + // * @param {Object} data Payload + // * @param {Function} respond Send a response to the iframe + // */ + // actionHandlers.exitFullScreen = function (iframe, data, respond) { + // iframe.style.position = ''; + // iframe.style.top = iframe.style.left = ''; + // iframe.style.zIndex = ''; + // iframe.style.width = '100%'; + // iframe.style.height = ''; + // document.body.removeEventListener('keyup', escape, false); + // respond('exitFullScreen'); + // }; // Listen for messages from iframes diff --git a/js/h5p.js b/js/h5p.js index 9567fee..95a7728 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -357,7 +357,7 @@ H5P.fullScreen = function ($element, instance, exitCallback, body) { // Create semi fullscreen. if (H5P.isFramed) { - return; // TODO: Ask parent for iframe + return; // TODO: Should we support semi-fullscreen for IE9 & 10 ? } before('h5p-semi-fullscreen'); From 971a55df585135729e3b2fa71b0249414eb098ed Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Fri, 20 Feb 2015 10:26:33 +0100 Subject: [PATCH 04/75] Satisfy JSHint and prevent crashing. --- js/h5p-x-api-event.js | 27 +++++++++++++-------------- js/h5p-x-api.js | 12 ++++++------ js/h5p.js | 6 +++--- 3 files changed, 22 insertions(+), 23 deletions(-) diff --git a/js/h5p-x-api-event.js b/js/h5p-x-api-event.js index db87477..346bf64 100644 --- a/js/h5p-x-api-event.js +++ b/js/h5p-x-api-event.js @@ -2,7 +2,7 @@ var H5P = H5P || {}; /** * Constructor for xAPI events - * + * * @class */ H5P.XAPIEvent = function() { @@ -14,7 +14,7 @@ H5P.XAPIEvent.prototype.constructor = H5P.XAPIEvent; /** * Helperfunction to set scored result statements - * + * * @param {int} score * @param {int} maxScore */ @@ -30,7 +30,7 @@ H5P.XAPIEvent.prototype.setScoredResult = function(score, maxScore) { /** * Helperfunction to set a verb. - * + * * @param {string} verb * Verb in short form, one of the verbs defined at * http://adlnet.gov/expapi/verbs/ @@ -45,14 +45,13 @@ H5P.XAPIEvent.prototype.setVerb = function(verb) { }; } else { - console.log('illegal verb'); + H5P.error('illegal verb'); } - // Else: Fail silently... }; /** * Helperfunction to get the statements verb id - * + * * @param {boolean} full * if true the full verb id prefixed by http://adlnet.gov/expapi/verbs/ will be returned * @returns {string} - Verb or null if no verb with an id has been defined @@ -68,13 +67,13 @@ H5P.XAPIEvent.prototype.getVerb = function(full) { else { return null; } -} +}; /** * Helperfunction to set the object part of the statement. - * + * * The id is found automatically (the url to the content) - * + * * @param {object} instance - the H5P instance */ H5P.XAPIEvent.prototype.setObject = function(instance) { @@ -109,7 +108,7 @@ H5P.XAPIEvent.prototype.setActor = function() { /** * Get the max value of the result - score part of the statement - * + * * @returns {int} the max score, or null if not defined */ H5P.XAPIEvent.prototype.getMaxScore = function() { @@ -118,7 +117,7 @@ H5P.XAPIEvent.prototype.getMaxScore = function() { /** * Get the raw value of the result - score part of the statement - * + * * @returns {int} the max score, or null if not defined */ H5P.XAPIEvent.prototype.getScore = function() { @@ -127,7 +126,7 @@ H5P.XAPIEvent.prototype.getScore = function() { /** * Figure out if a property exists in the statement and return it - * + * * @param {array} keys * List describing the property we're looking for. For instance * ['result', 'score', 'raw'] for result.score.raw @@ -146,7 +145,7 @@ H5P.XAPIEvent.prototype.getVerifiedStatementValue = function(keys) { /** * List of verbs defined at http://adlnet.gov/expapi/verbs/ - * + * * @type Array */ H5P.XAPIEvent.allowedXAPIVerbs = [ @@ -175,4 +174,4 @@ H5P.XAPIEvent.allowedXAPIVerbs = [ 'suspended', 'terminated', 'voided' -]; \ No newline at end of file +]; diff --git a/js/h5p-x-api.js b/js/h5p-x-api.js index 77b9357..37a593b 100644 --- a/js/h5p-x-api.js +++ b/js/h5p-x-api.js @@ -11,7 +11,7 @@ if (window.top !== window.self && window.top.H5P !== undefined && window.top.H5P /** * Helper function for triggering xAPI added to the EventDispatcher - * + * * @param {string} verb - the short id of the verb we want to trigger * @param {oject} extra - extra properties for the xAPI statement */ @@ -21,10 +21,10 @@ H5P.EventDispatcher.prototype.triggerXAPI = function(verb, extra) { /** * Helper function to create event templates added to the EventDispatcher - * + * * Will in the future be used to add representations of the questions to the * statements. - * + * * @param {string} verb - verb id in short form * @param {object} extra - Extra values to be added to the statement * @returns {Function} - XAPIEvent object @@ -55,11 +55,11 @@ H5P.EventDispatcher.prototype.triggerXAPICompleted = function(score, maxScore) { var event = this.createXAPIEventTemplate('completed'); event.setScoredResult(score, maxScore); this.trigger(event); -} +}; /** * Internal H5P function listening for xAPI completed events and stores scores - * + * * @param {function} event - xAPI event */ H5P.xAPICompletedListener = function(event) { @@ -72,4 +72,4 @@ H5P.xAPICompletedListener = function(event) { H5P.setFinished(contentId, score, maxScore); } } -}; \ No newline at end of file +}; diff --git a/js/h5p.js b/js/h5p.js index bdbdff1..9d40789 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -548,7 +548,7 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize) { * @returns {undefined} */ H5P.error = function (err) { - if (window['console'] !== undefined && console.error !== undefined) { + if (window.console !== undefined && console.error !== undefined) { console.error(err); } }; @@ -1244,7 +1244,7 @@ H5P.trigger = function(instance, eventType) { } // Try deprecated event system else if (instance.$ !== undefined && instance.$.trigger !== undefined) { - instance.$.trigger(eventType) + instance.$.trigger(eventType); } }; @@ -1268,6 +1268,6 @@ H5P.on = function(instance, eventType, handler) { } // Try deprecated event system else if (instance.$ !== undefined && instance.$.on !== undefined) { - instance.$.on(eventType, handler) + instance.$.on(eventType, handler); } }; From f76290cddee0b9c7ba1f180821927fd36aa1b4b0 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Fri, 20 Feb 2015 11:41:09 +0100 Subject: [PATCH 05/75] Made user results work in new embed. --- js/h5p-event-dispatcher.js | 4 ++-- js/h5p-x-api-event.js | 7 +++---- js/h5p-x-api.js | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/js/h5p-event-dispatcher.js b/js/h5p-event-dispatcher.js index 204fda9..3df9495 100644 --- a/js/h5p-event-dispatcher.js +++ b/js/h5p-event-dispatcher.js @@ -11,7 +11,7 @@ H5P.Event = function(type, data) { }; H5P.EventDispatcher = (function () { - + /** * The base of the event system. * Inherit this class if you want your H5P to dispatch events. @@ -152,4 +152,4 @@ H5P.EventDispatcher = (function () { } return EventDispatcher; -})(); \ No newline at end of file +})(); diff --git a/js/h5p-x-api-event.js b/js/h5p-x-api-event.js index 346bf64..5e1a025 100644 --- a/js/h5p-x-api-event.js +++ b/js/h5p-x-api-event.js @@ -79,7 +79,7 @@ H5P.XAPIEvent.prototype.getVerb = function(full) { H5P.XAPIEvent.prototype.setObject = function(instance) { if (instance.contentId) { this.data.statement.object = { - 'id': H5PIntegration.getContentUrl(instance.contentId), + 'id': H5P.contentDatas['cid-' + instance.contentId].url, 'objectType': 'Activity', 'extensions': { 'http://h5p.org/x-api/h5p-local-content-id': instance.contentId @@ -98,10 +98,9 @@ H5P.XAPIEvent.prototype.setObject = function(instance) { * Helper function to set the actor, email and name will be added automatically */ H5P.XAPIEvent.prototype.setActor = function() { - var user = H5PIntegration.getUser(); this.data.statement.actor = { - 'name': user.name, - 'mbox': 'mailto:' + user.mail, + 'name': H5P.user.name, + 'mbox': 'mailto:' + H5P.user.mail, 'objectType': 'Agent' }; }; diff --git a/js/h5p-x-api.js b/js/h5p-x-api.js index 37a593b..e4f4edb 100644 --- a/js/h5p-x-api.js +++ b/js/h5p-x-api.js @@ -3,7 +3,7 @@ var H5P = H5P || {}; // Create object where external code may register and listen for H5P Events H5P.externalDispatcher = new H5P.EventDispatcher(); -if (window.top !== window.self && window.top.H5P !== undefined && window.top.H5P.externalDispatcher !== undefined) { +if (H5P.isFramed && H5P.externalEmbed === false) { H5P.externalDispatcher.on('xAPI', window.top.H5P.externalDispatcher.trigger); } From 152b51f02a380e3caaa544cc41d3bdbf8255998d Mon Sep 17 00:00:00 2001 From: Thomas Marstrander Date: Fri, 20 Feb 2015 13:00:50 +0100 Subject: [PATCH 06/75] Added new font --- fonts/h5p.eot | Bin 5956 -> 4396 bytes fonts/h5p.svg | 26 ++++---------------------- fonts/h5p.ttf | Bin 5764 -> 4248 bytes fonts/h5p.woff | Bin 0 -> 4324 bytes styles/h5p.css | 12 ++++++------ 5 files changed, 10 insertions(+), 28 deletions(-) create mode 100644 fonts/h5p.woff diff --git a/fonts/h5p.eot b/fonts/h5p.eot index 9680263b5d3282bcb9f66238f410cba90a4604ec..b71c1c89c2063e98f8fed4cfe33539915dd2c74a 100644 GIT binary patch literal 4396 zcmeHLdu$xV8K3vuKJKyh-L0M2KKstTb4YT&+{*=$*ba$X5?j0+Xa%U45OT!{vEm>l zsI)1Fw?w0oKrEF|3RJXeQU7TxwJC}KAyHar5f!Bpl~607s!&m-7F6m>f3tgb>;SFQ zzq{I*`QG2mH#;-m>^hH-6A3~nVPxe)$x&)36PK=}8`tkq)1BOoYewi9600%;iPoQ6+`8Cn|ub8 zQz+N%+BZG>?kjKK4xujKQ+uZ8jH?82(?<5}J$&N_dv}ZjKSYT8eq;Ca4f3|TF}PR* z{CWd~NJRV#__u)fH})MobaATv1n}p9e{S#oUDN*eQV~MpF7W)m=|i)mo5p}wf%o1# zy>IuLn{xL9pMsCR+5K||(N8=H_``s>AoM7ags^$`OHjTZw)8KgLM^Q7qJ%;=9#{*hh}ZD>f$OvW{~K+Ae84%o*NmMOv`-84gLlhQ2H==kw3^zlN@BSF_ud9Fu#_|=0|9~oS|v8mZmoITD8b>>{#{MZH1ot_H|cT zLf$3g>P{)FtJZC=X9l-jTVtnDSBqC{S#P~;t0%R7QrRzC>$hA{gxVkm!RZO|EI-Dc zL3}z%|5<;P@@5uz(3MoK)}E_XYAC5nialghjoI2YroE;%I5qXvr_1HL2H7(PT_l@! z!bC2z-)72t2fDZBOH(8L{UcK)v;%%> zPC&#Jmrsuyf$?cGXEq%qr-WobBQBO%j;tpakgeo0ay7XQOO8;LuK4Nt5Uu9pbhwbu z#;HnX=s;ejv`UZAGFbj_y*fksCULnx3HStMqn=f$Ha?2ZAM_aabEQ%_=)WcCa`r(H! zoje)Fv77u{xa_%4iDNxKgyEFunPlj?v|jL1;5UXp8h?QBS5T!<*^ysB^E+ z>!Tq0i&04NwmWR@*eir z1&=+uFvE_st6|Lj6k99(esqpxV5;SHr6k?8@K2_i1^WFac1I%EYVtK6V`r9PNrGqu zUtjt{T1X{Pm+PA-H(yk+MDlB?U#p_ds&2|7TFCt(D)vjE-+9#06>XnfPC99WY~OEz65^54Cx1 zmSwF@=%{{Ryua4wc{Guj)*W81;Q3N1rJKJ`bm~3D8*V*x=+@&=-?HOMSz?dwOr$cI z6q}AGCN~Mq81>q`b*tL@J+I5+Odm}o66?FW*2NDid8Jl5ZWPye(fF?)f4q6*=uvHH zov^HQG#*W4(&-ElG)o?Vp+A#4xrzLVI*3HNHEe9e1-uwcKAS@nR0x|u;PrzDU^6gi zJ?%@B9;#78{{jlw0Ac)Miy?unYM>to`h(Cga%;XUfUl`#yVW3DVO*LqIn0VAL@$sN4S^sFsoR*m=wo%@Tx?_iA(gOcPzsM zWij-r_o7}LuF*7O1KS6%;0641#a7o=SqW`A*rp3GvO2|>&UM0Gth$P0r8p{eUP8ws z%F*;Y9mS5$4V{dkCzP?iZCS?or5d|3Q;u>y`UTITKVXb`N#h-F@#-R#mK0!nb|k@U z-{k^5cYT*q6;lv-B_dtBBf)Jq+UZgrbL@64BNDI8oOHR<>-BWpR*uwh9hC_L?6q!J zB!XdUft>|?FvCu}8kSuLvFsb^Z8l zVee2A6G~-eOifH0T*}GGkEa*SA3-7DK^J0^lgn^pYcf2m{(t7LOf~+uGoR5pe1<<} zUm;z@%zF>*rb#t~6Jiss)-l0pgSRp!b7qH(OiK`qd??61&!yyV3OsKbXkLhU+QzAv zKZi~)W0ts3a3rXfC<~iC#5iz^&EU&!G??NyAxFAUFm@bm8GFbv#^eQP%`HaI!{jCY zL-s^)u5Kh3n{RdL0L}HCJwW{|x{ckfq_DSdC$kUgy(4{PODbrlaMA7kG_1P~G{ari|nQHDO6f8vYrjejSOFsq9E#(nIL zE-4T k8O%S2X>t(l4WM054x_%9?1I!Jiun^!G(j@A>i_Wk8|tYvasU7T literal 5956 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 - - -{ - "fontFamily": "h5p-core-fonts", - "majorVersion": 1, - "minorVersion": 0, - "fontURL": "http://h5p.org", - "license": "MIT license", - "licenseURL": "http://opensource.org/licenses/MIT", - "designer": "Magnus Vik Magnussen", - "designerURL": "", - "version": "Version 1.0", - "fontId": "h5p-core-fonts", - "psName": "h5p-core-fonts", - "subFamily": "Regular", - "fullName": "h5p-core-fonts", - "description": "Generated by IcoMoon" -} - - +Generated by IcoMoon - + + @@ -37,6 +19,6 @@ - + \ No newline at end of file diff --git a/fonts/h5p.ttf b/fonts/h5p.ttf index c0ffaf53e54a700855bfb88cab9c7a6c48155c71..a9bc2fd7d17905a79d61db7f73637000b054d644 100644 GIT binary patch literal 4248 zcmeHKdyHIF89(oHU-P)TGjn&_-JRW;-I*%L`xNklgm~{ZZ|lSe=qPE*ch1Kzi>lGgE$V<4>2tTtJWn^x&_{0B`AfQKC!zE~@;twvohEtkSIAY zpDT>hMkPx#YAsEV@p`Sq^6Y5snytmY#owAXLGyacgfc*W^)@-#ono4|fs+BPoTF}*WzTTOySL>+h zYMMP@M2*_|)uz9?K0Gt?l_x8euMM-O47xOI`VE^mx9+SyTz$C;Ta4aCe##$Wzb1WT zEg?h2F&bKEdswYBMuSWRKGupuxkA0xs8%xRl*;pmZcYV*<2y&nJI98G#&(v6w)QLt zSLB3fy}b3h>$aNu&dt4B3gwyc!NKvFGWww)y&%BiiOXiE48zo{Swx2plT$*npA(N{ z5a0FW0mAz3M$cks z>)P>gW?Rp3)i%qUTPE7NwlA?NzC6PTI`RJdFPuCX#j%(CLU`<%Pl#iqFar0}K_5LQ zGFaY1UdQRm$RM=YFtjE5k*KFK)zLA!GV0tP@CPWWPv7NC%u9qqMzcg5_#}OeOX^Z3 z<^_XCVIUno36C(MKv~J$D#Cjymsz_^An#^>TlCq}i*xK4y9&y@PtbL?-4D*;3{|zQ zp3O;jE&hwCR+0Xoh0~D;wwipE$Jv?XSduUr;oq0JkQUP^w3Wst%FQ1YZjr)T8q{lO zbE=neOAEPQ#Ke9n^ji^k0?}IO0 zEzx3BM*AqlF#EhHCd>WDz8AMMw(VQJBOQK+ZQHApI;QWN8mxEtK20WPb(fzn`hiqR z>DC{T-FkQF`dbeky!BWtu$_cbw%Dz^lj&?W&1MtH=}kg2M*R+d-Kx$(-|w+G(?^oY zyN!dmhyrH{xjdqvBG?21B?u#c!@!`8OdwJFs74F<3n*X%MDdF+g#@~4p?t_3 zV38Z6QMB4xAQhGn4egaeV_1!Fyb5C*+uCq)8pBH>03cNiQsoVqlrfBb8Mc6pjb#s@ zBH)o!r5s@RJemi+pb|LXj5-VfA1yZmp(&%uLf~M%JViOJ+*qZ8b_{iP7CM}`CI7CH{d}!G-Dv zdSZhX_wznh6?+$p;s!2?N?LA0VjlgIStclpV@~}S_2Y7lWf&XUHiQi?V4o|ty1vRz zYBRwOU4)X=X~uNE8~Wn4RUA9TRq6ARI$nNqGN>tiXfb2U~k~x9L1!nFA9;GU-!19tMJ*O+l9WU1HQ66`l zPAx5o;xIQ;>Gu15op6*ZbwbBv5&?Uq*AvOmZ7p!JU=GIZjHjX5a}mpd@qVtQmI8Bt z-)K8y+&Erf5`M#|V4AI}ueK64{NMu}m`qFsz1SXIy-?7dwh=dJGsjNAtnwgZSxIg9 zF)=seP)dKMV+!U~Oegv}f9D~)!9D&Ua)xh)Im7uMlQDE=Qn)sABiI>e3bk?kLugFu zG8*PDAO}(6QZ#7<5*YOLyQR#@gq+@?;y63hjvY$nWL)i-Hn^12(;rPQT0e$BNP{fc zrl(inM%Q$7SN;FaKRea<-|l=y7w{YYgngOx5VP)mw3nvT2yTc?wAR1^XAIuXn9Q9W z5wdMY5DF0^`y7{&zb^2-ZlDuFoS+??iWBFI(@U8xE)*OIsx2zQVGl44+-7t5vztw( z_)W->E)*`aMbxOO=y-qyt+g&l=I8jZDF3ioP{oDsmqz>awf{&n$xgq|bXt!@lo5d7 zI2(su#1eJngA&k>7fc+_ADp)?i*z=5-Z)kO|*tM6NLdVrs?9DdOme zx~Mf`(7Y(u&<9bEquzx&jM^*Lu+`A=2H+g3p_}mq)I+GRh8i5aVfsj!+(^Df{zz|U z!)zb>yErUniMuhjLry-bVhKbGlCoIm6IfV9NvI|_(s786o#3Fph8{p%={{R43Cqn=L literal 5764 zcmeHLdyJIT6~E^`zi%Gj%#DXp8V?1?aa@Y7y{=tS>#?l_DJM43q2v)Dh+}jvukUcMqRBdFm9#z|p&RyEdKrWBw~03^##-7y&BbsDxvn-TsVQ z^31ybWsDwXzY^c!C)hF&EC$>ZM7~lgR;hI{4O1<*mZZ{Z{wqRi@uZLvg_hzKf5qsg zYGd2F0Ve1OdrlnY&q5A8Y{_XJ>a}5BX;pd2)J>_9F171zy!m>bKLiFUZiQxJ+uCJB z%hqmdEZa<*=$0STN~+TxR3ZNCz`Cu?=GJusbM?y6(b+rdkJaC(GfsE0r^TcEWmaaZ z8CzO$TdM>juM51L8|LkNf!po#a%HKm@^%?R5KAB`;?bFCBBqE+BUcX%Zn+4OC||T? zFukO>IB@`*l%ih{37UPqIDCFDQSbS~#nOd?Jw!c&7nc4y6ZG{j?OM5Mq+}^PR>Siq@dsk4Y1=<@t?CU*2n5>4ZE6M$8Kb|v%A?hAvwK}a`ij} zH%L{k6V$F$sfaq@(BjfYIiS4BWeQYp*M_M)NcncF(Lxm83P7e=4#fhde?fAAb1a(W6#xi`Xw@j6Z)4y@gsR z2i!_>6+~?EwVV|-46s>InN?O7jVe!&E^4T+2#ZLNNUO9WylMJ?lcX<^ny)vUYUm9T6QlGN z)HWx{b7*Z&l5+Rh=scy(4xCn+!U~f3#3iFp8su#0kK9B2eL9)s&!4=3_+jFohjK$5 zJ8;o~&N*&IE)%EOv_=N&n#bk8K;0S+%m)e&*|hBUD4{#11tz66E!fd%HZ3zFblu61 zh#Q(6Ao?NAZe~pKUUsVp_^H#nV-%jA&e?*>R#PRLDe%>p;1`j=336UDJULbP&U_Qv zJI{L8MI<~}7l~=EwC2;`Pl~w-rP-1Lq$I`)@a$7aSH%9CjRX zUNn{FJSYX2O1qM+G%G23mNjz95@6ha}8{Hsf%^69`X7gf_mG1K;|gB#>%>53@4&Prk2Uou!l-s*NU|gR@DflW(ZDfUTvu(1cTvl z?^ri0LNEE2ID@W4WGOT<}?6Ct`6#bvP3f zkANg617C?CWjr4pj+BNJjMGLQm53)~Jd;gFbr6j!8%4m7fcarPVNzWQ&H2N5P%ag8S85i zvkr>)F6fOz79N}mVp^ih$1>Vz5ltteZnLp;!YYy|miBFI#1AXMvV@Fa2gXS3z+l1y z86!hlZpV;V6xk>}BLM&#hpH{4NhDMLZ@{d(25{u~&xr?Ij=cf?C(QCsXV_`D5G09k z6ocgag-suk&mntsNr<}yw9niH63A~lw}A5ctvwJ@uJwc8taVS|E~peMtxxLiU@ zq8}oZeB(w$b1wUZ#<9En3IKRivU-+dW1q~_XMe&Cq*Jt#1SZO&H3GDq)QXU=KpSPz z4Q$-_|J)dI6FgNBjgCp}({x!CoA>AT)e3J_Zc`sr~rG;>V8wVni9pzb{+ZH*&8+3Wdm~?!5;>;oBTV z>o(!->`L2^2tz_z+&4$nHpEPas8Su`_PYnY3F%(Nzsefuz%w;YUt;HOyp%x-4r{Sh zcso6Pm9@b9ohj?ehlG!OZO@dehV01Hyz!|$^Tz4K6f#&}4#Ks|sVQ3yx&gF5eH^S= zUK#t-uG$@6NL=`L7L^!a?9~@Sw>(!PGq>M*?AM?00gnBK#F)Z28JNd? z_Z_smJNM470__QKum`*x_>Yj6I$kU%9)Ur;*+_-WO@ E0f&Tg>;M1& diff --git a/fonts/h5p.woff b/fonts/h5p.woff new file mode 100644 index 0000000000000000000000000000000000000000..66394c10c29d618c76bc5a15fffdc7f78a306179 GIT binary patch literal 4324 zcmeHKdyHIF89(27&b{-zyEAil+wRWp%)1B3)oP=Y~BFd-7aD3O>bMBJ&rbMNf3TOcO> z?alqp`QG0--?`_U?|$K;$w@{GPwI6R4f^9DZSm#LMVAg%7^4WdpR(Yxkk+!F!sq*gF>9-MwwHaox-f96b}Z>c*2iq8CL zh4gP}*#*L&Ax6+mE{TO0Bj*O8KZyU|c=POCA^2T(?o={9mFSt9rk|ya)AZ0yJAO$U zcQB&+*|Xwaewr1alTYwWx|{p8LcTag^-7MidJSbpMQymu3;gKt)muv4^{s0!cci*q zCG}U8bS_`JwVoT;a&?WLMmt=-bkjQLc~?Jb%%j?U-dVTl(lX2jFe^dF*^}ZZe**dG zwBNu&ylQ2X2N6s)Q>e8UYSkJbQ_b-Etf_TdyUO-g)dr@gzx;Tma{B;(!jcPY+ppiW zsrl9FgVh(Sh{fp+_EYfy|26AoYZ&V@j?~q_b5&TOXw1Vv%!~xdI{js zJA%vo3aL=xD3J#-L9Yl!QPOh1RP31_j7@wGhe`)6qeu3Ax-Ytqj7e{tO`(f6D76qkG@`-({LGmE)oK{kS~FMB?f zGHJAx`bHA=iwd?#aSi#kVYGSOMIvIP+9%_3pOWUy2cxEH+>=XLCta|jyb)1fQ+Okh z$BgTm$C{6w#jq#lYsBOvKgB3*-RUYp}MD^n(J z?wRbXwRs+;QZuH*E0jE6X{}B352;SGt9lf;S+8c;z4%RwLk75o|-t8%|Cec)#o;*=`-F4ili zNl@kbDoJYVZ*MQQxd|mAk>2r~n~92u$c^PlitLcj1vMjy6;YQW$qXuuC{D9zN=Rvd zSiHye2hxd-m^LI7zJYi#3fK3oiuXl?SMac^*t=Mis2|0vQ;}#=VH`aZIWCDOFs7ah zdT_bMvz+&D?Z<|fh%Yo>SzF>!YHF8#(;UFZI25(@gi54H-buT_=?)fNYcSPL~k1g6W4wh zwns-79CT(J8eEGTP$Qi$IX{NJf-~eO=juH44STQBA@-d3A%7&e zS2wT=?YFvkffjl{xIq0phK@b0l_-C6)|era zHoIrh3>h=qqGxxvXgWP>GG;K83<8D5avE`wOTgg!Cm#81a>GAZRN7s|ej9t}wxvC) z2Ky&v|E|IZHo-p{Q>!gQw*GuuQropH!2>gRo>!~se!y|S9e@Eqms-tN!pm#G^8hQi z{5ilOz{`Oj7ymHbY=~XYzRCVbxAFnLm;YTJmcLhDR4?fb8h^FHT-qqc+`wXCq)Ug9 zV2`){@QCS_12OR4zDU z9{LRN&}WE;K0{o?w!dW?`fRWo+s`h;pUaJGj(wi(V|!T-ThGeKLLh6@_J;_&1WLQu wO>8%tfpTa#85kUZ$u3Cu25x590rb~`cMUs)_CmH3T2lb)yd2KowjcZa2h12PrvLx| literal 0 HcmV?d00001 diff --git a/styles/h5p.css b/styles/h5p.css index fff975b..dfb92db 100644 --- a/styles/h5p.css +++ b/styles/h5p.css @@ -2,12 +2,12 @@ /* Custom H5P font to use for icons. */ @font-face { - font-family: 'H5P'; - src: url('../fonts/h5p.eot?ver=1.2.1'); - src: url('../fonts/h5p.eot?#iefix&ver=1.2.1') format('embedded-opentype'), - url('data:application/font-woff;base64,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') format('woff'), - url('../fonts/h5p.ttf?ver=1.2.1') format('truetype'), - url('../fonts/h5p.svg?ver=1.2.1#h5pregular') format('svg'); + font-family: 'h5p'; + src:url('../fonts/h5p.eot?-9qymee'); + src:url('../fonts/h5p.eot?#iefix-9qymee') format('embedded-opentype'), + url('../fonts/h5p.woff?-9qymee') format('woff'), + url('../fonts/h5p.ttf?-9qymee') format('truetype'), + url('../fonts/h5p.svg?-9qymee#h5p') format('svg'); font-weight: normal; font-style: normal; } From 93cd6eea2811da017b6cc8ef5dab3761bcb9a73a Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Fri, 20 Feb 2015 13:17:41 +0100 Subject: [PATCH 07/75] Removed empty file. --- js/h5p-api.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 js/h5p-api.js diff --git a/js/h5p-api.js b/js/h5p-api.js deleted file mode 100644 index e69de29..0000000 From d6e69e63ee69fcaef0fd2712e0d263d9d641d5b4 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Fri, 20 Feb 2015 15:34:38 +0100 Subject: [PATCH 08/75] Use this instead of self so that we're compatible with jsdoc --- js/h5p-event-dispatcher.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/js/h5p-event-dispatcher.js b/js/h5p-event-dispatcher.js index 204fda9..f403ea2 100644 --- a/js/h5p-event-dispatcher.js +++ b/js/h5p-event-dispatcher.js @@ -36,7 +36,7 @@ H5P.EventDispatcher = (function () { * @param {Function} listener - Event listener * @param {Function} thisArg - Optionally specify the this value when calling listener. */ - self.on = function (type, listener, thisArg) { + this.on = function (type, listener, thisArg) { if (thisArg === undefined) { thisArg = self; } @@ -66,7 +66,7 @@ H5P.EventDispatcher = (function () { * @param {Function} listener - Event listener * @param {Function} thisArg - Optionally specify the this value when calling listener. */ - self.once = function (type, listener, thisArg) { + this.once = function (type, listener, thisArg) { if (thisArg === undefined) { thisArg = self; } @@ -91,7 +91,7 @@ H5P.EventDispatcher = (function () { * @param {String} type - Event type * @param {Function} listener - Event listener */ - self.off = function (type, listener) { + this.off = function (type, listener) { if (listener !== undefined && !(listener instanceof Function)) { throw TypeError('listener must be a function'); } @@ -131,7 +131,7 @@ H5P.EventDispatcher = (function () { * Custom event data(used when event type as string is used as first * argument */ - self.trigger = function (event, eventData) { + this.trigger = function (event, eventData) { if (event === undefined) { return; } From 0c5aacfa241acd02e15145cbe8434a22110e66b8 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Tue, 24 Feb 2015 16:02:14 +0100 Subject: [PATCH 09/75] Fixed old fullscreen exit in iframe. --- js/h5p.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/js/h5p.js b/js/h5p.js index 9d40789..3042977 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -290,9 +290,11 @@ H5P.fullScreen = function ($element, instance, exitCallback, body) { H5P.isFullscreen = true; H5P.exitFullScreen = function () { window.top.H5P.exitFullScreen(); + }; + H5P.on(instance, 'exitFullScreen', function () { H5P.isFullscreen = false; H5P.exitFullScreen = undefined; - }; + }); return; } From 1861f696c1ac8d4157cdb2c5c0b8a2530c5318e3 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Wed, 25 Feb 2015 12:10:07 +0100 Subject: [PATCH 10/75] Added new embed design. --- js/h5p.js | 55 +++++++++++++++++++++-- styles/h5p.css | 119 +++++++++++++++++++++++++++++++++++++------------ 2 files changed, 141 insertions(+), 33 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index 3042977..5c26b6e 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -83,7 +83,10 @@ H5P.init = function () { if (contentData.embedCode !== undefined) { // Display embed button H5P.jQuery('
  • ' + H5P.t('embed') + '
  • ').appendTo($actions).click(function () { - H5P.openEmbedDialog($actions, contentData.embedCode); + H5P.openEmbedDialog($actions, contentData.embedCode, contentData.resizeCode, { + width: $container.width(), + height: $container.height() + }); }); } if (contentData.showH5PIconInActionBar) { @@ -713,12 +716,56 @@ H5P.findCopyrights = function (info, parameters, contentId) { * @param {string} embed code. * @returns {undefined} */ -H5P.openEmbedDialog = function ($element, embedCode) { - var dialog = new H5P.Dialog('embed', H5P.t('embed'), '', $element); +H5P.openEmbedDialog = function ($element, embedCode, resizeCode, size) { + var dialog = new H5P.Dialog('embed', H5P.t('embed'), '' + H5P.t('size') + ': × px
    ' + H5P.t('showAdvanced') + '

    ' + H5P.t('advancedHelp') + '

    ', $element); // Selecting embed code when dialog is opened H5P.jQuery(dialog).on('dialog-opened', function (event, $dialog) { - $dialog.find('.h5p-embed-code-container').select(); + // Handle changing of width/height + var $w = $dialog.find('.h5p-embed-size:eq(0)'); + var $h = $dialog.find('.h5p-embed-size:eq(1)'); + var getNum = function ($e, d) { + var num = parseFloat($e.val()); + if (isNaN(num)) { + return d; + } + return Math.ceil(num); + }; + var updateEmbed = function () { + $dialog.find('.h5p-embed-code-container:first').val(embedCode.replace(':w', getNum($w, size.width)).replace(':h', getNum($h, size.height))); + }; + + var w = size.width; + $w.change(function () { + // Keep aspect ratio when changing width + var newW = getNum($w, size.width); + $h.val(Math.ceil(newW * (getNum($h, size.height) / w))); + w = newW; + updateEmbed(); + }); + $h.change(updateEmbed); + updateEmbed(); + + // Select text and expand textareas + $dialog.find('.h5p-embed-code-container').focus(function () { + H5P.jQuery(this).select().css('height', this.scrollHeight + 'px'); + }).blur(function () { + H5P.jQuery(this).css('height', ''); + }).select(); + + // Expand advanced embed + $dialog.find('.h5p-expander').click(function () { + var $expander = H5P.jQuery(this); + var $content = $expander.next(); + if ($content.is(':visible')) { + $expander.removeClass('h5p-open').text(H5P.t('showAdvanced')); + $content.hide(); + } + else { + $expander.addClass('h5p-open').text(H5P.t('hideAdvanced')); + $content.show(); + } + }); }); dialog.open(); diff --git a/styles/h5p.css b/styles/h5p.css index fff975b..57f40cc 100644 --- a/styles/h5p.css +++ b/styles/h5p.css @@ -220,47 +220,80 @@ div.h5p-fullscreen { -moz-transition: opacity 0.2s; -o-transition: opacity 0.2s; transition: opacity 0.2s; + background:#000; + background:rgba(0,0,0,0.75); } .h5p-popup-dialog.h5p-open { opacity: 1; } .h5p-popup-dialog .h5p-inner { box-sizing: border-box; - box-shadow: 0 0 2em #000; + -moz-box-sizing: border-box; background: #fff; - height: 90%; + height: 100%; max-height: 100%; - padding: 0.75em; position: relative; } .h5p-popup-dialog .h5p-inner > h2 { - font-size: 1.5em; - margin: 0.25em 0; position: absolute; + box-sizing: border-box; + -moz-box-sizing: border-box; + width: 100%; + margin: 0; + background: #eee; + display: block; + color: #656565; + font-size: 1.25em; + padding: 0.325em 0.5em 0.25em; line-height: 1.25em; - padding: 0; + border-bottom: 1px solid #ccc; } .h5p-embed-dialog .h5p-inner { - width: 50%; - left: 25%; - top: 25%; - height: auto; + width: 300px; + left: 50%; + top: 50%; + height: 180px; + margin: -90px 0 0 -150px; } -.h5p-embed-dialog .h5p-embed-code-container { - width: 90%; - padding: .3em; - min-height: 10em; +.h5p-embed-dialog .h5p-embed-code-container, +.h5p-embed-size { resize: none; outline: none; + width: 100%; + padding: 0.375em 0.5em 0.25em; + margin: 0; + overflow: hidden; + border: 1px solid #ccc; + box-shadow: 0 1px 2px 0 #d0d0d0 inset; + font-size: 0.875em; + letter-spacing: 0.065em; + font-family: sans-serif; + white-space: pre; + line-height: 1.5em; + height: 2.0714em; + background: #f5f5f5; + box-sizing: border-box; + -moz-box-sizing: border-box; +} +.h5p-embed-dialog .h5p-embed-code-container:focus { + height: 5em; +} +.h5p-embed-size { + width: 3.5em; + text-align: right; + margin: 0.5em 0; + line-height: 2em; } .h5p-popup-dialog .h5p-scroll-content { - border-top: 2.75em solid transparent; + border-top: 2.25em solid transparent; + padding: 1em; box-sizing: border-box; -moz-box-sizing: border-box; height: 100%; overflow: auto; overflow-x: hidden; overflow-y: auto; + color: #555555; } .h5p-popup-dialog .h5p-scroll-content::-webkit-scrollbar { width: 8px; @@ -281,22 +314,21 @@ div.h5p-fullscreen { content: "\e894"; font-size: 2em; position: absolute; - right: 0.5em; - top: 0.5em; + right: 0; + top: 0; + width: 1.125em; + height: 1.125em; + line-height: 1.125em; + color: #656565; cursor: pointer; - -webkit-transition: -webkit-transform 0.2s; - -moz-transition: -moz-transform 0.2s; - -o-transition: -o-transform 0.2s; - transition: transform 0.2s; - -webkit-backface-visibility: hidden; - backface-visibility: hidden; + text-indent: -0.065em; } -.h5p-popup-dialog .h5p-close:hover:after { - -webkit-transform: scale(1.1, 1.1); - -moz-transform: scale(1.1, 1.1); - -ms-transform: scale(1.1, 1.1); - -o-transform: scale(1.1, 1.1); - transform: scale(1.1, 1.1); +.h5p-popup-dialog .h5p-close:hover:after, +.h5p-popup-dialog .h5p-close:focus:after { + color: #454545; +} +.h5p-popup-dialog .h5p-close:active:after { + color: #252525; } .h5p-poopup-dialog h2 { margin: 0.25em 0 0.5em; @@ -317,6 +349,35 @@ div.h5p-fullscreen { .h5p-popup-dialog dd { margin: 0; } +.h5p-expander { + cursor: default; + font-size: 1.125em; + outline: none; + margin: 0.5em 0 0; +} +.h5p-expander:before { + content: "+"; + width: 1em; + display: inline-block; + font-weight: bold; +} +.h5p-expander.h5p-open:before { + content: "-"; + text-indent: 0.125em; +} +.h5p-expander:hover, +.h5p-expander:focus { + color: #303030; +} +.h5p-expander:active { + color: #202020; +} +.h5p-expander-content { + display: none; +} +.h5p-expander-content p { + margin: 0.5em 0; +} .h5p-content-copyrights { border-left: 0.25em solid #d0d0d0; margin-left: 0.25em; From fabde2f1cea01544985f3cfaf92d27d8a7f17456 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Wed, 25 Feb 2015 13:05:45 +0100 Subject: [PATCH 11/75] Automatic positioning. --- js/h5p.js | 13 +++++++++++++ styles/h5p.css | 4 ++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index 5c26b6e..c44a55b 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -721,6 +721,16 @@ H5P.openEmbedDialog = function ($element, embedCode, resizeCode, size) { // Selecting embed code when dialog is opened H5P.jQuery(dialog).on('dialog-opened', function (event, $dialog) { + var $inner = $dialog.find('.h5p-inner'); + var positionInner = function () { + $inner.css('height', ''); + var h = $inner.height(); + if (Math.floor($dialog.height()) === h) { + $inner.css('height', '100%'); + } + $inner.css('marginTop', '-' + (h / 2) + 'px'); + }; + // Handle changing of width/height var $w = $dialog.find('.h5p-embed-size:eq(0)'); var $h = $dialog.find('.h5p-embed-size:eq(1)'); @@ -749,8 +759,10 @@ H5P.openEmbedDialog = function ($element, embedCode, resizeCode, size) { // Select text and expand textareas $dialog.find('.h5p-embed-code-container').focus(function () { H5P.jQuery(this).select().css('height', this.scrollHeight + 'px'); + positionInner(); }).blur(function () { H5P.jQuery(this).css('height', ''); + positionInner(); }).select(); // Expand advanced embed @@ -765,6 +777,7 @@ H5P.openEmbedDialog = function ($element, embedCode, resizeCode, size) { $expander.addClass('h5p-open').text(H5P.t('hideAdvanced')); $content.show(); } + positionInner(); }); }); diff --git a/styles/h5p.css b/styles/h5p.css index 57f40cc..1925892 100644 --- a/styles/h5p.css +++ b/styles/h5p.css @@ -252,8 +252,8 @@ div.h5p-fullscreen { width: 300px; left: 50%; top: 50%; - height: 180px; - margin: -90px 0 0 -150px; + height: auto; + margin: 0 0 0 -150px; } .h5p-embed-dialog .h5p-embed-code-container, .h5p-embed-size { From 3463a3c67165f7a445d094d0aa8f12891f9b8ab8 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Thu, 26 Feb 2015 16:16:20 +0100 Subject: [PATCH 12/75] Added support for multiple workers during content upgrade. --- js/h5p-content-upgrade-process.js | 275 ++++++++++++++++ js/h5p-content-upgrade-worker.js | 76 +++++ js/h5p-content-upgrade.js | 528 +++++++++++++----------------- js/h5p-version.js | 27 ++ 4 files changed, 599 insertions(+), 307 deletions(-) create mode 100644 js/h5p-content-upgrade-process.js create mode 100644 js/h5p-content-upgrade-worker.js create mode 100644 js/h5p-version.js diff --git a/js/h5p-content-upgrade-process.js b/js/h5p-content-upgrade-process.js new file mode 100644 index 0000000..a7cc2cc --- /dev/null +++ b/js/h5p-content-upgrade-process.js @@ -0,0 +1,275 @@ +/*jshint -W083 */ +var H5PUpgrades = H5PUpgrades || {}; + +H5P.ContentUpgradeProcess = (function (Version) { + + /** + * @class + * @namespace H5P + */ + function ContentUpgradeProcess(name, oldVersion, newVersion, params, id, loadLibrary, done) { + var self = this; + + // Make params possible to work with + try { + params = JSON.parse(params); + if (!(params instanceof Object)) { + throw true; + } + } + catch (event) { + return done({ + type: 'errorParamsBroken', + id: id + }); + } + + self.loadLibrary = loadLibrary; + self.upgrade(name, oldVersion, newVersion, params, function (err, result) { + if (err) { + return done(err); + } + + done(null, JSON.stringify(params)); + }); + } + + /** + * + */ + ContentUpgradeProcess.prototype.upgrade = function (name, oldVersion, newVersion, params, done) { + var self = this; + + // Load library details and upgrade routines + self.loadLibrary(name, newVersion, function (err, library) { + if (err) { + return done(err); + } + + // Run upgrade routines on params + self.processParams(library, oldVersion, newVersion, params, function (err, params) { + if (err) { + return done(err); + } + + // Check if any of the sub-libraries need upgrading + asyncSerial(library.semantics, function (index, field, next) { + self.processField(field, params[field.name], function (err, upgradedParams) { + if (upgradedParams) { + params[field.name] = upgradedParams; + } + next(err); + }); + }, function (err) { + done(err, params); + }); + }); + }); + }; + + /** + * Run upgrade hooks on params. + * + * @public + * @param {Object} library + * @param {Version} oldVersion + * @param {Version} newVersion + * @param {Object} params + * @param {Function} next + */ + ContentUpgradeProcess.prototype.processParams = function (library, oldVersion, newVersion, params, next) { + if (H5PUpgrades[library.name] === undefined) { + if (library.upgradesScript) { + // Upgrades script should be loaded so the upgrades should be here. + return next({ + type: 'scriptMissing', + library: library.name + ' ' + newVersion + }); + } + + // No upgrades script. Move on + return next(null, params); + } + + // Run upgrade hooks. Start by going through major versions + asyncSerial(H5PUpgrades[library.name], function (major, minors, nextMajor) { + if (major < oldVersion.major || major > newVersion.major) { + // Older than the current version or newer than the selected + nextMajor(); + } + else { + // Go through the minor versions for this major version + asyncSerial(minors, function (minor, upgrade, nextMinor) { + if (minor <= oldVersion.minor || minor > newVersion.minor) { + // Older than or equal to the current version or newer than the selected + nextMinor(); + } + else { + // We found an upgrade hook, run it + var unnecessaryWrapper = (upgrade.contentUpgrade !== undefined ? upgrade.contentUpgrade : upgrade); + + try { + unnecessaryWrapper(params, function (err, upgradedParams) { + params = upgradedParams; + nextMinor(err); + }); + } + catch (err) { + next(err); + } + } + }, nextMajor); + } + }, function (err) { + next(err, params); + }); + }; + + /** + * Process parameter fields to find and upgrade sub-libraries. + * + * @public + * @param {Object} field + * @param {Object} params + * @param {Function} done + */ + ContentUpgradeProcess.prototype.processField = function (field, params, done) { + var self = this; + + if (params === undefined) { + return done(); + } + + switch (field.type) { + case 'library': + if (params.library === undefined || params.params === undefined) { + return done(); + } + + // Look for available upgrades + var usedLib = params.library.split(' ', 2); + for (var i = 0; i < field.options.length; i++) { + var availableLib = field.options[i].split(' ', 2); + if (availableLib[0] === usedLib[0]) { + if (availableLib[1] === usedLib[1]) { + return done(); // Same version + } + + // We have different versions + var usedVer = new Version(usedLib[1]); + var availableVer = new Version(availableLib[1]); + if (usedVer.major > availableVer.major || (usedVer.major === availableVer.major && usedVer.minor >= availableVer.minor)) { + return done(); // Larger or same version that's available + } + + // A newer version is available, upgrade params + return self.upgrade(availableLib[0], usedVer, availableVer, params.params, function (err, upgraded) { + if (!err) { + params.library = availableLib[0] + ' ' + availableVer.major + '.' + availableVer.minor; + params.params = upgraded; + } + done(err, params); + }); + } + } + done(); + break; + + case 'group': + if (field.fields.length === 1) { + // Single field to process, wrapper will be skipped + self.processField(field.fields[0], params, function (err, upgradedParams) { + if (upgradedParams) { + params = upgradedParams; + } + done(err, params); + }); + } + else { + // Go through all fields in the group + asyncSerial(field.fields, function (index, subField, next) { + self.processField(subField, params[subField.name], function (err, upgradedParams) { + if (upgradedParams) { + params[subField.name] = upgradedParams; + } + next(err); + }); + }, function (err) { + done(err, params); + }); + } + break; + + case 'list': + // Go trough all params in the list + asyncSerial(params, function (index, subParams, next) { + self.processField(field.field, subParams, function (err, upgradedParams) { + if (upgradedParams) { + params[index] = upgradedParams; + } + next(err); + }); + }, function (err) { + done(err, params); + }); + break; + + default: + done(); + } + }; + + /** + * Helps process each property on the given object asynchronously in serial order. + * + * @private + * @param {Object} obj + * @param {Function} process + * @param {Function} finished + */ + var asyncSerial = function (obj, process, finished) { + var id, isArray = obj instanceof Array; + + // Keep track of each property that belongs to this object. + if (!isArray) { + var ids = []; + for (id in obj) { + if (obj.hasOwnProperty(id)) { + ids.push(id); + } + } + } + + var i = -1; // Keeps track of the current property + + /** + * Private. Process the next property + */ + var next = function () { + id = isArray ? i : ids[i]; + process(id, obj[id], check); + }; + + /** + * Private. Check if we're done or have an error. + * + * @param {String} err + */ + var check = function (err) { + // We need to use a real async function in order for the stack to clear. + setTimeout(function () { + i++; + if (i === (isArray ? obj.length : ids.length) || (err !== undefined && err !== null)) { + finished(err); + } + else { + next(); + } + }, 0); + }; + + check(); // Start + }; + + return ContentUpgradeProcess; +})(H5P.Version); diff --git a/js/h5p-content-upgrade-worker.js b/js/h5p-content-upgrade-worker.js new file mode 100644 index 0000000..8b5f4f5 --- /dev/null +++ b/js/h5p-content-upgrade-worker.js @@ -0,0 +1,76 @@ +var H5P = H5P || {}; +importScripts('/wp-content/plugins/h5p/h5p-php-library/js/h5p-version.js'); +importScripts('/wp-content/plugins/h5p/h5p-php-library/js/h5p-content-upgrade-process.js'); + +var libraryLoadedCallback; + +/** + * Register message handlers + */ +var messageHandlers = { + newJob: function (job) { + // Start new job + new H5P.ContentUpgradeProcess(job.name, new H5P.Version(job.oldVersion), new H5P.Version(job.newVersion), job.params, job.id, function loadLibrary(name, version, next) { + // TODO: Cache? + postMessage({ + action: 'loadLibrary', + name: name, + version: version.toString() + }); + libraryLoadedCallback = next; + }, function done(err, result) { + if (err) { + // Return error + postMessage({ + action: 'error', + id: job.id, + err: err + }); + + return; + } + + // Return upgraded content + postMessage({ + action: 'done', + id: job.id, + params: result + }); + }); + }, + libraryLoaded: function (data) { + var library = data.library; + if (library.upgradesScript) { + try { + importScripts(library.upgradesScript); + } + catch (err) { + libraryLoadedCallback(err); + return; + } + } + libraryLoadedCallback(null, data.library); + } +}; + +/** + * Handle messages from our master + */ +onmessage = function (event) { + if (event.data.action !== undefined && messageHandlers[event.data.action]) { + messageHandlers[event.data.action].call(this, event.data); + } +}; + + +// if (library.upgradesScript) { +// self.loadScript(library.upgradesScript, function (err) { +// if (err) { +// err = info.errorScript.replace('%lib', name + ' ' + version); +// } +// next(err, library); +// }); +// } +// else { +// next(null, library); +// } diff --git a/js/h5p-content-upgrade.js b/js/h5p-content-upgrade.js index ff3756f..e384f97 100644 --- a/js/h5p-content-upgrade.js +++ b/js/h5p-content-upgrade.js @@ -1,8 +1,7 @@ /*jshint -W083 */ -var H5PUpgrades = H5PUpgrades || {}; -(function ($) { - var info, $container, librariesCache = {}; +(function ($, Version) { + var info, $container, librariesCache = {}, scriptsCache = {}; // Initialize $(document).ready(function () { @@ -43,87 +42,6 @@ var H5PUpgrades = H5PUpgrades || {}; } }; - /** - * Private. Helps process each property on the given object asynchronously in serial order. - * - * @param {Object} obj - * @param {Function} process - * @param {Function} finished - */ - var asyncSerial = function (obj, process, finished) { - var id, isArray = obj instanceof Array; - - // Keep track of each property that belongs to this object. - if (!isArray) { - var ids = []; - for (id in obj) { - if (obj.hasOwnProperty(id)) { - ids.push(id); - } - } - } - - var i = -1; // Keeps track of the current property - - /** - * Private. Process the next property - */ - var next = function () { - id = isArray ? i : ids[i]; - process(id, obj[id], check); - }; - - /** - * Private. Check if we're done or have an error. - * - * @param {String} err - */ - var check = function (err) { - // We need to use a real async function in order for the stack to clear. - setTimeout(function () { - i++; - if (i === (isArray ? obj.length : ids.length) || (err !== undefined && err !== null)) { - finished(err); - } - else { - next(); - } - }, 0); - }; - - check(); // Start - }; - - /** - * Make it easy to keep track of version details. - * - * @param {String} version - * @param {Number} libraryId - * @returns {_L1.Version} - */ - function Version(version, libraryId) { - if (libraryId !== undefined) { - version = info.versions[libraryId]; - - // Public - this.libraryId = libraryId; - } - var versionSplit = version.split('.', 3); - - // Public - this.major = versionSplit[0]; - this.minor = versionSplit[1]; - - /** - * Public. Custom string for this object. - * - * @returns {String} - */ - this.toString = function () { - return version; - }; - } - /** * Displays a throbber in the status field. * @@ -154,18 +72,84 @@ var H5PUpgrades = H5PUpgrades || {}; var self = this; // Get selected version - self.version = new Version(null, libraryId); + self.version = new Version(info.versions[libraryId]); + self.version.libraryId = libraryId; // Create throbber with loading text and progress self.throbber = new Throbber(info.inProgress.replace('%ver', self.version)); - // Get the next batch - self.nextBatch({ - libraryId: libraryId, - token: info.token - }); +self.started = new Date().getTime(); +self.io = 0; + + // Track number of working + self.working = 0; + + var start = function () { + // Get the next batch + self.nextBatch({ + libraryId: libraryId, + token: info.token + }); + }; + + if (window.Worker !== undefined) { + // Prepare our workers + self.initWorkers(); + start(); + } + else { + // No workers, do the job our self + self.loadScript('/wp-content/plugins/h5p/h5p-php-library/js/h5p-content-upgrade-process.js', start); + } } + /** + * Initialize workers + */ + ContentUpgrade.prototype.initWorkers = function () { + var self = this; + + // Determine number of workers (defaults to 4) + var numWorkers = (window.navigator !== undefined && window.navigator.hardwareConcurrency ? window.navigator.hardwareConcurrency : 4); + self.workers = new Array(numWorkers); + + // Register message handlers + var messageHandlers = { + done: function (result) { + self.workDone(result.id, result.params, this); + }, + error: function (error) { + self.printError(error.err); + + // Stop everything + self.terminate(); + }, + loadLibrary: function (details) { + var worker = this; + self.loadLibrary(details.name, new Version(details.version), function (err, library) { + if (err) { + // Reset worker? + return; + } + + worker.postMessage({ + action: 'libraryLoaded', + library: library + }); + }); + } + }; + + for (var i = 0; i < numWorkers; i++) { + self.workers[i] = new Worker('/wp-content/plugins/h5p/h5p-php-library/js/h5p-content-upgrade-worker.js'); + self.workers[i].onmessage = function (event) { + if (event.data.action !== undefined && messageHandlers[event.data.action]) { + messageHandlers[event.data.action].call(this, event.data); + } + }; + } + }; + /** * Get the next batch and start processing it. * @@ -174,12 +158,21 @@ var H5PUpgrades = H5PUpgrades || {}; ContentUpgrade.prototype.nextBatch = function (outData) { var self = this; +var start = new Date().getTime(); $.post(info.infoUrl, outData, function (inData) { +self.io += new Date().getTime() - start; if (!(inData instanceof Object)) { // Print errors from backend return self.setStatus(inData); } if (inData.left === 0) { + var total = new Date().getTime() - self.started; + console.log('Upgrade took ' + total + 'ms'); + console.log((self.io/(total/100)) + ' % of the time went to IO (' + self.io + 'ms)'); + + // Terminate workers + self.terminate(); + // Nothing left to process return self.setStatus(info.done); } @@ -208,90 +201,125 @@ var H5PUpgrades = H5PUpgrades || {}; */ ContentUpgrade.prototype.processBatch = function (parameters) { var self = this; - var upgraded = {}; // Track upgraded params - var current = 0; // Track progress - asyncSerial(parameters, function (id, params, next) { + // Track upgraded params + self.upgraded = {}; - try { - // Make params possible to work with - params = JSON.parse(params); - if (!(params instanceof Object)) { - throw true; - } + // Track current batch + self.parameters = parameters; + + // Create id mapping + self.ids = []; + for (var id in parameters) { + if (parameters.hasOwnProperty(id)) { + self.ids.push(id); } - catch (event) { - return next(info.errorContent.replace('%id', id) + ' ' + info.errorParamsBroken); + } + + // Keep track of current content + self.current = -1; + + if (self.workers !== undefined) { + // Assign each worker content to upgrade + for (var i = 0; i < self.workers.length; i++) { + self.assignWork(self.workers[i]); } + } + else { - // Upgrade this content. - self.upgrade(info.library.name, new Version(info.library.version), self.version, params, function (err, params) { - if (err) { - return next(info.errorContent.replace('%id', id) + ' ' + err); - } - - upgraded[id] = JSON.stringify(params); - - current++; - self.throbber.setProgress(Math.round((info.total - self.left + current) / (info.total / 100)) + ' %'); - next(); - }); - - }, function (err) { - // Finished with all parameters that came in - if (err) { - return self.setStatus('

    ' + info.error + '
    ' + err + '

    '); - } - - // Save upgraded content and get next round of data to process - self.nextBatch({ - libraryId: self.version.libraryId, - token: self.token, - params: JSON.stringify(upgraded) - }); - }); + self.assignWork(); + } }; /** - * Upgade the given content. * - * @param {String} name - * @param {Version} oldVersion - * @param {Version} newVersion - * @param {Object} params - * @param {Function} next - * @returns {undefined} */ - ContentUpgrade.prototype.upgrade = function (name, oldVersion, newVersion, params, next) { + ContentUpgrade.prototype.assignWork = function (worker) { var self = this; - // Load library details and upgrade routines - self.loadLibrary(name, newVersion, function (err, library) { - if (err) { - return next(err); - } + var id = self.ids[self.current + 1]; + if (id === undefined) { + return false; // Out of work + } + self.current++; + self.working++; - // Run upgrade routines on params - self.processParams(library, oldVersion, newVersion, params, function (err, params) { + if (worker) { + worker.postMessage({ + action: 'newJob', + id: id, + name: info.library.name, + oldVersion: info.library.version, + newVersion: self.version.toString(), + params: self.parameters[id] + }); + } + else { + new H5P.ContentUpgradeProcess(info.library.name, new Version(info.library.version), self.version, self.parameters[id], id, function loadLibrary(name, version, next) { + self.loadLibrary(name, version, function (err, library) { + if (library.upgradesScript) { + self.loadScript(library.upgradesScript, function (err) { + if (err) { + err = info.errorScript.replace('%lib', name + ' ' + version); + } + next(err, library); + }); + } + else { + next(null, library); + } + }); + + }, function done(err, result) { if (err) { - return next(err); + self.printError(err); + return ; } - // Check if any of the sub-libraries need upgrading - asyncSerial(library.semantics, function (index, field, next) { - self.processField(field, params[field.name], function (err, upgradedParams) { - if (upgradedParams) { - params[field.name] = upgradedParams; - } - next(err); - }); - }, function (err) { - next(err, params); - }); + self.workDone(id, result); }); - }); + } }; + /** + * + */ + ContentUpgrade.prototype.workDone = function (id, result, worker) { + var self = this; + + self.working--; + self.upgraded[id] = result; + + // Update progress message + self.throbber.setProgress(Math.round((info.total - self.left + self.current) / (info.total / 100)) + ' %'); + + // Assign next job + if (self.assignWork(worker) === false && self.working === 0) { + // All workers have finsihed. + self.nextBatch({ + libraryId: self.version.libraryId, + token: self.token, + params: JSON.stringify(self.upgraded) + }); + } + }; + + /** + * + */ + ContentUpgrade.prototype.terminate = function () { + var self = this; + + if (self.workers) { + // Stop all workers + for (var i = 0; i < self.workers.length; i++) { + self.workers[i].terminate(); + } + } + }; + + var librariesLoadedCallbacks = {}; + /** * Load library data needed for content upgrade. * @@ -303,32 +331,42 @@ var H5PUpgrades = H5PUpgrades || {}; var self = this; var key = name + '/' + version.major + '/' + version.minor; - if (librariesCache[key] !== undefined) { + + if (librariesCache[key] === true) { + // Library is being loaded, que callback + if (librariesLoadedCallbacks[key] === undefined) { + librariesLoadedCallbacks[key] = [next]; + return; + } + librariesLoadedCallbacks[key].push(next); + return; + } + else if (librariesCache[key] !== undefined) { // Library has been loaded before. Return cache. next(null, librariesCache[key]); return; } +var start = new Date().getTime(); + librariesCache[key] = true; $.ajax({ dataType: 'json', cache: true, url: info.libraryBaseUrl + '/' + key }).fail(function () { +self.io += new Date().getTime() - start; next(info.errorData.replace('%lib', name + ' ' + version)); }).done(function (library) { +self.io += new Date().getTime() - start; librariesCache[key] = library; + next(null, library); - if (library.upgradesScript) { - self.loadScript(library.upgradesScript, function (err) { - if (err) { - err = info.errorScript.replace('%lib', name + ' ' + version); - } - next(err, library); - }); - } - else { - next(null, library); + if (librariesLoadedCallbacks[key] !== undefined) { + for (var i = 0; i < librariesLoadedCallbacks[key].length; i++) { + librariesLoadedCallbacks[key][i](null, library); + } } + delete librariesLoadedCallbacks[key]; }); }; @@ -339,162 +377,38 @@ var H5PUpgrades = H5PUpgrades || {}; * @param {Function} next */ ContentUpgrade.prototype.loadScript = function (url, next) { + if (scriptsCache[url] !== undefined) { + next(); + return; + } + +var start = new Date().getTime(); $.ajax({ dataType: 'script', cache: true, url: url }).fail(function () { +self.io += new Date().getTime() - start; next(true); }).done(function () { + scriptsCache[url] = true; +self.io += new Date().getTime() - start; next(); }); }; /** - * Run upgrade hooks on params. * - * @param {Object} library - * @param {Version} oldVersion - * @param {Version} newVersion - * @param {Object} params - * @param {Function} next */ - ContentUpgrade.prototype.processParams = function (library, oldVersion, newVersion, params, next) { - if (H5PUpgrades[library.name] === undefined) { - if (library.upgradesScript) { - // Upgrades script should be loaded so the upgrades should be here. - return next(info.errorScript.replace('%lib', library.name + ' ' + newVersion)); - } - - // No upgrades script. Move on - return next(null, params); + ContentUpgrade.prototype.printError = function (error) { + if (error.type === 'errorParamsBroken') { + error = info.errorContent.replace('%id', error.id) + ' ' + info.errorParamsBroken; // TODO: Translate! + } + else if (error.type === 'scriptMissing') { + error.err = info.errorScript.replace('%lib', error.library); } - // Run upgrade hooks. Start by going through major versions - asyncSerial(H5PUpgrades[library.name], function (major, minors, nextMajor) { - if (major < oldVersion.major || major > newVersion.major) { - // Older than the current version or newer than the selected - nextMajor(); - } - else { - // Go through the minor versions for this major version - asyncSerial(minors, function (minor, upgrade, nextMinor) { - if (minor <= oldVersion.minor || minor > newVersion.minor) { - // Older than or equal to the current version or newer than the selected - nextMinor(); - } - else { - // We found an upgrade hook, run it - var unnecessaryWrapper = (upgrade.contentUpgrade !== undefined ? upgrade.contentUpgrade : upgrade); - - try { - unnecessaryWrapper(params, function (err, upgradedParams) { - params = upgradedParams; - nextMinor(err); - }); - } - catch (err) { - next(err); - } - } - }, nextMajor); - } - }, function (err) { - next(err, params); - }); + self.setStatus('

    ' + info.error + '
    ' + error.err + '

    '); }; - /** - * Process parameter fields to find and upgrade sub-libraries. - * - * @param {Object} field - * @param {Object} params - * @param {Function} next - */ - ContentUpgrade.prototype.processField = function (field, params, next) { - var self = this; - - if (params === undefined) { - return next(); - } - - switch (field.type) { - case 'library': - if (params.library === undefined || params.params === undefined) { - return next(); - } - - // Look for available upgrades - var usedLib = params.library.split(' ', 2); - for (var i = 0; i < field.options.length; i++) { - var availableLib = field.options[i].split(' ', 2); - if (availableLib[0] === usedLib[0]) { - if (availableLib[1] === usedLib[1]) { - return next(); // Same version - } - - // We have different versions - var usedVer = new Version(usedLib[1]); - var availableVer = new Version(availableLib[1]); - if (usedVer.major > availableVer.major || (usedVer.major === availableVer.major && usedVer.minor >= availableVer.minor)) { - return next(); // Larger or same version that's available - } - - // A newer version is available, upgrade params - return self.upgrade(availableLib[0], usedVer, availableVer, params.params, function (err, upgraded) { - if (!err) { - params.library = availableLib[0] + ' ' + availableVer.major + '.' + availableVer.minor; - params.params = upgraded; - } - next(err, params); - }); - } - } - next(); - break; - - case 'group': - if (field.fields.length === 1) { - // Single field to process, wrapper will be skipped - self.processField(field.fields[0], params, function (err, upgradedParams) { - if (upgradedParams) { - params = upgradedParams; - } - next(err, params); - }); - } - else { - // Go through all fields in the group - asyncSerial(field.fields, function (index, subField, next) { - self.processField(subField, params[subField.name], function (err, upgradedParams) { - if (upgradedParams) { - params[subField.name] = upgradedParams; - } - next(err); - }); - }, function (err) { - next(err, params); - }); - } - break; - - case 'list': - // Go trough all params in the list - asyncSerial(params, function (index, subParams, next) { - self.processField(field.field, subParams, function (err, upgradedParams) { - if (upgradedParams) { - params[index] = upgradedParams; - } - next(err); - }); - }, function (err) { - next(err, params); - }); - break; - - default: - next(); - } - }; - -})(H5P.jQuery); +})(H5P.jQuery, H5P.Version); diff --git a/js/h5p-version.js b/js/h5p-version.js new file mode 100644 index 0000000..78275b1 --- /dev/null +++ b/js/h5p-version.js @@ -0,0 +1,27 @@ +H5P.Version = (function () { + /** + * Make it easy to keep track of version details. + * + * @class + * @namespace H5P + * @param {String} version + */ + function Version(version) { + var versionSplit = version.split('.', 3); + + // Public + this.major = versionSplit[0]; + this.minor = versionSplit[1]; + + /** + * Public. Custom string for this object. + * + * @returns {String} + */ + this.toString = function () { + return version; + }; + } + + return Version; +})(); From 037e26f42ba8dc3c2c99565125fbfd55164dd403 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Fri, 27 Feb 2015 08:57:02 +0100 Subject: [PATCH 13/75] Space support. --- js/h5p.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/js/h5p.js b/js/h5p.js index c44a55b..15a9f97 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -766,7 +766,7 @@ H5P.openEmbedDialog = function ($element, embedCode, resizeCode, size) { }).select(); // Expand advanced embed - $dialog.find('.h5p-expander').click(function () { + var expand = function () { var $expander = H5P.jQuery(this); var $content = $expander.next(); if ($content.is(':visible')) { @@ -778,6 +778,11 @@ H5P.openEmbedDialog = function ($element, embedCode, resizeCode, size) { $content.show(); } positionInner(); + }; + $dialog.find('.h5p-expander').click(expand).keypress(function (event) { + if (event.keyCode === 32) { + expand(); + } }); }); From 4e594dd57345b0aec5402052f9c4cdb089862b43 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Fri, 27 Feb 2015 10:26:57 +0100 Subject: [PATCH 14/75] Fixed dialog pos and expand. --- js/h5p.js | 20 ++++++++++++-------- styles/h5p.css | 1 - 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index 15a9f97..4183e1c 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -159,8 +159,7 @@ H5P.init = function () { // When resize has been prepared tell parent window to resize H5P.communicator.on('resizePrepared', function (data) { H5P.communicator.send('resize', { - height: document.body.scrollHeight, - parentHeight: data.parentHeight + height: document.body.scrollHeight }); }); @@ -722,13 +721,18 @@ H5P.openEmbedDialog = function ($element, embedCode, resizeCode, size) { // Selecting embed code when dialog is opened H5P.jQuery(dialog).on('dialog-opened', function (event, $dialog) { var $inner = $dialog.find('.h5p-inner'); + var $scroll = $inner.find('.h5p-scroll-content'); + var diff = $scroll.outerHeight() - $scroll.innerHeight(); var positionInner = function () { - $inner.css('height', ''); - var h = $inner.height(); - if (Math.floor($dialog.height()) === h) { - $inner.css('height', '100%'); + var height = $inner.height(); + if ($scroll[0].scrollHeight + diff > height) { + $inner.css('height', ''); // 100% } - $inner.css('marginTop', '-' + (h / 2) + 'px'); + else { + $inner.css('height', 'auto'); + height = $inner.height(); + } + $inner.css('marginTop', '-' + (height / 2) + 'px'); }; // Handle changing of width/height @@ -781,7 +785,7 @@ H5P.openEmbedDialog = function ($element, embedCode, resizeCode, size) { }; $dialog.find('.h5p-expander').click(expand).keypress(function (event) { if (event.keyCode === 32) { - expand(); + expand.apply(this); } }); }); diff --git a/styles/h5p.css b/styles/h5p.css index 1925892..b4478b9 100644 --- a/styles/h5p.css +++ b/styles/h5p.css @@ -252,7 +252,6 @@ div.h5p-fullscreen { width: 300px; left: 50%; top: 50%; - height: auto; margin: 0 0 0 -150px; } .h5p-embed-dialog .h5p-embed-code-container, From 1e4a7ff26f418fde8164053b865369638bfb0a0f Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Fri, 27 Feb 2015 10:29:43 +0100 Subject: [PATCH 15/75] Removed unused code. --- js/h5p-resizer.js | 48 ++--------------------------------------------- 1 file changed, 2 insertions(+), 46 deletions(-) diff --git a/js/h5p-resizer.js b/js/h5p-resizer.js index 54ec5d8..d1a1905 100644 --- a/js/h5p-resizer.js +++ b/js/h5p-resizer.js @@ -47,14 +47,10 @@ actionHandlers.prepareResize = function (iframe, data, respond) { responseData = {}; - // Retain parent size to avoid jumping/scrolling - responseData.parentHeight = iframe.parentElement.style.height; - //iframe.parentElement.style.height = iframe.parentElement.clientHeight + 'px'; - // Reset iframe height, in case content has shrinked. iframe.style.height = '1px'; - respond('resizePrepared', responseData); + respond('resizePrepared'); }; /** @@ -68,9 +64,6 @@ actionHandlers.resize = function (iframe, data, respond) { // Resize iframe so all content is visible. iframe.style.height = data.height + 'px'; - - // Free parent - //iframe.parentElement.style.height = data.parentHeight; }; /** @@ -84,43 +77,6 @@ } }; - // /** - // * Enter semi full screen. - // * Expands the iframe so that it covers the whole page. - // * - // * @private - // * @param {Object} iframe Element - // * @param {Object} data Payload - // * @param {Function} respond Send a response to the iframe - // */ - // actionHandlers.fullScreen = function (iframe, data, respond) { - // iframe.style.position = 'fixed'; - // iframe.style.top = iframe.style.left = 0; - // iframe.style.zIndex = 101; - // iframe.style.width = iframe.style.height = '100%'; - // document.body.addEventListener('keyup', escape, false); - // respond('fullScreen'); - // }; - // - // /** - // * Exit semi full screen. - // * - // * @private - // * @param {Object} iframe Element - // * @param {Object} data Payload - // * @param {Function} respond Send a response to the iframe - // */ - // actionHandlers.exitFullScreen = function (iframe, data, respond) { - // iframe.style.position = ''; - // iframe.style.top = iframe.style.left = ''; - // iframe.style.zIndex = ''; - // iframe.style.width = '100%'; - // iframe.style.height = ''; - // document.body.removeEventListener('keyup', escape, false); - // respond('exitFullScreen'); - // }; - - // Listen for messages from iframes window.addEventListener('message', function receiveMessage(event) { if (event.data.context !== 'h5p') { @@ -142,7 +98,7 @@ // Find action handler handler if (actionHandlers[event.data.action]) { - actionHandlers[event.data.action](iframe, event.data, function (action, data) { + actionHandlers[event.data.action](iframe, event.data, function respond(action, data) { if (data === undefined) { data = {}; } From 77d589ec6868d834aa3b91ece1cf0c124dbada71 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Fri, 27 Feb 2015 10:55:47 +0100 Subject: [PATCH 16/75] Fixed absolute URLs violating its parent. --- js/h5p.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/h5p.js b/js/h5p.js index 4183e1c..932477e 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -459,7 +459,8 @@ H5P.getPath = function (path, contentId) { } if (!hasProtocol(prefix)) { - prefix = window.parent.location.protocol + "//" + window.parent.location.host + prefix; + // Use absolute urls + prefix = window.location.protocol + "//" + window.location.host + prefix; } return prefix + '/' + path; From d1cc8bcb71c054b4dbfcca583d988b96b6105072 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Fri, 27 Feb 2015 11:05:22 +0100 Subject: [PATCH 17/75] Moved embed template to core. --- embed.php | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 embed.php diff --git a/embed.php b/embed.php new file mode 100644 index 0000000..25b37f4 --- /dev/null +++ b/embed.php @@ -0,0 +1,27 @@ + + + + + <?php print $content['title']; ?> + + + + + + + + +
    + + + From f2595b2bce932eb5832effb4186f5a9cbee0b617 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Fri, 27 Feb 2015 13:59:42 +0100 Subject: [PATCH 18/75] Change how H5Ps are integrated. --- embed.php | 7 +---- js/h5p-content-upgrade.js | 2 +- js/h5p-library-details.js | 4 +-- js/h5p-library-list.js | 10 +++--- js/h5p-utils.js | 2 +- js/h5p-x-api-event.js | 4 +-- js/h5p.js | 65 +++++++++++++++++++++++++++++++-------- 7 files changed, 65 insertions(+), 29 deletions(-) diff --git a/embed.php b/embed.php index 25b37f4..7e6250b 100644 --- a/embed.php +++ b/embed.php @@ -13,13 +13,8 @@
    diff --git a/js/h5p-content-upgrade.js b/js/h5p-content-upgrade.js index ff3756f..6828fdc 100644 --- a/js/h5p-content-upgrade.js +++ b/js/h5p-content-upgrade.js @@ -7,7 +7,7 @@ var H5PUpgrades = H5PUpgrades || {}; // Initialize $(document).ready(function () { // Get library info - info = H5PIntegration.getLibraryInfo(); + info = H5PAdminIntegration.libraryInfo; // Get and reset container $container = $('#h5p-admin-container').html('

    ' + info.message + '

    '); diff --git a/js/h5p-library-details.js b/js/h5p-library-details.js index 8605b3d..9d22167 100644 --- a/js/h5p-library-details.js +++ b/js/h5p-library-details.js @@ -7,8 +7,8 @@ var H5PLibraryDetails= H5PLibraryDetails || {}; * Initializing */ H5PLibraryDetails.init = function () { - H5PLibraryDetails.$adminContainer = H5PIntegration.getAdminContainer(); - H5PLibraryDetails.library = H5PIntegration.getLibraryInfo(); + H5PLibraryDetails.$adminContainer = H5P.jQuery(H5PAdminIntegration.containerSelector); + H5PLibraryDetails.library = H5PAdminIntegration.libraryInfo; // currentContent holds the current list if data (relevant for filtering) H5PLibraryDetails.currentContent = H5PLibraryDetails.library.content; diff --git a/js/h5p-library-list.js b/js/h5p-library-list.js index 4382b28..5f419d6 100644 --- a/js/h5p-library-list.js +++ b/js/h5p-library-list.js @@ -7,15 +7,15 @@ var H5PLibraryList = H5PLibraryList || {}; * Initializing */ H5PLibraryList.init = function () { - var $adminContainer = H5PIntegration.getAdminContainer(); + var $adminContainer = H5P.jQuery(H5PAdminIntegration.containerSelector); - var libraryList = H5PIntegration.getLibraryList(); + var libraryList = H5PAdminIntegration.libraryList; if (libraryList.notCached) { $adminContainer.append(H5PUtils.getRebuildCache(libraryList.notCached)); } // Create library list - $adminContainer.append(H5PLibraryList.createLibraryList(H5PIntegration.getLibraryList())); + $adminContainer.append(H5PLibraryList.createLibraryList(H5PAdminIntegration.libraryList)); }; /** @@ -24,7 +24,7 @@ var H5PLibraryList = H5PLibraryList || {}; * @param {object} libraries List of libraries and headers */ H5PLibraryList.createLibraryList = function (libraries) { - var t = H5PIntegration.i18n.H5P; + var t = H5PAdminIntegration.l10n; if(libraries.listData === undefined || libraries.listData.length === 0) { return $('
    ' + t.NA + '
    '); } @@ -123,7 +123,7 @@ var H5PLibraryList = H5PLibraryList || {}; } } }; - + // Initialize me: $(document).ready(function () { if (!H5PLibraryList.initialized) { diff --git a/js/h5p-utils.js b/js/h5p-utils.js index 00af7bc..d90ca58 100644 --- a/js/h5p-utils.js +++ b/js/h5p-utils.js @@ -7,7 +7,7 @@ var H5PUtils = H5PUtils || {}; * @param {array} headers List of headers */ H5PUtils.createTable = function (headers) { - var $table = $('
    '); + var $table = $('
    '); if(headers) { var $thead = $(''); diff --git a/js/h5p-x-api-event.js b/js/h5p-x-api-event.js index 5e1a025..e199c28 100644 --- a/js/h5p-x-api-event.js +++ b/js/h5p-x-api-event.js @@ -99,8 +99,8 @@ H5P.XAPIEvent.prototype.setObject = function(instance) { */ H5P.XAPIEvent.prototype.setActor = function() { this.data.statement.actor = { - 'name': H5P.user.name, - 'mbox': 'mailto:' + H5P.user.mail, + 'name': H5PIntegration.user.name, + 'mbox': 'mailto:' + H5PIntegration.user.mail, 'objectType': 'Agent' }; }; diff --git a/js/h5p.js b/js/h5p.js index 932477e..d193881 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -48,7 +48,7 @@ H5P.init = function () { var $element = H5P.jQuery(this); var $container = H5P.jQuery('
    ').appendTo($element); var contentId = $element.data('content-id'); - var contentData = H5P.contentDatas['cid-' + contentId]; + var contentData = H5PIntegration.contents['cid-' + contentId]; if (contentData === undefined) { return H5P.error('No data for content id ' + contentId + '. Perhaps the library is gone?'); } @@ -217,6 +217,47 @@ H5P.init = function () { }); }; +/** + * Loop through assets for iframe content and create a set of tags for head. + * + * @private + * @param {number} contentId + * @returns {string} HTML + */ +H5P.getHeadTags = function (contentId) { + var basePath = window.location.protocol + '//' + window.location.host + H5PIntegration.basePath; + + var createUrl = function (path) { + if (path.substring(0,7) !== 'http://' && path.substring(0,8) !== 'https://') { + // Not external, add base path. + path = basePath + path; + } + return path; + }; + + var createStyleTags = function (styles) { + var tags = ''; + for (var i = 0; i < styles.length; i++) { + tags += ''; + } + return tags; + }; + + var createScriptTags = function (scripts) { + var tags = ''; + for (var i = 0; i < scripts.length; i++) { + tags += ''; + } + return tags; + }; + + return createStyleTags(H5PIntegration.core.styles) + + createStyleTags(H5PIntegration.contents['cid-' + contentId].styles) + + createScriptTags(H5PIntegration.core.scripts) + + createScriptTags(H5PIntegration.contents['cid-' + contentId].scripts) + + ''; +}; + H5P.communicator = (function () { /** * @class @@ -449,7 +490,7 @@ H5P.getPath = function (path, contentId) { } if (contentId !== undefined) { - prefix = H5P.url + '/content/' + contentId; + prefix = H5PIntegration.url + '/content/' + contentId; } else if (window.H5PEditor !== undefined) { prefix = H5PEditor.filesPath; @@ -571,15 +612,15 @@ H5P.t = function (key, vars, ns) { ns = 'H5P'; } - if (H5P.l10n[ns] === undefined) { + if (H5PIntegration.l10n[ns] === undefined) { return '[Missing translation namespace "' + ns + '"]'; } - if (H5P.l10n[ns][key] === undefined) { + if (H5PIntegration.l10n[ns][key] === undefined) { return '[Missing translation "' + key + '" in "' + ns + '"]'; } - var translation = H5P.l10n[ns][key]; + var translation = H5PIntegration.l10n[ns][key]; if (vars !== undefined) { // Replace placeholder with variables. @@ -1156,7 +1197,7 @@ H5P.libraryFromString = function (library) { * @returns {String} The full path to the library. */ H5P.getLibraryPath = function (library) { - return H5P.url + '/libraries/' + library; + return H5PIntegration.url + '/libraries/' + library; }; /** @@ -1204,8 +1245,8 @@ H5P.trim = function (value) { * @returns {Boolean} */ H5P.jsLoaded = function (path) { - H5P.loadedJs = H5P.loadedJs || []; - return H5P.jQuery.inArray(path, H5P.loadedJs) !== -1; + H5PIntegration.loadedJs = H5PIntegration.loadedJs || []; + return H5P.jQuery.inArray(path, H5PIntegration.loadedJs) !== -1; }; /** @@ -1215,8 +1256,8 @@ H5P.jsLoaded = function (path) { * @returns {Boolean} */ H5P.cssLoaded = function (path) { - H5P.loadedCss = H5P.loadedCss || []; - return H5P.jQuery.inArray(path, H5P.loadedCss) !== -1; + H5PIntegration.loadedCss = H5PIntegration.loadedCss || []; + return H5P.jQuery.inArray(path, H5PIntegration.loadedCss) !== -1; }; /** @@ -1255,7 +1296,7 @@ H5P.shuffleArray = function (array) { * @param {Number} time optional reported time usage */ H5P.setFinished = function (contentId, score, maxScore, time) { - if (H5P.postUserStatistics === true) { + if (H5PIntegration.postUserStatistics === true) { /** * Return unix timestamp for the given JS Date. * @@ -1268,7 +1309,7 @@ H5P.setFinished = function (contentId, score, maxScore, time) { // Post the results // TODO: Should we use a variable with the complete path? - H5P.jQuery.post(H5P.ajaxPath + 'setFinished', { + H5P.jQuery.post(H5PIntegration.ajaxPath + 'setFinished', { contentId: contentId, score: score, maxScore: maxScore, From aac10b86fa3a42d2679af3bc5a71da946d1c0a2d Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Fri, 27 Feb 2015 14:53:26 +0100 Subject: [PATCH 19/75] Make old embed code insert new. --- js/h5p-embed.js | 250 +++++++----------------------------------------- 1 file changed, 35 insertions(+), 215 deletions(-) diff --git a/js/h5p-embed.js b/js/h5p-embed.js index deeab96..3196fa1 100644 --- a/js/h5p-embed.js +++ b/js/h5p-embed.js @@ -1,69 +1,27 @@ /*jshint multistr: true */ /** - * + * Converts old script tag embed to iframe */ -var H5P = H5P || (function () { +var H5POldEmbed = H5POldEmbed || (function () { var head = document.getElementsByTagName('head')[0]; - var contentId = 0; - var contents = {}; + var resizer = false; /** - * Wraps multiple content between a prefix and a suffix. + * Loads the resizing script */ - var wrap = function (prefix, content, suffix) { - var result = ''; - for (var i = 0; i < content.length; i++) { - result += prefix + content[i] + suffix; - } - return result; - }; - - /** - * - */ - var loadContent = function (id, script) { - var url = script.getAttribute('data-h5p'); - var data, callback = 'H5P' + id; - - // Prevent duplicate loading. - script.removeAttribute('data-h5p'); + var loadResizer = function (url) { + var data, callback = 'H5POldEmbed'; + resizer = true; // Callback for when content data is loaded. window[callback] = function (content) { - contents[id] = content; - - var iframe = document.createElement('iframe'); - var parent = script.parentNode; - parent.insertBefore(iframe, script); - - iframe.id = 'h5p-iframe-' + id; - iframe.style.display = 'block'; - iframe.style.width = '100%'; - iframe.style.height = '1px'; - iframe.style.border = 'none'; - iframe.style.zIndex = 101; - iframe.style.top = 0; - iframe.style.left = 0; - iframe.className = 'h5p-iframe'; - iframe.setAttribute('frameBorder', '0'); - iframe.contentDocument.open(); - iframe.contentDocument.write('\ - \ - \ - \ - ' + wrap('') + '\ - ' + wrap('') + '\ - \ -
    \ - '); - iframe.contentDocument.close(); - iframe.contentDocument.documentElement.style.overflow = 'hidden'; + // Add resizing script to head + var resizer = document.createElement('script'); + resizer.src = content; + head.appendChild(resizer); // Clean up - parent.removeChild(script); head.removeChild(data); delete window[callback]; }; @@ -74,183 +32,45 @@ var H5P = H5P || (function () { head.appendChild(data); }; + /** + * Replaced script tag with iframe + */ + var addIframe = function (script) { + // Add iframe + var iframe = document.createElement('iframe'); + iframe.src = script.getAttribute('data-h5p'); + iframe.frameBorder = false; + iframe.allowFullscreen = true; + var parent = script.parentNode; + parent.insertBefore(iframe, script); + parent.removeChild(script); + }; + /** * Go throught all script tags with the data-h5p attribute and load content. */ - function H5P() { + function H5POldEmbed() { var scripts = document.getElementsByTagName('script'); var h5ps = []; // Use seperate array since scripts grow in size. for (var i = 0; i < scripts.length; i++) { var script = scripts[i]; - if (script.hasAttribute('data-h5p')) { + if (script.src.indexOf('/h5p-resizer.js') !== -1) { + resizer = true; + } + else if (script.hasAttribute('data-h5p')) { h5ps.push(script); } } for (i = 0; i < h5ps.length; i++) { - loadContent(contentId, h5ps[i]); - contentId++; - } - } - - /** - * Return integration object - */ - H5P.getIntegration = function (id) { - var content = contents[id]; - return { - getJsonContent: function () { - return content.params; - }, - getContentPath: function () { - return content.path + 'content/' + content.id + '/'; - }, - getFullscreen: function () { - return content.fullscreen; - }, - getLibraryPath: function (library) { - return content.path + 'libraries/' + library; - }, - getContentData: function () { - return { - library: content.library, - jsonContent: content.params, - fullScreen: content.fullscreen, - exportUrl: content.exportUrl, - embedCode: content.embedCode - }; - }, - i18n: content.i18n, - showH5PIconInActionBar: function () { - // Always show H5P-icon when embedding - return true; + if (!resizer) { + loadResizer(h5ps[i].getAttribute('data-h5p')); } - }; - }; + addIframe(h5ps[i]); - // Detect if we support fullscreen, and what prefix to use. - var fullScreenBrowserPrefix, safariBrowser; - if (document.documentElement.requestFullScreen) { - fullScreenBrowserPrefix = ''; - } - else if (document.documentElement.webkitRequestFullScreen && - navigator.userAgent.indexOf('Android') === -1 // Skip Android - ) { - safariBrowser = navigator.userAgent.match(/Version\/(\d)/); - safariBrowser = (safariBrowser === null ? 0 : parseInt(safariBrowser[1])); - - // Do not allow fullscreen for safari < 7. - if (safariBrowser === 0 || safariBrowser > 6) { - fullScreenBrowserPrefix = 'webkit'; } } - else if (document.documentElement.mozRequestFullScreen) { - fullScreenBrowserPrefix = 'moz'; - } - else if (document.documentElement.msRequestFullscreen) { - fullScreenBrowserPrefix = 'ms'; - } - /** - * Enter fullscreen mode. - */ - H5P.fullScreen = function ($element, instance, exitCallback, body) { - var iframe = document.getElementById('h5p-iframe-' + $element.parent().data('content-id')); - var $classes = $element.add(body); - var $body = $classes.eq(1); - - /** - * Prepare for resize by setting the correct styles. - * - * @param {String} classes CSS - */ - var before = function (classes) { - $classes.addClass(classes); - iframe.style.height = '100%'; - }; - - /** - * Gets called when fullscreen mode has been entered. - * Resizes and sets focus on content. - */ - var entered = function () { - // Do not rely on window resize events. - instance.$.trigger('resize'); - instance.$.trigger('focus'); - }; - - /** - * Gets called when fullscreen mode has been exited. - * Resizes and sets focus on content. - * - * @param {String} classes CSS - */ - var done = function (classes) { - H5P.isFullscreen = false; - $classes.removeClass(classes); - - // Do not rely on window resize events. - instance.$.trigger('resize'); - instance.$.trigger('focus'); - - if (exitCallback !== undefined) { - exitCallback(); - } - }; - - H5P.isFullscreen = true; - if (fullScreenBrowserPrefix === undefined) { - // Create semi fullscreen. - - before('h5p-semi-fullscreen'); - iframe.style.position = 'fixed'; - - var $disable = $element.prepend('').children(':first'); - var keyup, disableSemiFullscreen = function () { - $disable.remove(); - $body.unbind('keyup', keyup); - iframe.style.position = 'static'; - done('h5p-semi-fullscreen'); - return false; - }; - keyup = function (event) { - if (event.keyCode === 27) { - disableSemiFullscreen(); - } - }; - $disable.click(disableSemiFullscreen); - $body.keyup(keyup); // TODO: Does not work with iframe's $! - entered(); - } - else { - // Create real fullscreen. - - before('h5p-fullscreen'); - var first, eventName = (fullScreenBrowserPrefix === 'ms' ? 'MSFullscreenChange' : fullScreenBrowserPrefix + 'fullscreenchange'); - document.addEventListener(eventName, function () { - if (first === undefined) { - // We are entering fullscreen mode - first = false; - entered(); - return; - } - - // We are exiting fullscreen - done('h5p-fullscreen'); - document.removeEventListener(eventName, arguments.callee, false); - }); - - if (fullScreenBrowserPrefix === '') { - iframe.requestFullScreen(); - } - else { - var method = (fullScreenBrowserPrefix === 'ms' ? 'msRequestFullscreen' : fullScreenBrowserPrefix + 'RequestFullScreen'); - var params = (fullScreenBrowserPrefix === 'webkit' && safariBrowser === 0 ? Element.ALLOW_KEYBOARD_INPUT : undefined); - iframe[method](params); - } - } - }; - - return H5P; + return H5POldEmbed; })(); -new H5P(); +new H5POldEmbed(); From 1c1829b13b351327294f07306334e880508692d3 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Fri, 27 Feb 2015 14:53:38 +0100 Subject: [PATCH 20/75] Fixed integration bug. --- js/h5p-x-api-event.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/h5p-x-api-event.js b/js/h5p-x-api-event.js index e199c28..940a5cb 100644 --- a/js/h5p-x-api-event.js +++ b/js/h5p-x-api-event.js @@ -79,7 +79,7 @@ H5P.XAPIEvent.prototype.getVerb = function(full) { H5P.XAPIEvent.prototype.setObject = function(instance) { if (instance.contentId) { this.data.statement.object = { - 'id': H5P.contentDatas['cid-' + instance.contentId].url, + 'id': H5PIntegration.contents['cid-' + instance.contentId].url, 'objectType': 'Activity', 'extensions': { 'http://h5p.org/x-api/h5p-local-content-id': instance.contentId From 07cd5c0d426b6532fb7c8d04a00011c96111554e Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Fri, 27 Feb 2015 15:37:16 +0100 Subject: [PATCH 21/75] Added a floating touch. --- js/h5p.js | 7 +++++-- styles/h5p.css | 2 ++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index d193881..1a3aecc 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -807,8 +807,11 @@ H5P.openEmbedDialog = function ($element, embedCode, resizeCode, size) { H5P.jQuery(this).select().css('height', this.scrollHeight + 'px'); positionInner(); }).blur(function () { - H5P.jQuery(this).css('height', ''); - positionInner(); + var $area = H5P.jQuery(this); + setTimeout(function () { + $area.css('height', ''); + positionInner(); + }, 100); }).select(); // Expand advanced embed diff --git a/styles/h5p.css b/styles/h5p.css index b4478b9..37a6b62 100644 --- a/styles/h5p.css +++ b/styles/h5p.css @@ -253,6 +253,7 @@ div.h5p-fullscreen { left: 50%; top: 50%; margin: 0 0 0 -150px; + transition: margin 250ms linear 100ms; } .h5p-embed-dialog .h5p-embed-code-container, .h5p-embed-size { @@ -353,6 +354,7 @@ div.h5p-fullscreen { font-size: 1.125em; outline: none; margin: 0.5em 0 0; + display: inline-block; } .h5p-expander:before { content: "+"; From 8e53f626791494ebd0848d06ab676380f1175626 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Mon, 2 Mar 2015 13:42:15 +0100 Subject: [PATCH 22/75] Make sure admin container is empty. --- js/h5p-library-list.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/h5p-library-list.js b/js/h5p-library-list.js index 5f419d6..dcb0cc8 100644 --- a/js/h5p-library-list.js +++ b/js/h5p-library-list.js @@ -7,7 +7,7 @@ var H5PLibraryList = H5PLibraryList || {}; * Initializing */ H5PLibraryList.init = function () { - var $adminContainer = H5P.jQuery(H5PAdminIntegration.containerSelector); + var $adminContainer = H5P.jQuery(H5PAdminIntegration.containerSelector).html(''); var libraryList = H5PAdminIntegration.libraryList; if (libraryList.notCached) { From 553ae243d2e1fd648aada5a58d3064f9713537cc Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Mon, 2 Mar 2015 15:01:05 +0100 Subject: [PATCH 23/75] UX improvements to embed code system --- js/h5p.js | 35 +++++++++++++++-------------------- styles/h5p.css | 2 +- 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index 1a3aecc..8ade0e6 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -198,8 +198,9 @@ H5P.init = function () { H5P.trigger(instance, 'resize'); } }); - H5P.instances.push(instance); } + + H5P.instances.push(instance); // Resize content. H5P.trigger(instance, 'resize'); @@ -758,6 +759,7 @@ H5P.findCopyrights = function (info, parameters, contentId) { * @returns {undefined} */ H5P.openEmbedDialog = function ($element, embedCode, resizeCode, size) { + var fullEmbedCode = embedCode + resizeCode; var dialog = new H5P.Dialog('embed', H5P.t('embed'), '' + H5P.t('size') + ': × px
    ' + H5P.t('showAdvanced') + '

    ' + H5P.t('advancedHelp') + '

    ', $element); // Selecting embed code when dialog is opened @@ -788,31 +790,21 @@ H5P.openEmbedDialog = function ($element, embedCode, resizeCode, size) { return Math.ceil(num); }; var updateEmbed = function () { - $dialog.find('.h5p-embed-code-container:first').val(embedCode.replace(':w', getNum($w, size.width)).replace(':h', getNum($h, size.height))); + $dialog.find('.h5p-embed-code-container:first').val(fullEmbedCode.replace(':w', getNum($w, size.width)).replace(':h', getNum($h, size.height))); }; - var w = size.width; - $w.change(function () { - // Keep aspect ratio when changing width - var newW = getNum($w, size.width); - $h.val(Math.ceil(newW * (getNum($h, size.height) / w))); - w = newW; - updateEmbed(); - }); + $w.change(updateEmbed); $h.change(updateEmbed); updateEmbed(); // Select text and expand textareas - $dialog.find('.h5p-embed-code-container').focus(function () { - H5P.jQuery(this).select().css('height', this.scrollHeight + 'px'); - positionInner(); - }).blur(function () { - var $area = H5P.jQuery(this); - setTimeout(function () { - $area.css('height', ''); - positionInner(); - }, 100); - }).select(); + $dialog.find('.h5p-embed-code-container').each(function(index, value) { + H5P.jQuery(this).css('height', this.scrollHeight + 'px').focus(function() { + H5P.jQuery(this).select(); + }); + }); + $dialog.find('.h5p-embed-code-container').eq(0).select(); + positionInner(); // Expand advanced embed var expand = function () { @@ -826,6 +818,9 @@ H5P.openEmbedDialog = function ($element, embedCode, resizeCode, size) { $expander.addClass('h5p-open').text(H5P.t('hideAdvanced')); $content.show(); } + $dialog.find('.h5p-embed-code-container').each(function(index, value) { + H5P.jQuery(this).css('height', this.scrollHeight + 'px'); + }); positionInner(); }; $dialog.find('.h5p-expander').click(expand).keypress(function (event) { diff --git a/styles/h5p.css b/styles/h5p.css index 37a6b62..e3ef889 100644 --- a/styles/h5p.css +++ b/styles/h5p.css @@ -350,7 +350,7 @@ div.h5p-fullscreen { margin: 0; } .h5p-expander { - cursor: default; + cursor: pointer; font-size: 1.125em; outline: none; margin: 0.5em 0 0; From 99db5699fb39d5b5a040d9325eb94fb1ec9d3278 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Mon, 2 Mar 2015 15:49:27 +0100 Subject: [PATCH 24/75] Handle anonymous users --- js/h5p-x-api-event.js | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/js/h5p-x-api-event.js b/js/h5p-x-api-event.js index 940a5cb..141e232 100644 --- a/js/h5p-x-api-event.js +++ b/js/h5p-x-api-event.js @@ -98,11 +98,33 @@ H5P.XAPIEvent.prototype.setObject = function(instance) { * Helper function to set the actor, email and name will be added automatically */ H5P.XAPIEvent.prototype.setActor = function() { - this.data.statement.actor = { - 'name': H5PIntegration.user.name, - 'mbox': 'mailto:' + H5PIntegration.user.mail, - 'objectType': 'Agent' - }; + if (H5PIntegration.user !== undefined) { + this.data.statement.actor = { + 'name': H5PIntegration.user.name, + 'mbox': 'mailto:' + H5PIntegration.user.mail, + 'objectType': 'Agent' + }; + } + else { + var uuid; + if (localStorage.H5PUserUUID) { + uuid = localStorage.H5PUserUUID; + } + else { + uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(char) { + var random = Math.random()*16|0, newChar = char === 'x' ? random : (random&0x3|0x8); + return newChar.toString(16); + }); + localStorage.H5PUserUUID = uuid; + } + this.data.statement.actor = { + 'account': { + 'name': uuid, + 'homePage': window.location.origin + H5PIntegration.basePath + }, + 'objectType': 'Agent' + }; + } }; /** From e961f614a427d19597d2928411d8d69e24c9e3ea Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Mon, 2 Mar 2015 15:53:29 +0100 Subject: [PATCH 25/75] Fixed so that iframes can be loaded before resizer script. --- js/h5p-embed.js | 1 - js/h5p-resizer.js | 13 +++++++++++++ js/h5p.js | 12 +++++++++--- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/js/h5p-embed.js b/js/h5p-embed.js index 3196fa1..c141456 100644 --- a/js/h5p-embed.js +++ b/js/h5p-embed.js @@ -66,7 +66,6 @@ var H5POldEmbed = H5POldEmbed || (function () { loadResizer(h5ps[i].getAttribute('data-h5p')); } addIframe(h5ps[i]); - } } diff --git a/js/h5p-resizer.js b/js/h5p-resizer.js index d1a1905..6318fc8 100644 --- a/js/h5p-resizer.js +++ b/js/h5p-resizer.js @@ -108,4 +108,17 @@ }); } }, false); + + // Let h5p iframes know we're ready! + var iframes = document.getElementsByTagName('iframe'); + var ready = { + context: 'h5p', + action: 'ready' + }; + for (var i = 0; i < iframes.length; i++) { + if (iframes[i].src.indexOf('h5p') !== -1) { + iframes[i].contentWindow.postMessage(ready, '*'); + } + } + })(); diff --git a/js/h5p.js b/js/h5p.js index 8ade0e6..1937f43 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -145,6 +145,11 @@ H5P.init = function () { // External embed var parentIsFriendly = false; + // Handle that the resizer is loaded after the iframe + H5P.communicator.on('ready', function () { + H5P.communicator.send('hello'); + }); + // Handle hello message from our parent window H5P.communicator.on('hello', function () { // Initial setup/handshake is done @@ -153,7 +158,8 @@ H5P.init = function () { // Hide scrollbars for correct size document.body.style.overflow = 'hidden'; - H5P.communicator.send('prepareResize'); + // Content need to be resized to fit the new iframe size + H5P.trigger(instance, 'resize'); }); // When resize has been prepared tell parent window to resize @@ -192,14 +198,14 @@ H5P.init = function () { H5P.jQuery(window.top).resize(function () { if (window.parent.H5P.isFullscreen) { // Use timeout to avoid bug in certain browsers when exiting fullscreen. Some browser will trigger resize before the fullscreenchange event. - H5P.trigger(instance, 'resize'); + H5P.trigger(instance, 'resize'); } else { H5P.trigger(instance, 'resize'); } }); } - + H5P.instances.push(instance); // Resize content. From acca9627755f2f6780330c64c433e1c2887ea01e Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Tue, 3 Mar 2015 11:03:10 +0100 Subject: [PATCH 26/75] Fixed misplaced stash (after merge). --- js/h5p.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index 1937f43..b93d9a7 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -108,8 +108,8 @@ H5P.init = function () { H5P.on(instance, 'xAPI', H5P.xAPICompletedListener); H5P.on(instance, 'xAPI', H5P.externalDispatcher.trigger); - if (H5P.isFramed) - var resizeDelay;{ + if (H5P.isFramed) { + var resizeDelay; if (H5P.externalEmbed === false) { // Internal embed // Make it possible to resize the iframe when the content changes size. This way we get no scrollbars. From 23f68d821ec1f5de2d446013dc3fad325333476e Mon Sep 17 00:00:00 2001 From: Thomas Marstrander Date: Wed, 4 Mar 2015 10:06:35 +0100 Subject: [PATCH 27/75] Added woff to css inline --- fonts/h5p.woff | Bin 4324 -> 0 bytes styles/h5p.css | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 fonts/h5p.woff diff --git a/fonts/h5p.woff b/fonts/h5p.woff deleted file mode 100644 index 66394c10c29d618c76bc5a15fffdc7f78a306179..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4324 zcmeHKdyHIF89(27&b{-zyEAil+wRWp%)1B3)oP=Y~BFd-7aD3O>bMBJ&rbMNf3TOcO> z?alqp`QG0--?`_U?|$K;$w@{GPwI6R4f^9DZSm#LMVAg%7^4WdpR(Yxkk+!F!sq*gF>9-MwwHaox-f96b}Z>c*2iq8CL zh4gP}*#*L&Ax6+mE{TO0Bj*O8KZyU|c=POCA^2T(?o={9mFSt9rk|ya)AZ0yJAO$U zcQB&+*|Xwaewr1alTYwWx|{p8LcTag^-7MidJSbpMQymu3;gKt)muv4^{s0!cci*q zCG}U8bS_`JwVoT;a&?WLMmt=-bkjQLc~?Jb%%j?U-dVTl(lX2jFe^dF*^}ZZe**dG zwBNu&ylQ2X2N6s)Q>e8UYSkJbQ_b-Etf_TdyUO-g)dr@gzx;Tma{B;(!jcPY+ppiW zsrl9FgVh(Sh{fp+_EYfy|26AoYZ&V@j?~q_b5&TOXw1Vv%!~xdI{js zJA%vo3aL=xD3J#-L9Yl!QPOh1RP31_j7@wGhe`)6qeu3Ax-Ytqj7e{tO`(f6D76qkG@`-({LGmE)oK{kS~FMB?f zGHJAx`bHA=iwd?#aSi#kVYGSOMIvIP+9%_3pOWUy2cxEH+>=XLCta|jyb)1fQ+Okh z$BgTm$C{6w#jq#lYsBOvKgB3*-RUYp}MD^n(J z?wRbXwRs+;QZuH*E0jE6X{}B352;SGt9lf;S+8c;z4%RwLk75o|-t8%|Cec)#o;*=`-F4ili zNl@kbDoJYVZ*MQQxd|mAk>2r~n~92u$c^PlitLcj1vMjy6;YQW$qXuuC{D9zN=Rvd zSiHye2hxd-m^LI7zJYi#3fK3oiuXl?SMac^*t=Mis2|0vQ;}#=VH`aZIWCDOFs7ah zdT_bMvz+&D?Z<|fh%Yo>SzF>!YHF8#(;UFZI25(@gi54H-buT_=?)fNYcSPL~k1g6W4wh zwns-79CT(J8eEGTP$Qi$IX{NJf-~eO=juH44STQBA@-d3A%7&e zS2wT=?YFvkffjl{xIq0phK@b0l_-C6)|era zHoIrh3>h=qqGxxvXgWP>GG;K83<8D5avE`wOTgg!Cm#81a>GAZRN7s|ej9t}wxvC) z2Ky&v|E|IZHo-p{Q>!gQw*GuuQropH!2>gRo>!~se!y|S9e@Eqms-tN!pm#G^8hQi z{5ilOz{`Oj7ymHbY=~XYzRCVbxAFnLm;YTJmcLhDR4?fb8h^FHT-qqc+`wXCq)Ug9 zV2`){@QCS_12OR4zDU z9{LRN&}WE;K0{o?w!dW?`fRWo+s`h;pUaJGj(wi(V|!T-ThGeKLLh6@_J;_&1WLQu wO>8%tfpTa#85kUZ$u3Cu25x590rb~`cMUs)_CmH3T2lb)yd2KowjcZa2h12PrvLx| diff --git a/styles/h5p.css b/styles/h5p.css index dfb92db..141be24 100644 --- a/styles/h5p.css +++ b/styles/h5p.css @@ -5,7 +5,7 @@ font-family: 'h5p'; src:url('../fonts/h5p.eot?-9qymee'); src:url('../fonts/h5p.eot?#iefix-9qymee') format('embedded-opentype'), - url('../fonts/h5p.woff?-9qymee') format('woff'), + url('data:application/font-woff;base64,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') format('woff'), url('../fonts/h5p.ttf?-9qymee') format('truetype'), url('../fonts/h5p.svg?-9qymee#h5p') format('svg'); font-weight: normal; From c91391e0bc03f6622d2ea83afba5eed1ae49387e Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Wed, 4 Mar 2015 11:13:32 +0100 Subject: [PATCH 28/75] Fixed URLs. Finishing touches on the time tracking. --- js/h5p-content-upgrade-worker.js | 16 +-------------- js/h5p-content-upgrade.js | 35 ++++++++++++++++++-------------- 2 files changed, 21 insertions(+), 30 deletions(-) diff --git a/js/h5p-content-upgrade-worker.js b/js/h5p-content-upgrade-worker.js index 8b5f4f5..c201b54 100644 --- a/js/h5p-content-upgrade-worker.js +++ b/js/h5p-content-upgrade-worker.js @@ -1,6 +1,5 @@ var H5P = H5P || {}; -importScripts('/wp-content/plugins/h5p/h5p-php-library/js/h5p-version.js'); -importScripts('/wp-content/plugins/h5p/h5p-php-library/js/h5p-content-upgrade-process.js'); +importScripts('h5p-version.js', 'h5p-content-upgrade-process.js'); var libraryLoadedCallback; @@ -61,16 +60,3 @@ onmessage = function (event) { messageHandlers[event.data.action].call(this, event.data); } }; - - -// if (library.upgradesScript) { -// self.loadScript(library.upgradesScript, function (err) { -// if (err) { -// err = info.errorScript.replace('%lib', name + ' ' + version); -// } -// next(err, library); -// }); -// } -// else { -// next(null, library); -// } diff --git a/js/h5p-content-upgrade.js b/js/h5p-content-upgrade.js index 63addce..05667d8 100644 --- a/js/h5p-content-upgrade.js +++ b/js/h5p-content-upgrade.js @@ -78,8 +78,8 @@ // Create throbber with loading text and progress self.throbber = new Throbber(info.inProgress.replace('%ver', self.version)); -self.started = new Date().getTime(); -self.io = 0; + self.started = new Date().getTime(); + self.io = 0; // Track number of working self.working = 0; @@ -98,8 +98,8 @@ self.io = 0; start(); } else { - // No workers, do the job our self - self.loadScript('/wp-content/plugins/h5p/h5p-php-library/js/h5p-content-upgrade-process.js', start); + // No workers, do the job ourselves + self.loadScript(info.scriptBaseUrl + '/h5p-content-upgrade-process.js' + info.buster, start); } } @@ -141,7 +141,7 @@ self.io = 0; }; for (var i = 0; i < numWorkers; i++) { - self.workers[i] = new Worker('/wp-content/plugins/h5p/h5p-php-library/js/h5p-content-upgrade-worker.js'); + self.workers[i] = new Worker(info.scriptBaseUrl + '/h5p-content-upgrade-worker.js' + info.buster); self.workers[i].onmessage = function (event) { if (event.data.action !== undefined && messageHandlers[event.data.action]) { messageHandlers[event.data.action].call(this, event.data); @@ -158,17 +158,20 @@ self.io = 0; ContentUpgrade.prototype.nextBatch = function (outData) { var self = this; -var start = new Date().getTime(); + // Track time spent on IO + var start = new Date().getTime(); $.post(info.infoUrl, outData, function (inData) { -self.io += new Date().getTime() - start; + self.io += new Date().getTime() - start; if (!(inData instanceof Object)) { // Print errors from backend return self.setStatus(inData); } if (inData.left === 0) { var total = new Date().getTime() - self.started; - console.log('Upgrade took ' + total + 'ms'); - console.log((self.io/(total/100)) + ' % of the time went to IO (' + self.io + 'ms)'); + + if (window.console && console.log) { + console.log('The upgrade process took ' + (total / 1000) + ' seconds. (' + (Math.round((self.io / (total / 100)) * 100) / 100) + ' % IO)' ); + } // Terminate workers self.terminate(); @@ -347,17 +350,18 @@ self.io += new Date().getTime() - start; return; } -var start = new Date().getTime(); + // Track time spent loading + var start = new Date().getTime(); librariesCache[key] = true; $.ajax({ dataType: 'json', cache: true, url: info.libraryBaseUrl + '/' + key }).fail(function () { -self.io += new Date().getTime() - start; + self.io += new Date().getTime() - start; next(info.errorData.replace('%lib', name + ' ' + version)); }).done(function (library) { -self.io += new Date().getTime() - start; + self.io += new Date().getTime() - start; librariesCache[key] = library; next(null, library); @@ -382,17 +386,18 @@ self.io += new Date().getTime() - start; return; } -var start = new Date().getTime(); + // Track time spent loading + var start = new Date().getTime(); $.ajax({ dataType: 'script', cache: true, url: url }).fail(function () { -self.io += new Date().getTime() - start; + self.io += new Date().getTime() - start; next(true); }).done(function () { scriptsCache[url] = true; -self.io += new Date().getTime() - start; + self.io += new Date().getTime() - start; next(); }); }; From 6d4caf855d008adcb4deded897decbaa6c212be7 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Wed, 4 Mar 2015 11:30:20 +0100 Subject: [PATCH 29/75] Fixed error handling. --- js/h5p-content-upgrade-worker.js | 2 +- js/h5p-content-upgrade.js | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/js/h5p-content-upgrade-worker.js b/js/h5p-content-upgrade-worker.js index c201b54..4cd0047 100644 --- a/js/h5p-content-upgrade-worker.js +++ b/js/h5p-content-upgrade-worker.js @@ -23,7 +23,7 @@ var messageHandlers = { postMessage({ action: 'error', id: job.id, - err: err + err: err.message ? err.message : err }); return; diff --git a/js/h5p-content-upgrade.js b/js/h5p-content-upgrade.js index 05667d8..cd50b86 100644 --- a/js/h5p-content-upgrade.js +++ b/js/h5p-content-upgrade.js @@ -381,6 +381,8 @@ * @param {Function} next */ ContentUpgrade.prototype.loadScript = function (url, next) { + var self = this; + if (scriptsCache[url] !== undefined) { next(); return; @@ -406,14 +408,16 @@ * */ ContentUpgrade.prototype.printError = function (error) { + var self = this; + if (error.type === 'errorParamsBroken') { - error = info.errorContent.replace('%id', error.id) + ' ' + info.errorParamsBroken; // TODO: Translate! + error = info.errorContent.replace('%id', error.id) + ' ' + info.errorParamsBroken; } else if (error.type === 'scriptMissing') { - error.err = info.errorScript.replace('%lib', error.library); + error = info.errorScript.replace('%lib', error.library); } - self.setStatus('

    ' + info.error + '
    ' + error.err + '

    '); + self.setStatus('

    ' + info.error + '
    ' + error + '

    '); }; })(H5P.jQuery, H5P.Version); From b45bfe770bfa3236a2a35e750671fea43ae5132d Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Fri, 6 Mar 2015 12:56:41 +0100 Subject: [PATCH 30/75] Critical bug fix for safari --- js/h5p.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index b93d9a7..7ed4074 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -215,12 +215,11 @@ H5P.init = function () { // Insert H5Ps that should be in iframes. H5P.jQuery("iframe.h5p-iframe").each(function () { var contentId = H5P.jQuery(this).data('content-id'); + this.contentWindow.H5P = this.contentWindow.H5P || {}; + this.contentWindow.H5P.externalEmbed = false; this.contentDocument.open(); this.contentDocument.write('' + H5P.getHeadTags(contentId) + '
    '); this.contentDocument.close(); - this.contentWindow.H5P = { - externalEmbed: false - }; }); }; From 72db596bac97c8755e015447c051c1453fefd51d Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Tue, 10 Mar 2015 09:53:33 +0100 Subject: [PATCH 31/75] Make sure H5P.externalEmbed is set inside the iframe. Run H5P.init from external resource to ensure correct execution order in IE9. --- js/h5p.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index 7ed4074..9a08a7a 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -215,8 +215,6 @@ H5P.init = function () { // Insert H5Ps that should be in iframes. H5P.jQuery("iframe.h5p-iframe").each(function () { var contentId = H5P.jQuery(this).data('content-id'); - this.contentWindow.H5P = this.contentWindow.H5P || {}; - this.contentWindow.H5P.externalEmbed = false; this.contentDocument.open(); this.contentDocument.write('' + H5P.getHeadTags(contentId) + '
    '); this.contentDocument.close(); @@ -261,7 +259,7 @@ H5P.getHeadTags = function (contentId) { createStyleTags(H5PIntegration.contents['cid-' + contentId].styles) + createScriptTags(H5PIntegration.core.scripts) + createScriptTags(H5PIntegration.contents['cid-' + contentId].scripts) + - ''; + ''; }; H5P.communicator = (function () { @@ -1387,3 +1385,11 @@ H5P.on = function(instance, eventType, handler) { instance.$.on(eventType, handler); } }; + + +H5P.jQuery(document).ready(function () { + if (!H5P.preventInit) { + // Start script need to be an external resource to load in correct order for IE9. + H5P.init(); + } +}); From 2f44edd65ad6db8bf1b9c5739443face290fa610 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Tue, 10 Mar 2015 10:08:35 +0100 Subject: [PATCH 32/75] Prevent double init. Ie9 fix. --- embed.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/embed.php b/embed.php index 7e6250b..430b5a0 100644 --- a/embed.php +++ b/embed.php @@ -14,9 +14,6 @@
    From efd79c9e796724ce259a6857e10d41b9b53a2262 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Tue, 10 Mar 2015 10:09:31 +0100 Subject: [PATCH 33/75] Support init target. --- js/h5p.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index 9a08a7a..4223230 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -39,13 +39,15 @@ H5P.canHasFullScreen = (H5P.isFramed && H5P.externalEmbed !== false) ? (document * Initialize H5P content. * Scans for ".h5p-content" in the document and initializes H5P instances where found. */ -H5P.init = function () { +H5P.init = function (target) { // Useful jQuery object. - H5P.$body = H5P.jQuery(document.body); + if (H5P.$body === undefined) { + H5P.$body = H5P.jQuery(document.body); + } // H5Ps added in normal DIV. - var $containers = H5P.jQuery(".h5p-content").each(function () { - var $element = H5P.jQuery(this); + var $containers = H5P.jQuery('.h5p-content:not(.h5p-initialized)', target).each(function () { + var $element = H5P.jQuery(this).addClass('h5p-initialized'); var $container = H5P.jQuery('
    ').appendTo($element); var contentId = $element.data('content-id'); var contentData = H5PIntegration.contents['cid-' + contentId]; @@ -213,8 +215,8 @@ H5P.init = function () { }); // Insert H5Ps that should be in iframes. - H5P.jQuery("iframe.h5p-iframe").each(function () { - var contentId = H5P.jQuery(this).data('content-id'); + H5P.jQuery('iframe.h5p-iframe:not(.h5p-initialized)', target).each(function () { + var contentId = H5P.jQuery(this).addClass('h5p-initialized').data('content-id'); this.contentDocument.open(); this.contentDocument.write('' + H5P.getHeadTags(contentId) + '
    '); this.contentDocument.close(); @@ -1390,6 +1392,6 @@ H5P.on = function(instance, eventType, handler) { H5P.jQuery(document).ready(function () { if (!H5P.preventInit) { // Start script need to be an external resource to load in correct order for IE9. - H5P.init(); + H5P.init(document.body); } }); From 7ef9ffbf1f470082e15f276606750b57da46753b Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Wed, 11 Mar 2015 11:40:07 +0100 Subject: [PATCH 34/75] Added default sort option to data view. Improved buttons. --- js/h5p-data-view.js | 17 +++++++++-------- js/h5p-utils.js | 27 ++++++++++++++++++++++++--- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/js/h5p-data-view.js b/js/h5p-data-view.js index 323290a..d856933 100644 --- a/js/h5p-data-view.js +++ b/js/h5p-data-view.js @@ -32,8 +32,9 @@ var H5PDataView = (function ($) { * search in column 2. * @param {Function} loaded * Callback for when data has been loaded. + * @param {Object} order */ - function H5PDataView(container, source, headers, l10n, classes, filters, loaded) { + function H5PDataView(container, source, headers, l10n, classes, filters, loaded, order) { var self = this; self.$container = $(container).addClass('h5p-data-view').html(''); @@ -44,6 +45,7 @@ var H5PDataView = (function ($) { self.classes = (classes === undefined ? {} : classes); self.filters = (filters === undefined ? [] : filters); self.loaded = loaded; + self.order = order; self.limit = 20; self.offset = 0; @@ -68,8 +70,8 @@ var H5PDataView = (function ($) { url += (url.indexOf('?') === -1 ? '?' : '&') + 'offset=' + self.offset + '&limit=' + self.limit; // Add sorting - if (self.sortBy !== undefined && self.sortDir !== undefined) { - url += '&sortBy=' + self.sortBy + '&sortDir=' + self.sortDir; + if (self.order !== undefined) { + url += '&sortBy=' + self.order.by + '&sortDir=' + self.order.dir; } // Add filters @@ -144,12 +146,11 @@ var H5PDataView = (function ($) { // Create new table self.table = new H5PUtils.Table(self.classes, self.headers); - self.table.setHeaders(self.headers, function (col, dir) { - // Sorting column or direction has changed callback. - self.sortBy = col; - self.sortDir = dir; + self.table.setHeaders(self.headers, function (order) { + // Sorting column or direction has changed. + self.order = order; self.loadData(); - }); + }, self.order); self.table.appendTo(self.$container); } diff --git a/js/h5p-utils.js b/js/h5p-utils.js index d90ca58..423eb30 100644 --- a/js/h5p-utils.js +++ b/js/h5p-utils.js @@ -182,18 +182,30 @@ var H5PUtils = H5PUtils || {}; if (sortByCol !== undefined && col.sortable === true) { // Make sortable options.role = 'button'; - options.tabIndex = 1; + options.tabIndex = 0; // This is the first sortable column, use as default sort if (sortCol === undefined) { sortCol = id; sortDir = 0; + } + + // This is the sort column + if (sortCol === id) { options['class'] = 'h5p-sort'; + if (sortDir === 1) { + options['class'] += ' h5p-reverse'; + } } options.on.click = function () { sort($th, id); }; + options.on.keypress = function (event) { + if ((event.charCode || event.keyCode) === 32) { // Space + sort($th, id); + } + }; } } @@ -232,7 +244,10 @@ var H5PUtils = H5PUtils || {}; sortDir = 0; } - sortByCol(sortCol, sortDir); + sortByCol({ + by: sortCol, + dir: sortDir + }); }; /** @@ -244,11 +259,17 @@ var H5PUtils = H5PUtils || {}; * "text" and "sortable". E.g. * [{text: 'Col 1', sortable: true}, 'Col 2', 'Col 3'] * @param {Function} sort Callback which is runned when sorting changes + * @param {Object} [order] */ - this.setHeaders = function (cols, sort) { + this.setHeaders = function (cols, sort, order) { numCols = cols.length; sortByCol = sort; + if (order) { + sortCol = order.by; + sortDir = order.dir; + } + // Create new head var $newThead = $(''); var $tr = $('').appendTo($newThead); From fb823285357bfd0f616d02f4d10766feb40208a0 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Fri, 13 Mar 2015 12:51:31 +0100 Subject: [PATCH 35/75] Fullscreen fix --- js/h5p.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index 4223230..a9c2617 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -33,8 +33,6 @@ else if (document.documentElement.msRequestFullscreen) { // Keep track of when the H5Ps where started H5P.opened = {}; -H5P.canHasFullScreen = (H5P.isFramed && H5P.externalEmbed !== false) ? (document.fullscreenEnabled || document.webkitFullscreenEnabled || document.mozFullScreenEnabled || document.msFullscreenEnabled) : true; - /** * Initialize H5P content. * Scans for ".h5p-content" in the document and initializes H5P instances where found. @@ -45,6 +43,11 @@ H5P.init = function (target) { H5P.$body = H5P.jQuery(document.body); } + // Determine if we can use full screen + if (H5P.canHasFullScreen === undefined) { + H5P.canHasFullScreen = (H5P.isFramed && H5P.externalEmbed !== false) ? (document.fullscreenEnabled || document.webkitFullscreenEnabled || document.mozFullScreenEnabled || document.msFullscreenEnabled) : true; + } + // H5Ps added in normal DIV. var $containers = H5P.jQuery('.h5p-content:not(.h5p-initialized)', target).each(function () { var $element = H5P.jQuery(this).addClass('h5p-initialized'); From 1b28c7b128ea442ca7353b1884ddde69b2c92497 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Wed, 18 Mar 2015 14:25:55 +0100 Subject: [PATCH 36/75] Use different vars for URL and file path to avoid having allow_url_fopen = On. --- h5p.classes.php | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/h5p.classes.php b/h5p.classes.php index 47df2f2..6945ca1 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -14,7 +14,7 @@ interface H5PFrameworkInterface { * - h5pVersion: The version of the H5P plugin/module */ public function getPlatformInfo(); - + /** * Fetches a file from a remote server using HTTP GET @@ -74,12 +74,6 @@ interface H5PFrameworkInterface { */ public function getUploadedH5pFolderPath(); - /** - * @return string - * Path to the folder where all h5p files are stored - */ - public function getH5pPath(); - /** * Get the path to the last uploaded h5p file * @@ -1270,7 +1264,7 @@ class H5PStorage { $contentId = $this->h5pC->saveContent($content, $contentMainId); $this->contentId = $contentId; - $contents_path = $this->h5pF->getH5pPath() . DIRECTORY_SEPARATOR . 'content'; + $contents_path = $this->h5pC->path . DIRECTORY_SEPARATOR . 'content'; if (!is_dir($contents_path)) { mkdir($contents_path, 0777, true); } @@ -1298,7 +1292,7 @@ class H5PStorage { $oldOnes = 0; // Find libraries directory and make sure it exists - $libraries_path = $this->h5pF->getH5pPath() . DIRECTORY_SEPARATOR . 'libraries'; + $libraries_path = $this->h5pC->path . DIRECTORY_SEPARATOR . 'libraries'; if (!is_dir($libraries_path)) { mkdir($libraries_path, 0777, true); } @@ -1396,8 +1390,9 @@ class H5PStorage { * The content id */ public function deletePackage($contentId) { - H5PCore::deleteFileTree($this->h5pF->getH5pPath() . DIRECTORY_SEPARATOR . 'content' . DIRECTORY_SEPARATOR . $contentId); + H5PCore::deleteFileTree($this->h5pC->path . DIRECTORY_SEPARATOR . 'content' . DIRECTORY_SEPARATOR . $contentId); $this->h5pF->deleteContentData($contentId); + // TODO: Delete export? } /** @@ -1430,8 +1425,8 @@ class H5PStorage { * The main id of the new content (used in frameworks that support revisioning) */ public function copyPackage($contentId, $copyFromId, $contentMainId = NULL) { - $source_path = $this->h5pF->getH5pPath() . DIRECTORY_SEPARATOR . 'content' . DIRECTORY_SEPARATOR . $copyFromId; - $destination_path = $this->h5pF->getH5pPath() . DIRECTORY_SEPARATOR . 'content' . DIRECTORY_SEPARATOR . $contentId; + $source_path = $this->h5pC->path . DIRECTORY_SEPARATOR . 'content' . DIRECTORY_SEPARATOR . $copyFromId; + $destination_path = $this->h5pC->path . DIRECTORY_SEPARATOR . 'content' . DIRECTORY_SEPARATOR . $contentId; $this->h5pC->copyFileTree($source_path, $destination_path); $this->h5pF->copyLibraryUsage($contentId, $copyFromId, $contentMainId); @@ -1467,7 +1462,7 @@ Class H5PExport { * @return string */ public function createExportFile($content) { - $h5pDir = $this->h5pF->getH5pPath() . DIRECTORY_SEPARATOR; + $h5pDir = $this->h5pC->path . DIRECTORY_SEPARATOR; $tempPath = $h5pDir . 'temp' . DIRECTORY_SEPARATOR . $content['id']; $zipPath = $h5pDir . 'exports' . DIRECTORY_SEPARATOR . $content['id'] . '.h5p'; @@ -1548,7 +1543,7 @@ Class H5PExport { * Identifier for the H5P */ public function deleteExport($contentId) { - $h5pDir = $this->h5pF->getH5pPath() . DIRECTORY_SEPARATOR; + $h5pDir = $this->h5pC->path . DIRECTORY_SEPARATOR; $zipPath = $h5pDir . 'exports' . DIRECTORY_SEPARATOR . $contentId . '.h5p'; if (file_exists($zipPath)) { unlink($zipPath); @@ -1617,11 +1612,12 @@ class H5PCore { * @param boolean $export enabled? * @param int $development_mode mode. */ - public function __construct($H5PFramework, $path, $language = 'en', $export = FALSE, $development_mode = H5PDevelopment::MODE_NONE) { + public function __construct($H5PFramework, $path, $url, $language = 'en', $export = FALSE, $development_mode = H5PDevelopment::MODE_NONE) { $this->h5pF = $H5PFramework; $this->h5pF = $H5PFramework; $this->path = $path; + $this->url = $url; $this->exportEnabled = $export; $this->development_mode = $development_mode; @@ -1787,7 +1783,7 @@ class H5PCore { $urls = array(); foreach ($assets as $asset) { - $urls[] = $asset->path . $asset->version; + $urls[] = $this->url . $asset->path . $asset->version; } return $urls; @@ -1806,7 +1802,7 @@ class H5PCore { ); foreach ($dependencies as $dependency) { if (isset($dependency['path']) === FALSE) { - $dependency['path'] = $this->path . '/libraries/' . H5PCore::libraryToString($dependency, TRUE); + $dependency['path'] = '/libraries/' . H5PCore::libraryToString($dependency, TRUE); $dependency['preloadedJs'] = explode(',', $dependency['preloadedJs']); $dependency['preloadedCss'] = explode(',', $dependency['preloadedCss']); } From 51a1d333e2129ad314556ed52c9742d91ebb05e4 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Thu, 19 Mar 2015 16:58:48 +0100 Subject: [PATCH 37/75] Improved packaging. Made sure zip format is followed by always using forward slashes. This will make h5p export work on Windows. --- h5p.classes.php | 51 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/h5p.classes.php b/h5p.classes.php index 47df2f2..5a1884a 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -14,7 +14,7 @@ interface H5PFrameworkInterface { * - h5pVersion: The version of the H5P plugin/module */ public function getPlatformInfo(); - + /** * Fetches a file from a remote server using HTTP GET @@ -1519,28 +1519,53 @@ Class H5PExport { $results = print_r(json_encode($h5pJson), true); file_put_contents($tempPath . DIRECTORY_SEPARATOR . 'h5p.json', $results); + // Get a complete file list from our tmp dir + $files = array(); + self::populateFileList($tempPath, $files); + // Create new zip instance. $zip = new ZipArchive(); $zip->open($zipPath, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE); - // Get all files and folders in $tempPath - $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($tempPath . DIRECTORY_SEPARATOR)); - // Add files to zip - foreach ($iterator as $key => $value) { - $test = '.'; - // Do not add the folders '.' and '..' to the zip. This will make zip invalid. - if (substr_compare($key, $test, -strlen($test), strlen($test)) !== 0) { - // Get files path in $tempPath - $filePath = explode($tempPath . DIRECTORY_SEPARATOR, $key); - // Add files to the zip with the intended file-structure - $zip->addFile($key, $filePath[1]); - } + // Add all the files from the tmp dir. + foreach ($files as $file) { + // Please note that the zip format has no concept of folders, we must + // use forward slashes to separate our directories. + $zip->addFile($file->absolutePath, $file->relativePath); } + // Close zip and remove temp dir $zip->close(); H5PCore::deleteFileTree($tempPath); } + /** + * Recursive function the will add the files of the given directory to the + * given files list. All files are objects with an absolute path and + * a relative path. The relative path is forward slashes only! Great for + * use in zip files and URLs. + * + * @param string $dir path + * @param array $files list + * @param string $relative prefix. Optional + */ + private static function populateFileList($dir, &$files, $relative = '') { + $strip = strlen($dir) + 1; + foreach (glob($dir . DIRECTORY_SEPARATOR . '*') as $file) { + $rel = $relative . substr($file, $strip); + if (is_dir($file)) { + self::populateFileList($file, $files, $rel . '/'); + } + else { + // TODO: Should we filter out files that aren't in the whitelist? + $files[] = (object) array( + 'absolutePath' => $file, + 'relativePath' => $rel + ); + } + } + } + /** * Delete .h5p file * From 8494e85c3e572e366d4c650c7a3b882e9eb22e5f Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Thu, 19 Mar 2015 19:27:44 +0100 Subject: [PATCH 38/75] Remove todo items --- h5p.classes.php | 8 -------- 1 file changed, 8 deletions(-) diff --git a/h5p.classes.php b/h5p.classes.php index 5a1884a..b6df5af 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -1487,8 +1487,6 @@ Class H5PExport { // Build h5p.json $h5pJson = array ( 'title' => $content['title'], - // TODO - stop using 'und', this is not the preferred way. - // Either remove language from the json if not existing, or use "language": null 'language' => (isset($content['language']) && strlen(trim($content['language'])) !== 0) ? $content['language'] : 'und', 'mainLibrary' => $content['library']['name'], 'embedTypes' => $embedTypes, @@ -1557,7 +1555,6 @@ Class H5PExport { self::populateFileList($file, $files, $rel . '/'); } else { - // TODO: Should we filter out files that aren't in the whitelist? $files[] = (object) array( 'absolutePath' => $file, 'relativePath' => $rel @@ -1742,8 +1739,6 @@ class H5PCore { // Recreate export file $exporter = new H5PExport($this->h5pF, $this); $exporter->createExportFile($content); - - // TODO: Should we rather create the file once first accessed, like imagecache? } // Cache. @@ -2248,8 +2243,6 @@ class H5PCore { /** * Helper function for creating markup for the unsupported libraries list * - * TODO: Make help text translatable - * * @return string Html * */ public function createMarkupForUnsupportedLibraryList($libraries) { @@ -2350,7 +2343,6 @@ class H5PContentValidator { // Keep track of the libraries we load to avoid loading it multiple times. $this->libraries = array(); - // TODO: Should this possible be done in core's loadLibrary? This might be done multiple places. // Keep track of all dependencies for the given content. $this->dependencies = array(); From e6e3ee75597b4cd8d3ad375013c33f728b8a8e17 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Fri, 20 Mar 2015 11:28:21 +0100 Subject: [PATCH 39/75] Allow assets to be relative to another location. Allow assets to contain external URLs. --- h5p.classes.php | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/h5p.classes.php b/h5p.classes.php index 6945ca1..13c196d 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -1753,8 +1753,9 @@ class H5PCore { * @param array $dependency * @param string $type * @param array $assets + * @param string $prefix Optional. Make paths relative to another dir. */ - private function getDependencyAssets($dependency, $type, &$assets) { + private function getDependencyAssets($dependency, $type, &$assets, $prefix = '') { // Check if dependency has any files of this type if (empty($dependency[$type]) || $dependency[$type][0] === '') { return; @@ -1767,7 +1768,7 @@ class H5PCore { foreach ($dependency[$type] as $file) { $assets[] = (object) array( - 'path' => $dependency['path'] . '/' . trim(is_array($file) ? $file['path'] : $file), + 'path' => $prefix . $dependency['path'] . '/' . trim(is_array($file) ? $file['path'] : $file), 'version' => $dependency['version'] ); } @@ -1783,7 +1784,19 @@ class H5PCore { $urls = array(); foreach ($assets as $asset) { - $urls[] = $this->url . $asset->path . $asset->version; + $url = $asset->path; + + // Add URL prefix if not external + if (strpos($asset->path, '://') === FALSE) { + $url = $this->url . $url; + } + + // Add version/cache buster if set + if (isset($asset->version)) { + $url .= $asset->version; + } + + $urls[] = $url; } return $urls; @@ -1793,9 +1806,10 @@ class H5PCore { * Return file paths for all dependecies files. * * @param array $dependencies + * @param string $prefix Optional. Make paths relative to another dir. * @return array files. */ - public function getDependenciesFiles($dependencies) { + public function getDependenciesFiles($dependencies, $prefix = '') { $files = array( 'scripts' => array(), 'styles' => array() @@ -1808,8 +1822,8 @@ class H5PCore { } $dependency['version'] = "?ver={$dependency['majorVersion']}.{$dependency['minorVersion']}.{$dependency['patchVersion']}"; - $this->getDependencyAssets($dependency, 'preloadedJs', $files['scripts']); - $this->getDependencyAssets($dependency, 'preloadedCss', $files['styles']); + $this->getDependencyAssets($dependency, 'preloadedJs', $files['scripts'], $prefix); + $this->getDependencyAssets($dependency, 'preloadedCss', $files['styles'], $prefix); } return $files; } From 76b0fc04f8c9f3d33ba9c95f38e9bb2fa6d5751d Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Sat, 21 Mar 2015 14:16:31 +0100 Subject: [PATCH 40/75] Make UUID creator available for everyone --- js/h5p-x-api-event.js | 5 +---- js/h5p.js | 12 ++++++++++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/js/h5p-x-api-event.js b/js/h5p-x-api-event.js index 141e232..abc7cca 100644 --- a/js/h5p-x-api-event.js +++ b/js/h5p-x-api-event.js @@ -111,10 +111,7 @@ H5P.XAPIEvent.prototype.setActor = function() { uuid = localStorage.H5PUserUUID; } else { - uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(char) { - var random = Math.random()*16|0, newChar = char === 'x' ? random : (random&0x3|0x8); - return newChar.toString(16); - }); + uuid = H5P.createUUID(); localStorage.H5PUserUUID = uuid; } this.data.statement.actor = { diff --git a/js/h5p.js b/js/h5p.js index a9c2617..c80d2a5 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -1391,6 +1391,18 @@ H5P.on = function(instance, eventType, handler) { } }; +/** + * Create UUID + * + * @returns {String} UUID + */ +H5P.createUUID = function() { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(char) { + var random = Math.random()*16|0, newChar = char === 'x' ? random : (random&0x3|0x8); + return newChar.toString(16); + }); +}; + H5P.jQuery(document).ready(function () { if (!H5P.preventInit) { From 313bb757ba77246ea63820533d6d3944e2999b8d Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Sat, 21 Mar 2015 16:45:38 +0100 Subject: [PATCH 41/75] Add bubble system and xAPI context --- h5p.classes.php | 5 ++++- js/h5p-event-dispatcher.js | 11 +++++++++++ js/h5p-x-api-event.js | 40 +++++++++++++++++++++++++++++++++++++- js/h5p.js | 22 +++++++++++++++++++-- 4 files changed, 74 insertions(+), 4 deletions(-) diff --git a/h5p.classes.php b/h5p.classes.php index b6df5af..c4c703d 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -2731,11 +2731,14 @@ class H5PContentValidator { 'type' => 'group', 'fields' => $library['semantics'], ), FALSE); - $validkeys = array('library', 'params'); + $validkeys = array('library', 'params', 'uuid'); if (isset($semantics->extraAttributes)) { $validkeys = array_merge($validkeys, $semantics->extraAttributes); } $this->filterParams($value, $validkeys); + if (isset($value->uuid) && ! preg_match('/^\{?[A-Z0-9]{8}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{12}\}?$/', $value->uuid)) { + unset($value->uuid); + } // Find all dependencies for this library $depkey = 'preloaded-' . $library['machineName']; diff --git a/js/h5p-event-dispatcher.js b/js/h5p-event-dispatcher.js index cb40cd6..e164564 100644 --- a/js/h5p-event-dispatcher.js +++ b/js/h5p-event-dispatcher.js @@ -8,6 +8,13 @@ var H5P = H5P || {}; H5P.Event = function(type, data) { this.type = type; this.data = data; + var bubbles = true; + this.preventBubbling = function() { + bubbles = false; + }; + this.getBubbles = function() { + return bubbles; + }; }; H5P.EventDispatcher = (function () { @@ -132,6 +139,7 @@ H5P.EventDispatcher = (function () { * argument */ this.trigger = function (event, eventData) { + console.log(event); if (event === undefined) { return; } @@ -148,6 +156,9 @@ H5P.EventDispatcher = (function () { for (var i = 0; i < triggers[event.type].length; i++) { triggers[event.type][i].listener.call(triggers[event.type][i].thisArg, event); } + if (event.getBubbles() && typeof self.parent === 'function' && typeof self.parent.trigger === 'function') { + self.parent.trigger(event); + } }; } diff --git a/js/h5p-x-api-event.js b/js/h5p-x-api-event.js index abc7cca..0d18d84 100644 --- a/js/h5p-x-api-event.js +++ b/js/h5p-x-api-event.js @@ -79,12 +79,20 @@ H5P.XAPIEvent.prototype.getVerb = function(full) { H5P.XAPIEvent.prototype.setObject = function(instance) { if (instance.contentId) { this.data.statement.object = { - 'id': H5PIntegration.contents['cid-' + instance.contentId].url, + 'id': this.getContentXAPIId(instance), 'objectType': 'Activity', 'extensions': { 'http://h5p.org/x-api/h5p-local-content-id': instance.contentId } }; + if (instance.h5pUUID) { + this.data.statement.object.extensions['http://h5p.org/x-api/h5p-uuid'] = instance.h5pUUID; + } + if (typeof instance.getH5PTitle === 'function') { + this.data.statement.object.description = { + "en-US": instance.getH5PTitle() + }; + } } else { // Not triggered by an H5P content type... @@ -94,6 +102,25 @@ H5P.XAPIEvent.prototype.setObject = function(instance) { } }; +/** + * Helperfunction to set the context part of the statement. + * + * @param {object} instance - the H5P instance + */ +H5P.XAPIEvent.prototype.setContext = function(instance) { + if (instance.parent && instance.parent.contentId || instance.parent.uuid) { + var parentId = instance.parent.uuid === undefined ? instance.parent.contentId : instance.parent.uuid; + this.data.statement.context = { + "parent": [ + { + "id": getContentXAPIId(instance.parent), + "objectType": "Activity" + } + ] + }; + } +}; + /** * Helper function to set the actor, email and name will be added automatically */ @@ -142,6 +169,17 @@ H5P.XAPIEvent.prototype.getScore = function() { return this.getVerifiedStatementValue(['result', 'score', 'raw']); }; +H5P.XAPIEvent.prototype.getContentXAPIId = function (instance) { + var xAPIId; + if (instance.contentId) { + xAPIId = H5PIntegration.contents['cid-' + instance.contentId].url; + if (instance.h5pUUID) { + xAPIId += '?uuid=' + instance.h5pUUID; + } + } + return xAPIId; +} + /** * Figure out if a property exists in the statement and return it * diff --git a/js/h5p.js b/js/h5p.js index c80d2a5..0370476 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -544,7 +544,7 @@ H5P.classFromName = function (name) { * @param {Object} The parent of this H5P * @return {Object} Instance. */ -H5P.newRunnable = function (library, contentId, $attachTo, skipResize) { +H5P.newRunnable = function (library, contentId, $attachTo, skipResize, parent) { var nameSplit, versionSplit; try { nameSplit = library.library.split(' ', 2); @@ -575,7 +575,16 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize) { return H5P.error('Unable to find constructor for: ' + library.library); } - var instance = new constructor(library.params, contentId); + var extras = {}; + + if (library.uuid) { + extras.uuid = library.uuid; + } + if (parent) { + extras.parent = parent; + } + + var instance = new constructor(library.params, contentId, extras); if (instance.$ === undefined) { instance.$ = H5P.jQuery(instance); @@ -584,6 +593,12 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize) { if (instance.contentId === undefined) { instance.contentId = contentId; } + if (instance.uuid === undefined && library.uuid) { + instance.uuid = library.uuid; + } + if (instance.parent === undefined && parent) { + instance.parent = parent; + } if ($attachTo !== undefined) { instance.attach($attachTo); @@ -1403,6 +1418,9 @@ H5P.createUUID = function() { }); }; +H5P.createH5PTitle = function(rawTitle) { + return H5P.jQuery('
    ').text(rawTitle).text().substr(0, 60); +}; H5P.jQuery(document).ready(function () { if (!H5P.preventInit) { From 3da0de46264895c946db1b0955618e6dc230cfd3 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Sun, 22 Mar 2015 11:17:58 +0100 Subject: [PATCH 42/75] Fix bubble system --- js/h5p-event-dispatcher.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/js/h5p-event-dispatcher.js b/js/h5p-event-dispatcher.js index e164564..31043f9 100644 --- a/js/h5p-event-dispatcher.js +++ b/js/h5p-event-dispatcher.js @@ -5,10 +5,16 @@ var H5P = H5P || {}; * The Event class for the EventDispatcher * @class */ -H5P.Event = function(type, data) { +H5P.Event = function(type, data, extras) { this.type = type; this.data = data; var bubbles = true; + if (extras === undefined) { + extras = {}; + } + if (extras.bubbles === false) { + bubbles = false; + } this.preventBubbling = function() { bubbles = false; }; @@ -139,7 +145,6 @@ H5P.EventDispatcher = (function () { * argument */ this.trigger = function (event, eventData) { - console.log(event); if (event === undefined) { return; } @@ -156,7 +161,7 @@ H5P.EventDispatcher = (function () { for (var i = 0; i < triggers[event.type].length; i++) { triggers[event.type][i].listener.call(triggers[event.type][i].thisArg, event); } - if (event.getBubbles() && typeof self.parent === 'function' && typeof self.parent.trigger === 'function') { + if (event.getBubbles() && self.parent instanceof H5P.EventDispatcher && typeof self.parent.trigger === 'function') { self.parent.trigger(event); } }; From b79a5b61b83d28faf425a0b080fac7bbeb2d85bf Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Sun, 22 Mar 2015 12:42:07 +0100 Subject: [PATCH 43/75] uuid consists of lowercase letters --- h5p.classes.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/h5p.classes.php b/h5p.classes.php index c4c703d..2fba0f2 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -2736,8 +2736,8 @@ class H5PContentValidator { $validkeys = array_merge($validkeys, $semantics->extraAttributes); } $this->filterParams($value, $validkeys); - if (isset($value->uuid) && ! preg_match('/^\{?[A-Z0-9]{8}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{12}\}?$/', $value->uuid)) { - unset($value->uuid); + if (isset($value->uuid) && ! preg_match('/^\{?[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\}?$/', $value->uuid)) { + unset($value->uuid); } // Find all dependencies for this library From e14c24cc070a1c197fecefa14ccecdcf49f8a076 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Sun, 22 Mar 2015 12:43:07 +0100 Subject: [PATCH 44/75] Fix xAPI statement structure to follow the standard better --- js/h5p-x-api-event.js | 28 +++++++++++++--------------- js/h5p-x-api.js | 5 ++++- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/js/h5p-x-api-event.js b/js/h5p-x-api-event.js index 0d18d84..837b219 100644 --- a/js/h5p-x-api-event.js +++ b/js/h5p-x-api-event.js @@ -81,25 +81,21 @@ H5P.XAPIEvent.prototype.setObject = function(instance) { this.data.statement.object = { 'id': this.getContentXAPIId(instance), 'objectType': 'Activity', - 'extensions': { - 'http://h5p.org/x-api/h5p-local-content-id': instance.contentId + 'definition': { + 'extensions': { + 'http://h5p.org/x-api/h5p-local-content-id': instance.contentId + } } }; if (instance.h5pUUID) { this.data.statement.object.extensions['http://h5p.org/x-api/h5p-uuid'] = instance.h5pUUID; } if (typeof instance.getH5PTitle === 'function') { - this.data.statement.object.description = { + this.data.statement.object.definition.name = { "en-US": instance.getH5PTitle() }; } } - else { - // Not triggered by an H5P content type... - this.data.statement.object = { - 'objectType': 'Activity' - }; - } }; /** @@ -111,12 +107,14 @@ H5P.XAPIEvent.prototype.setContext = function(instance) { if (instance.parent && instance.parent.contentId || instance.parent.uuid) { var parentId = instance.parent.uuid === undefined ? instance.parent.contentId : instance.parent.uuid; this.data.statement.context = { - "parent": [ - { - "id": getContentXAPIId(instance.parent), - "objectType": "Activity" - } - ] + "contextActivities": { + "parent": [ + { + "id": this.getContentXAPIId(instance.parent), + "objectType": "Activity" + } + ] + } }; } }; diff --git a/js/h5p-x-api.js b/js/h5p-x-api.js index e4f4edb..f18cfd3 100644 --- a/js/h5p-x-api.js +++ b/js/h5p-x-api.js @@ -39,9 +39,12 @@ H5P.EventDispatcher.prototype.createXAPIEventTemplate = function(verb, extra) { event.data.statement[i] = extra[i]; } } - if (!('object' in event)) { + if (!('object' in event.data.statement)) { event.setObject(this); } + if (!('context' in event.data.statement)) { + event.setContext(this); + } return event; }; From 93f2bcc01a1c06ee6ac30477d17b9f71a5193e32 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Sun, 22 Mar 2015 12:44:55 +0100 Subject: [PATCH 45/75] Use uuid instead of h5pUUID --- js/h5p-x-api-event.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/h5p-x-api-event.js b/js/h5p-x-api-event.js index 837b219..12bfb36 100644 --- a/js/h5p-x-api-event.js +++ b/js/h5p-x-api-event.js @@ -171,8 +171,8 @@ H5P.XAPIEvent.prototype.getContentXAPIId = function (instance) { var xAPIId; if (instance.contentId) { xAPIId = H5PIntegration.contents['cid-' + instance.contentId].url; - if (instance.h5pUUID) { - xAPIId += '?uuid=' + instance.h5pUUID; + if (instance.uuid) { + xAPIId += '?uuid=' + instance.uuid; } } return xAPIId; From 5a882e4d55cc85bfec06bf80039a5fe022e997c6 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Sun, 22 Mar 2015 12:45:03 +0100 Subject: [PATCH 46/75] Improve title generator --- js/h5p.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index 0370476..5123091 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -576,7 +576,6 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize, parent) { } var extras = {}; - if (library.uuid) { extras.uuid = library.uuid; } @@ -1418,8 +1417,20 @@ H5P.createUUID = function() { }); }; -H5P.createH5PTitle = function(rawTitle) { - return H5P.jQuery('
    ').text(rawTitle).text().substr(0, 60); +H5P.createH5PTitle = function(rawTitle, maxLength) { + if (maxLength === undefined) { + maxLength = 60; + } + var title = H5P.jQuery('
    ') + .text( + // Strip tags + rawTitle.replace(/(<([^>]+)>)/ig,"") + // Escape + ).text(); + if (title.length > maxLength) { + title = title.substr(0, maxLength - 3) + '...'; + } + return title; }; H5P.jQuery(document).ready(function () { From 1c4a5e014b11cb957d2f2e4e5e564dedc2c82fb8 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Sun, 22 Mar 2015 13:12:44 +0100 Subject: [PATCH 47/75] Comment out prefixing that isn't working --- h5p.classes.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/h5p.classes.php b/h5p.classes.php index 955dab0..4073c71 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -1785,10 +1785,9 @@ class H5PCore { if ($type === 'preloadedCss' && (isset($dependency['dropCss']) && $dependency['dropCss'] === '1')) { return; } - foreach ($dependency[$type] as $file) { $assets[] = (object) array( - 'path' => $prefix . $dependency['path'] . '/' . trim(is_array($file) ? $file['path'] : $file), + 'path' => /*$prefix . */$dependency['path'] . '/' . trim(is_array($file) ? $file['path'] : $file), 'version' => $dependency['version'] ); } @@ -1808,7 +1807,7 @@ class H5PCore { // Add URL prefix if not external if (strpos($asset->path, '://') === FALSE) { - $url = $this->url . $url; + $url = /*$this->url .*/ $url; } // Add version/cache buster if set From c4e488f9c8a4c1c58f0c36da40cd5d54480307e5 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Sun, 22 Mar 2015 13:27:40 +0100 Subject: [PATCH 48/75] Get contentId from the right place in the statement --- js/h5p-x-api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/h5p-x-api.js b/js/h5p-x-api.js index f18cfd3..cc18943 100644 --- a/js/h5p-x-api.js +++ b/js/h5p-x-api.js @@ -71,7 +71,7 @@ H5P.xAPICompletedListener = function(event) { if (statement.verb.id === 'http://adlnet.gov/expapi/verbs/completed') { var score = statement.result.score.raw; var maxScore = statement.result.score.max; - var contentId = statement.object.extensions['http://h5p.org/x-api/h5p-local-content-id']; + var contentId = statement.object.definition.extensions['http://h5p.org/x-api/h5p-local-content-id']; H5P.setFinished(contentId, score, maxScore); } } From 9a204b3d8f7dceaa80c71224401e93c0a8de37a0 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Sun, 22 Mar 2015 15:14:28 +0100 Subject: [PATCH 49/75] Make sure title are added --- js/h5p-x-api-event.js | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/js/h5p-x-api-event.js b/js/h5p-x-api-event.js index 12bfb36..3ef34e6 100644 --- a/js/h5p-x-api-event.js +++ b/js/h5p-x-api-event.js @@ -87,13 +87,21 @@ H5P.XAPIEvent.prototype.setObject = function(instance) { } } }; - if (instance.h5pUUID) { - this.data.statement.object.extensions['http://h5p.org/x-api/h5p-uuid'] = instance.h5pUUID; + if (instance.uuid) { + this.data.statement.object.definition.extensions['http://h5p.org/x-api/h5p-uuid'] = instance.uuid; + // Don't set titles on main content, title should come from publishing platform + if (typeof instance.getH5PTitle === 'function') { + this.data.statement.object.definition.name = { + "en-US": instance.getH5PTitle() + }; + } } - if (typeof instance.getH5PTitle === 'function') { - this.data.statement.object.definition.name = { - "en-US": instance.getH5PTitle() - }; + else { + if (H5PIntegration.contents['cid-' + instance.contentId].title) { + this.data.statement.object.definition.name = { + "en-US": H5P.createH5PTitle(H5PIntegration.contents['cid-' + instance.contentId].title) + }; + } } } }; From 431cff0196e1aee4cd1a50c493d8f5c80db9c38d Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Sun, 22 Mar 2015 19:28:16 +0100 Subject: [PATCH 50/75] New API for saving user data per content. --- js/h5p.js | 146 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 139 insertions(+), 7 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index a9c2617..ee2bca0 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -59,7 +59,8 @@ H5P.init = function (target) { } var library = { library: contentData.library, - params: JSON.parse(contentData.jsonContent) + params: JSON.parse(contentData.jsonContent), + userDatas: contentData.contentUserDatas }; // Create new instance. @@ -575,7 +576,18 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize) { return H5P.error('Unable to find constructor for: ' + library.library); } - var instance = new constructor(library.params, contentId); + var contentExtrasWrapper; + if (library.userDatas && library.userDatas.state) { + try { + contentExtrasWrapper = { + previousState: JSON.parse(library.userDatas.state) + }; + } + catch (err) {} + } + console.log(contentExtrasWrapper); + + var instance = new constructor(library.params, contentId, contentExtrasWrapper); if (instance.$ === undefined) { instance.$ = H5P.jQuery(instance); @@ -1391,10 +1403,130 @@ H5P.on = function(instance, eventType, handler) { } }; +// Wrap in privates +(function ($) { -H5P.jQuery(document).ready(function () { - if (!H5P.preventInit) { - // Start script need to be an external resource to load in correct order for IE9. - H5P.init(document.body); + /** + * Creates ajax requests for inserting, updateing and deleteing + * content user data. + * + * @private + * @param {number} contentId What content to store the data for. + * @param {string} dataType Identifies the set of data for this content. + * @param {function} [done] Callback when ajax is done. + * @param {object} [data] To be stored for future use. + * @param {boolean} [preload=false] Data is loaded when content is loaded. + * @param {boolean} [invalidate=false] Data is invalidated when content changes. + * @param {boolean} [async=true] + */ + function contentUserDataAjax(contentId, dataType, done, data, preload, invalidate, async) { + var options = { + url: H5PIntegration.ajaxPath + 'content-user-data/' + contentId + '/' + dataType, + dataType: 'json', + async: async === undefined ? true : async + }; + if (data !== undefined) { + options.type = 'POST'; + options.data = { + data: (data === null ? 0 : JSON.stringify(data)), + preload: (preload ? 1 : 0), + invalidate: (invalidate ? 1 : 0) + }; + } + else { + options.type = 'GET'; + } + if (done !== undefined) { + options.error = function (xhr, error) { + done(error); + }; + options.success = function (response) { + if (!response.success) { + done(response.error); + return; + } + + if (response.data === false || response.data === undefined) { + done(); + return; + } + + try { + done(undefined, JSON.parse(response.data)); + } + catch (error) { + done('Unable to decode data.'); + } + }; + } + + $.ajax(options); } -}); + + /** + * Get user data for given content. + * + * @public + * @param {number} contentId What content to get data for. + * @param {string} dataId Identifies the set of data for this content. + * @param {function} [done] Callback with error and data parameters. + */ + H5P.getUserData = function (contentId, dataId, done) { + contentUserDataAjax(contentId, dataId, done); + }; + + /** + * Set user data for given content. + * + * @public + * @param {number} contentId What content to get data for. + * @param {string} dataId Identifies the set of data for this content. + * @param {object} data The data that is to be stored. + * @param {boolean} [preloaded=false] If the data should be loaded when content is loaded. + * @param {boolean} [deleteOnChange=false] If the data should be invalidated when the content changes. + * @param {function} [errorCallback] Callback with error as parameters. + */ + H5P.setUserData = function (contentId, dataId, data, preloaded, deleteOnChange, errorCallback) { + contentUserDataAjax(contentId, dataId, function (error, data) { + if (errorCallback && error) { + errorCallback(error); + } + }, data, preloaded, deleteOnChange); + }; + + /** + * Delete user data for given content. + * + * @public + * @param {number} contentId What content to remove data for. + * @param {string} dataId Identifies the set of data for this content. + */ + H5P.deleteUserData = function (contentId, dataId) { + contentUserDataAjax(contentId, dataId, undefined, null); + }; + + // Init H5P when page is fully loadded + $(document).ready(function () { + if (!H5P.preventInit) { + // Note that this start script has to be an external resource for it to + // load in correct order in IE9. + H5P.init(document.body); + } + + // Store the current state of the H5P when leaving the page. + H5P.$window.on('unload', function () { + for (var i = 0; i < H5P.instances.length; i++) { + var instance = H5P.instances[i]; + if (instance.getCurrentState instanceof Function || + typeof instance.getCurrentState === 'function') { + var state = instance.getCurrentState(); + if (state !== undefined) { + // Async is not used to prevent the request from being cancelled. + contentUserDataAjax(instance.contentId, 'state', undefined, state, true, true, false); + } + } + } + }); + }); + +})(H5P.jQuery); From a712d6ed61a8a618d049df9c600981e882098076 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Sun, 22 Mar 2015 19:31:35 +0100 Subject: [PATCH 51/75] Removed debug. --- js/h5p.js | 1 - 1 file changed, 1 deletion(-) diff --git a/js/h5p.js b/js/h5p.js index ee2bca0..a416d26 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -585,7 +585,6 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize) { } catch (err) {} } - console.log(contentExtrasWrapper); var instance = new constructor(library.params, contentId, contentExtrasWrapper); From 6b3e550a48287a980caad57b0d314b1985541f22 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Sun, 22 Mar 2015 20:37:10 +0100 Subject: [PATCH 52/75] Don't bubble by default --- js/h5p-event-dispatcher.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/js/h5p-event-dispatcher.js b/js/h5p-event-dispatcher.js index 31043f9..d67ee10 100644 --- a/js/h5p-event-dispatcher.js +++ b/js/h5p-event-dispatcher.js @@ -8,12 +8,12 @@ var H5P = H5P || {}; H5P.Event = function(type, data, extras) { this.type = type; this.data = data; - var bubbles = true; + var bubbles = false; if (extras === undefined) { extras = {}; } - if (extras.bubbles === false) { - bubbles = false; + if (extras.bubbles === true) { + bubbles = true; } this.preventBubbling = function() { bubbles = false; @@ -161,6 +161,7 @@ H5P.EventDispatcher = (function () { for (var i = 0; i < triggers[event.type].length; i++) { triggers[event.type][i].listener.call(triggers[event.type][i].thisArg, event); } + // Bubble if (event.getBubbles() && self.parent instanceof H5P.EventDispatcher && typeof self.parent.trigger === 'function') { self.parent.trigger(event); } From 46c92607550ace97f50160f5c609991c8a575cd0 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Sun, 22 Mar 2015 20:37:28 +0100 Subject: [PATCH 53/75] Don't bubble by default --- js/h5p-x-api-event.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/h5p-x-api-event.js b/js/h5p-x-api-event.js index 3ef34e6..7d85d90 100644 --- a/js/h5p-x-api-event.js +++ b/js/h5p-x-api-event.js @@ -6,7 +6,7 @@ var H5P = H5P || {}; * @class */ H5P.XAPIEvent = function() { - H5P.Event.call(this, 'xAPI', {'statement': {}}); + H5P.Event.call(this, 'xAPI', {'statement': {}}, {bubbles: true}); }; H5P.XAPIEvent.prototype = Object.create(H5P.Event.prototype); From 5d6d22a13fc65a10129b20ed3016e97aea33b731 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Sun, 22 Mar 2015 20:37:48 +0100 Subject: [PATCH 54/75] Accept other verbs than the adl verbs --- js/h5p-x-api-event.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/h5p-x-api-event.js b/js/h5p-x-api-event.js index 7d85d90..79504f0 100644 --- a/js/h5p-x-api-event.js +++ b/js/h5p-x-api-event.js @@ -44,8 +44,8 @@ H5P.XAPIEvent.prototype.setVerb = function(verb) { } }; } - else { - H5P.error('illegal verb'); + else if (verb.id !== undefined) { + this.data.statement.verb = verb; } }; From d3453b86375e56a053af15b446225ba059e961b0 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Sun, 22 Mar 2015 20:38:17 +0100 Subject: [PATCH 55/75] Context bug fix --- js/h5p-x-api-event.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/h5p-x-api-event.js b/js/h5p-x-api-event.js index 79504f0..19bc211 100644 --- a/js/h5p-x-api-event.js +++ b/js/h5p-x-api-event.js @@ -112,7 +112,7 @@ H5P.XAPIEvent.prototype.setObject = function(instance) { * @param {object} instance - the H5P instance */ H5P.XAPIEvent.prototype.setContext = function(instance) { - if (instance.parent && instance.parent.contentId || instance.parent.uuid) { + if (instance.parent && (instance.parent.contentId || instance.parent.uuid)) { var parentId = instance.parent.uuid === undefined ? instance.parent.contentId : instance.parent.uuid; this.data.statement.context = { "contextActivities": { From eb02a5942d8d9da697ab1f04abf5dd17c7df4822 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Sun, 22 Mar 2015 20:38:33 +0100 Subject: [PATCH 56/75] Make externalDispatcher from framed content work again --- js/h5p-x-api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/h5p-x-api.js b/js/h5p-x-api.js index cc18943..c3efcf2 100644 --- a/js/h5p-x-api.js +++ b/js/h5p-x-api.js @@ -3,7 +3,7 @@ var H5P = H5P || {}; // Create object where external code may register and listen for H5P Events H5P.externalDispatcher = new H5P.EventDispatcher(); -if (H5P.isFramed && H5P.externalEmbed === false) { +if (H5P.isFramed && H5P.externalEmbed !== true) { H5P.externalDispatcher.on('xAPI', window.top.H5P.externalDispatcher.trigger); } From 9d38f838867c31edc6da1223abf27ffe6e7b923b Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Sun, 22 Mar 2015 20:38:57 +0100 Subject: [PATCH 57/75] Refactor completed listener to use more of the new xAPI api functions --- js/h5p-x-api.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/js/h5p-x-api.js b/js/h5p-x-api.js index c3efcf2..9d0e7cd 100644 --- a/js/h5p-x-api.js +++ b/js/h5p-x-api.js @@ -66,13 +66,10 @@ H5P.EventDispatcher.prototype.triggerXAPICompleted = function(score, maxScore) { * @param {function} event - xAPI event */ H5P.xAPICompletedListener = function(event) { - var statement = event.data.statement; - if ('verb' in statement) { - if (statement.verb.id === 'http://adlnet.gov/expapi/verbs/completed') { - var score = statement.result.score.raw; - var maxScore = statement.result.score.max; - var contentId = statement.object.definition.extensions['http://h5p.org/x-api/h5p-local-content-id']; - H5P.setFinished(contentId, score, maxScore); - } + if (event.getVerb() === 'completed' && !event.getVerifiedStatementValue(['context', 'contextActivities', 'parent'])) { + var score = event.getScore(); + var maxScore = event.getMaxScore(); + var contentId = event.getVerifiedStatementValue(['object', 'definition', 'extensions', 'http://h5p.org/x-api/h5p-local-content-id']); + H5P.setFinished(contentId, score, maxScore); } }; From 8e113ff792f7cc82d1b51605ec8527141432584e Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Sun, 22 Mar 2015 20:39:16 +0100 Subject: [PATCH 58/75] Add parameter set as parameter 5 to new runnable, and blacklist libraries that already have a custom third parameter for their constructor --- js/h5p.js | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index 5123091..7ac82d0 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -541,10 +541,10 @@ H5P.classFromName = function (name) { * @param {Number} contentId * @param {jQuery} $attachTo An optional element to attach the instance to. * @param {Boolean} skipResize Optionally skip triggering of the resize event after attaching. - * @param {Object} The parent of this H5P + * @param {Object} extras - extra params for the H5P content constructor * @return {Object} Instance. */ -H5P.newRunnable = function (library, contentId, $attachTo, skipResize, parent) { +H5P.newRunnable = function (library, contentId, $attachTo, skipResize, extras) { var nameSplit, versionSplit; try { nameSplit = library.library.split(' ', 2); @@ -575,15 +575,20 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize, parent) { return H5P.error('Unable to find constructor for: ' + library.library); } - var extras = {}; + if (extras === undefined) { + extras = {}; + } if (library.uuid) { extras.uuid = library.uuid; } - if (parent) { - extras.parent = parent; + + // Some old library versions have their own custom third parameter. Make sure we don't send them the extras. They'll interpret it as something else + if (H5P.jQuery.inArray(library.library, ['H5P.CoursePresentation 1.0', 'H5P.CoursePresentation 1.1', 'H5P.CoursePresentation 1.2', 'H5P.CoursePresentation 1.3']) > -1) { + var instance = new constructor(library.params, contentId); + } + else { + var instance = new constructor(library.params, contentId, extras); } - - var instance = new constructor(library.params, contentId, extras); if (instance.$ === undefined) { instance.$ = H5P.jQuery(instance); @@ -595,8 +600,8 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize, parent) { if (instance.uuid === undefined && library.uuid) { instance.uuid = library.uuid; } - if (instance.parent === undefined && parent) { - instance.parent = parent; + if (instance.parent === undefined && extras && extras.parent) { + instance.parent = extras.parent; } if ($attachTo !== undefined) { From 67288c2a0b7d13e4e025986973019e6e2481057c Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Sun, 22 Mar 2015 20:44:35 +0100 Subject: [PATCH 59/75] Added auto save loop. Added save after xAPI events. Made it possible to disable saving. --- js/h5p.js | 57 +++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 45 insertions(+), 12 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index a416d26..ab30799 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -114,6 +114,37 @@ H5P.init = function (target) { H5P.on(instance, 'xAPI', H5P.xAPICompletedListener); H5P.on(instance, 'xAPI', H5P.externalDispatcher.trigger); + // Auto save current state if supported + if (H5PIntegration.saveFreq !== false && ( + instance.getCurrentState instanceof Function || + typeof instance.getCurrentState === 'function')) { + + var saveTimer, save = function () { + var state = instance.getCurrentState(); + if (state !== undefined) { + H5P.setUserData(contentId, 'state', state, true, true); + } + saveTimer = null; + }; + + if (H5PIntegration.saveFreq) { + // Only run the loop when there's stuff happening (reduces load) + H5P.$body.on('mousedown keydown touchstart', function () { + if (!saveTimer) { + saveTimer = setTimeout(save, H5PIntegration.saveFreq * 1000); + } + }); + } + + // xAPI events will schedule a save in three seconds. + H5P.on(instance, 'xAPI', function () { + if (saveTimer) { + clearTimeout(saveTimer); + } + saveTimer = setTimeout(save, 3000); + }); + } + if (H5P.isFramed) { var resizeDelay; if (H5P.externalEmbed === false) { @@ -1512,20 +1543,22 @@ H5P.on = function(instance, eventType, handler) { H5P.init(document.body); } - // Store the current state of the H5P when leaving the page. - H5P.$window.on('unload', function () { - for (var i = 0; i < H5P.instances.length; i++) { - var instance = H5P.instances[i]; - if (instance.getCurrentState instanceof Function || - typeof instance.getCurrentState === 'function') { - var state = instance.getCurrentState(); - if (state !== undefined) { - // Async is not used to prevent the request from being cancelled. - contentUserDataAjax(instance.contentId, 'state', undefined, state, true, true, false); + if (H5PIntegration.saveFreq !== false) { + // Store the current state of the H5P when leaving the page. + H5P.$window.on('unload', function () { + for (var i = 0; i < H5P.instances.length; i++) { + var instance = H5P.instances[i]; + if (instance.getCurrentState instanceof Function || + typeof instance.getCurrentState === 'function') { + var state = instance.getCurrentState(); + if (state !== undefined) { + // Async is not used to prevent the request from being cancelled. + contentUserDataAjax(instance.contentId, 'state', undefined, state, true, true, false); + } } } - } - }); + }); + } }); })(H5P.jQuery); From eee77519b714fd41c2d7355859c1e0614306d05b Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Mon, 23 Mar 2015 09:45:02 +0100 Subject: [PATCH 60/75] Always use complete URLs. --- js/h5p-x-api-event.js | 2 +- js/h5p.js | 14 ++------------ 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/js/h5p-x-api-event.js b/js/h5p-x-api-event.js index 141e232..24cb783 100644 --- a/js/h5p-x-api-event.js +++ b/js/h5p-x-api-event.js @@ -120,7 +120,7 @@ H5P.XAPIEvent.prototype.setActor = function() { this.data.statement.actor = { 'account': { 'name': uuid, - 'homePage': window.location.origin + H5PIntegration.basePath + 'homePage': H5PIntegration.siteUrl }, 'objectType': 'Agent' }; diff --git a/js/h5p.js b/js/h5p.js index a9c2617..d507f4c 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -234,20 +234,10 @@ H5P.init = function (target) { * @returns {string} HTML */ H5P.getHeadTags = function (contentId) { - var basePath = window.location.protocol + '//' + window.location.host + H5PIntegration.basePath; - - var createUrl = function (path) { - if (path.substring(0,7) !== 'http://' && path.substring(0,8) !== 'https://') { - // Not external, add base path. - path = basePath + path; - } - return path; - }; - var createStyleTags = function (styles) { var tags = ''; for (var i = 0; i < styles.length; i++) { - tags += ''; + tags += ''; } return tags; }; @@ -255,7 +245,7 @@ H5P.getHeadTags = function (contentId) { var createScriptTags = function (scripts) { var tags = ''; for (var i = 0; i < scripts.length; i++) { - tags += ''; + tags += ''; } return tags; }; From cfe73006a21554c94c6ac191ac220528bb8da47c Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Mon, 23 Mar 2015 10:53:21 +0100 Subject: [PATCH 61/75] Added back deprecated function. --- js/h5p.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/js/h5p.js b/js/h5p.js index 4223230..01b32d4 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -513,6 +513,19 @@ H5P.getPath = function (path, contentId) { return prefix + '/' + path; }; +/** + * THIS FUNCTION IS DEPRECATED, USE getPath INSTEAD + * Will be remove march 2016. + * + * Find the path to the content files folder based on the id of the content + * + * @param contentId + * Id of the content requesting a path + */ +H5P.getContentPath = function (contentId) { + return H5PIntegration.url + '/content/' + contentId; +}; + /** * Get library class constructor from H5P by classname. * Note that this class will only work for resolve "H5P.NameWithoutDot". From 24fa34f9c29381ad55f72c33457cbeb68e4c37b2 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Tue, 24 Mar 2015 11:01:23 +0100 Subject: [PATCH 62/75] Avoid parsing state multiple times. --- js/h5p.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index 99c3b1c..d473f66 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -57,6 +57,12 @@ H5P.init = function (target) { if (contentData === undefined) { return H5P.error('No data for content id ' + contentId + '. Perhaps the library is gone?'); } + if (contentData.contentUserDatas && contentData.contentUserDatas.state) { + try { + contentData.contentUserDatas.state = JSON.parse(contentData.contentUserDatas.state); + } + catch (err) {} + } var library = { library: contentData.library, params: JSON.parse(contentData.jsonContent), @@ -612,12 +618,9 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize) { var contentExtrasWrapper; if (library.userDatas && library.userDatas.state) { - try { - contentExtrasWrapper = { - previousState: JSON.parse(library.userDatas.state) - }; - } - catch (err) {} + contentExtrasWrapper = { + previousState: library.userDatas.state + }; } var instance = new constructor(library.params, contentId, contentExtrasWrapper); From c4c2f6b16ab0cde0a7eec24e0cbb52799c29711b Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Tue, 24 Mar 2015 16:17:26 +0100 Subject: [PATCH 63/75] Display dialog when content user data is reset. --- js/h5p.js | 23 ++++++++++++++++++++--- styles/h5p.css | 15 +++++++++++++++ 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index d473f66..7902733 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -58,10 +58,27 @@ H5P.init = function (target) { return H5P.error('No data for content id ' + contentId + '. Perhaps the library is gone?'); } if (contentData.contentUserDatas && contentData.contentUserDatas.state) { - try { - contentData.contentUserDatas.state = JSON.parse(contentData.contentUserDatas.state); + if (contentData.contentUserDatas.state === 'RESET') { + // Content has been reset. Display dialog. + delete contentData.contentUserDatas; + var dialog = new H5P.Dialog('content-user-data-reset', 'Data Reset', '

    ' + H5P.t('contentChanged') + '

    ' + H5P.t('startingOver') + '

    OK
    ', $container); + H5P.jQuery(dialog).on('dialog-opened', function (event, $dialog) { + $dialog.find('.h5p-dialog-ok-button').click(function () { + dialog.close(); + }).keypress(function (event) { + if (event.which === 32) { + dialog.close(); + } + }); + }); + dialog.open(); + } + else { + try { + contentData.contentUserDatas.state = JSON.parse(contentData.contentUserDatas.state); + } + catch (err) {} } - catch (err) {} } var library = { library: contentData.library, diff --git a/styles/h5p.css b/styles/h5p.css index e6bd6fd..a9ecaf9 100644 --- a/styles/h5p.css +++ b/styles/h5p.css @@ -390,3 +390,18 @@ div.h5p-fullscreen { min-height: 30px; line-height: 30px; } +.h5p-dialog-ok-button { + cursor: default; + float: right; + outline: none; + border: 2px solid #ccc; + padding: 0.25em 0.75em 0.125em; + background: #eee; +} +.h5p-dialog-ok-button:hover, +.h5p-dialog-ok-button:focus { + background: #fafafa; +} +.h5p-dialog-ok-button:active { + background: #eeffee; +} From c746457a872b1ccc20af7e464e27b8d45a50bf7e Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Tue, 24 Mar 2015 18:24:07 +0100 Subject: [PATCH 64/75] Make xAPI work in editor --- js/h5p-x-api-event.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/h5p-x-api-event.js b/js/h5p-x-api-event.js index 19bc211..0d0290b 100644 --- a/js/h5p-x-api-event.js +++ b/js/h5p-x-api-event.js @@ -97,7 +97,7 @@ H5P.XAPIEvent.prototype.setObject = function(instance) { } } else { - if (H5PIntegration.contents['cid-' + instance.contentId].title) { + if (H5PIntegration && H5PIntegration.contents && H5PIntegration.contents['cid-' + instance.contentId].title) { this.data.statement.object.definition.name = { "en-US": H5P.createH5PTitle(H5PIntegration.contents['cid-' + instance.contentId].title) }; @@ -177,7 +177,7 @@ H5P.XAPIEvent.prototype.getScore = function() { H5P.XAPIEvent.prototype.getContentXAPIId = function (instance) { var xAPIId; - if (instance.contentId) { + if (instance.contentId && H5PIntegration && H5PIntegration.contents) { xAPIId = H5PIntegration.contents['cid-' + instance.contentId].url; if (instance.uuid) { xAPIId += '?uuid=' + instance.uuid; From ef7a31d2e16bd8d667b50d495668e80d23c0859b Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Tue, 24 Mar 2015 18:55:13 +0100 Subject: [PATCH 65/75] Try to get back the no url branch --- h5p.classes.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/h5p.classes.php b/h5p.classes.php index daa6ead..3bc78d1 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -1785,9 +1785,11 @@ class H5PCore { if ($type === 'preloadedCss' && (isset($dependency['dropCss']) && $dependency['dropCss'] === '1')) { return; } + dpm($prefix); + dpm($dependency['path']); foreach ($dependency[$type] as $file) { $assets[] = (object) array( - 'path' => /*$prefix . */$dependency['path'] . '/' . trim(is_array($file) ? $file['path'] : $file), + 'path' => $prefix . $dependency['path'] . '/' . trim(is_array($file) ? $file['path'] : $file), 'version' => $dependency['version'] ); } @@ -1807,7 +1809,7 @@ class H5PCore { // Add URL prefix if not external if (strpos($asset->path, '://') === FALSE) { - $url = /*$this->url .*/ $url; + $url = $this->url . $url; } // Add version/cache buster if set @@ -1839,7 +1841,6 @@ class H5PCore { $dependency['preloadedJs'] = explode(',', $dependency['preloadedJs']); $dependency['preloadedCss'] = explode(',', $dependency['preloadedCss']); } - $dependency['version'] = "?ver={$dependency['majorVersion']}.{$dependency['minorVersion']}.{$dependency['patchVersion']}"; $this->getDependencyAssets($dependency, 'preloadedJs', $files['scripts'], $prefix); $this->getDependencyAssets($dependency, 'preloadedCss', $files['styles'], $prefix); From 065ee4e8a2bdeb63554dd59c9287f185319d29ef Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Tue, 24 Mar 2015 19:29:28 +0100 Subject: [PATCH 66/75] Make editor work again --- h5p-development.class.php | 3 +-- h5p.classes.php | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/h5p-development.class.php b/h5p-development.class.php index 7f33689..491c456 100644 --- a/h5p-development.class.php +++ b/h5p-development.class.php @@ -86,7 +86,7 @@ class H5PDevelopment { $library['libraryId'] = $this->h5pF->getLibraryId($library['machineName'], $library['majorVersion'], $library['minorVersion']); $this->h5pF->saveLibraryData($library, $library['libraryId'] === FALSE); - $library['path'] = $libraryPath; + $library['path'] = $path . '/' . $contents[$i]; $this->libraries[H5PDevelopment::libraryToString($library['machineName'], $library['majorVersion'], $library['minorVersion'])] = $library; } @@ -139,7 +139,6 @@ class H5PDevelopment { */ public function getSemantics($name, $majorVersion, $minorVersion) { $library = H5PDevelopment::libraryToString($name, $majorVersion, $minorVersion); - if (isset($this->libraries[$library]) === FALSE) { return NULL; } diff --git a/h5p.classes.php b/h5p.classes.php index 3bc78d1..6a2795e 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -1785,8 +1785,6 @@ class H5PCore { if ($type === 'preloadedCss' && (isset($dependency['dropCss']) && $dependency['dropCss'] === '1')) { return; } - dpm($prefix); - dpm($dependency['path']); foreach ($dependency[$type] as $file) { $assets[] = (object) array( 'path' => $prefix . $dependency['path'] . '/' . trim(is_array($file) ? $file['path'] : $file), From 61a8e7e9e935d128bf977be8dac19eb3d1e558ba Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Tue, 24 Mar 2015 19:36:13 +0100 Subject: [PATCH 67/75] Make view work again as well --- h5p.classes.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/h5p.classes.php b/h5p.classes.php index 6a2795e..1bef34d 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -1787,7 +1787,7 @@ class H5PCore { } foreach ($dependency[$type] as $file) { $assets[] = (object) array( - 'path' => $prefix . $dependency['path'] . '/' . trim(is_array($file) ? $file['path'] : $file), + 'path' => /*$prefix .*/ $dependency['path'] . '/' . trim(is_array($file) ? $file['path'] : $file), 'version' => $dependency['version'] ); } From bf227bdae09ab61dfd26f354a5d86921b13e34cf Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Fri, 27 Mar 2015 10:29:34 +0100 Subject: [PATCH 68/75] Moved in changes from Drupal. --- h5p-development.class.php | 10 +- h5p.classes.php | 24 ++- js/h5p-event-dispatcher.js | 68 ++++++++- js/h5p-x-api-event.js | 14 +- js/h5p-x-api.js | 2 +- js/h5p.js | 302 ++++++++++++++++++++++++++++++++++--- 6 files changed, 374 insertions(+), 46 deletions(-) diff --git a/h5p-development.class.php b/h5p-development.class.php index 491c456..1eb793f 100644 --- a/h5p-development.class.php +++ b/h5p-development.class.php @@ -9,7 +9,7 @@ class H5PDevelopment { const MODE_CONTENT = 1; const MODE_LIBRARY = 2; - private $h5pF, $libraries, $language; + private $h5pF, $libraries, $language, $filesPath; /** * Constructor. @@ -23,6 +23,7 @@ class H5PDevelopment { public function __construct($H5PFramework, $filesPath, $language, $libraries = NULL) { $this->h5pF = $H5PFramework; $this->language = $language; + $this->filesPath = $filesPath; if ($libraries !== NULL) { $this->libraries = $libraries; } @@ -86,7 +87,7 @@ class H5PDevelopment { $library['libraryId'] = $this->h5pF->getLibraryId($library['machineName'], $library['majorVersion'], $library['minorVersion']); $this->h5pF->saveLibraryData($library, $library['libraryId'] === FALSE); - $library['path'] = $path . '/' . $contents[$i]; + $library['path'] = 'development/' . $contents[$i]; $this->libraries[H5PDevelopment::libraryToString($library['machineName'], $library['majorVersion'], $library['minorVersion'])] = $library; } @@ -142,8 +143,7 @@ class H5PDevelopment { if (isset($this->libraries[$library]) === FALSE) { return NULL; } - - return $this->getFileContents($this->libraries[$library]['path'] . '/semantics.json'); + return $this->getFileContents($this->filesPath . $this->libraries[$library]['path'] . '/semantics.json'); } /** @@ -161,7 +161,7 @@ class H5PDevelopment { return NULL; } - return $this->getFileContents($this->libraries[$library]['path'] . '/language/' . $language . '.json'); + return $this->getFileContents($this->filesPath . $this->libraries[$library]['path'] . '/language/' . $language . '.json'); } /** diff --git a/h5p.classes.php b/h5p.classes.php index 1bef34d..9a70cc2 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -247,6 +247,13 @@ interface H5PFrameworkInterface { */ public function updateContent($content, $contentMainId = NULL); + /** + * Resets marked user data for the given content. + * + * @param int $contentId + */ + public function resetContentUserData($contentId); + /** * Save what libraries a library is dependending on * @@ -1599,7 +1606,7 @@ class H5PCore { public static $coreApi = array( 'majorVersion' => 1, - 'minorVersion' => 4 + 'minorVersion' => 5 ); public static $styles = array( 'styles/h5p.css', @@ -1644,7 +1651,7 @@ class H5PCore { $this->development_mode = $development_mode; if ($development_mode & H5PDevelopment::MODE_LIBRARY) { - $this->h5pD = new H5PDevelopment($this->h5pF, $path, $language); + $this->h5pD = new H5PDevelopment($this->h5pF, $path . '/', $language); } } @@ -1662,6 +1669,9 @@ class H5PCore { $content['id'] = $this->h5pF->insertContent($content, $contentMainId); } + // Some user data for content has to be reset when the content changes. + $this->h5pF->resetContentUserData($contentMainId ? $contentMainId : $content['id']); + return $content['id']; } @@ -1787,7 +1797,7 @@ class H5PCore { } foreach ($dependency[$type] as $file) { $assets[] = (object) array( - 'path' => /*$prefix .*/ $dependency['path'] . '/' . trim(is_array($file) ? $file['path'] : $file), + 'path' => $prefix . '/' . $dependency['path'] . '/' . trim(is_array($file) ? $file['path'] : $file), 'version' => $dependency['version'] ); } @@ -1835,7 +1845,7 @@ class H5PCore { ); foreach ($dependencies as $dependency) { if (isset($dependency['path']) === FALSE) { - $dependency['path'] = '/libraries/' . H5PCore::libraryToString($dependency, TRUE); + $dependency['path'] = 'libraries/' . H5PCore::libraryToString($dependency, TRUE); $dependency['preloadedJs'] = explode(',', $dependency['preloadedJs']); $dependency['preloadedCss'] = explode(',', $dependency['preloadedCss']); } @@ -2739,13 +2749,13 @@ class H5PContentValidator { 'type' => 'group', 'fields' => $library['semantics'], ), FALSE); - $validkeys = array('library', 'params', 'uuid'); + $validkeys = array('library', 'params', 'subContentId'); if (isset($semantics->extraAttributes)) { $validkeys = array_merge($validkeys, $semantics->extraAttributes); } $this->filterParams($value, $validkeys); - if (isset($value->uuid) && ! preg_match('/^\{?[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\}?$/', $value->uuid)) { - unset($value->uuid); + if (isset($value->subContentId) && ! preg_match('/^\{?[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\}?$/', $value->subContentId)) { + unset($value->subContentId); } // Find all dependencies for this library diff --git a/js/h5p-event-dispatcher.js b/js/h5p-event-dispatcher.js index d67ee10..1d52ef7 100644 --- a/js/h5p-event-dispatcher.js +++ b/js/h5p-event-dispatcher.js @@ -9,18 +9,55 @@ 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 + * + * @returns {undefined} + */ 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 + * - false otherwise + */ + this.scheduleForExternal = function() { + if (external && !scheduledForExternal) { + scheduledForExternal = true; + return true; + } + return false; + }; }; H5P.EventDispatcher = (function () { @@ -144,27 +181,42 @@ H5P.EventDispatcher = (function () { * Custom event data(used when event type as string is used as first * argument */ - this.trigger = function (event, eventData) { + this.trigger = function (event, eventData, extras) { if (event === undefined) { return; } if (typeof event === 'string') { - event = new H5P.Event(event, eventData); + event = new H5P.Event(event, eventData, extras); } else if (eventData !== undefined) { event.data = eventData; } - if (triggers[event.type] === undefined) { - return; + + // 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); + } } - // 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 i = 0; i < triggers['*'].length; i++) { + triggers['*'][i].listener.call(triggers['*'][i].thisArg, event); + } } + // Bubble if (event.getBubbles() && self.parent instanceof H5P.EventDispatcher && typeof self.parent.trigger === 'function') { self.parent.trigger(event); - } + } + + if (scheduledForExternal) { + H5P.externalDispatcher.trigger(event); + } }; } diff --git a/js/h5p-x-api-event.js b/js/h5p-x-api-event.js index 9b30675..a8513a4 100644 --- a/js/h5p-x-api-event.js +++ b/js/h5p-x-api-event.js @@ -6,7 +6,7 @@ var H5P = H5P || {}; * @class */ H5P.XAPIEvent = function() { - H5P.Event.call(this, 'xAPI', {'statement': {}}, {bubbles: true}); + H5P.Event.call(this, 'xAPI', {'statement': {}}, {bubbles: true, external: true}); }; H5P.XAPIEvent.prototype = Object.create(H5P.Event.prototype); @@ -87,8 +87,8 @@ H5P.XAPIEvent.prototype.setObject = function(instance) { } } }; - if (instance.uuid) { - this.data.statement.object.definition.extensions['http://h5p.org/x-api/h5p-uuid'] = instance.uuid; + if (instance.subContentId) { + this.data.statement.object.definition.extensions['http://h5p.org/x-api/h5p-subContentId'] = instance.subContentId; // Don't set titles on main content, title should come from publishing platform if (typeof instance.getH5PTitle === 'function') { this.data.statement.object.definition.name = { @@ -112,8 +112,8 @@ H5P.XAPIEvent.prototype.setObject = function(instance) { * @param {object} instance - the H5P instance */ H5P.XAPIEvent.prototype.setContext = function(instance) { - if (instance.parent && (instance.parent.contentId || instance.parent.uuid)) { - var parentId = instance.parent.uuid === undefined ? instance.parent.contentId : instance.parent.uuid; + if (instance.parent && (instance.parent.contentId || instance.parent.subContentId)) { + var parentId = instance.parent.subContentId === undefined ? instance.parent.contentId : instance.parent.subContentId; this.data.statement.context = { "contextActivities": { "parent": [ @@ -179,8 +179,8 @@ H5P.XAPIEvent.prototype.getContentXAPIId = function (instance) { var xAPIId; if (instance.contentId && H5PIntegration && H5PIntegration.contents) { xAPIId = H5PIntegration.contents['cid-' + instance.contentId].url; - if (instance.uuid) { - xAPIId += '?uuid=' + instance.uuid; + if (instance.subContentId) { + xAPIId += '?subContentId=' + instance.subContentId; } } return xAPIId; diff --git a/js/h5p-x-api.js b/js/h5p-x-api.js index 9d0e7cd..fa5e51a 100644 --- a/js/h5p-x-api.js +++ b/js/h5p-x-api.js @@ -4,7 +4,7 @@ var H5P = H5P || {}; H5P.externalDispatcher = new H5P.EventDispatcher(); if (H5P.isFramed && H5P.externalEmbed !== true) { - H5P.externalDispatcher.on('xAPI', window.top.H5P.externalDispatcher.trigger); + H5P.externalDispatcher.on('*', window.top.H5P.externalDispatcher.trigger); } // EventDispatcher extensions diff --git a/js/h5p.js b/js/h5p.js index 3425a03..c359c1b 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -62,6 +62,29 @@ H5P.init = function (target) { params: JSON.parse(contentData.jsonContent) }; + H5P.getUserData(contentId, 'state', function (err, previousState) { + if (previousState) { + library.userDatas = { + state: previousState + }; + } + else if (previousState === null) { + // Content has been reset. Display dialog. + delete contentData.contentUserData; + var dialog = new H5P.Dialog('content-user-data-reset', 'Data Reset', '

    ' + H5P.t('contentChanged') + '

    ' + H5P.t('startingOver') + '

    OK
    ', $container); + H5P.jQuery(dialog).on('dialog-opened', function (event, $dialog) { + $dialog.find('.h5p-dialog-ok-button').click(function () { + dialog.close(); + }).keypress(function (event) { + if (event.which === 32) { + dialog.close(); + } + }); + }); + dialog.open(); + } + }); + // Create new instance. var instance = H5P.newRunnable(library, contentId, $container, true); @@ -111,7 +134,37 @@ H5P.init = function (target) { // Listen for xAPI events. H5P.on(instance, 'xAPI', H5P.xAPICompletedListener); - H5P.on(instance, 'xAPI', H5P.externalDispatcher.trigger); + + // Auto save current state if supported + if (H5PIntegration.saveFreq !== false && ( + instance.getCurrentState instanceof Function || + typeof instance.getCurrentState === 'function')) { + + var saveTimer, save = function () { + var state = instance.getCurrentState(); + if (state !== undefined) { + H5P.setUserData(contentId, 'state', state, undefined, true, true); + } + if (H5PIntegration.saveFreq) { + // Continue autosave + saveTimer = setTimeout(save, H5PIntegration.saveFreq * 1000); + } + }; + + if (H5PIntegration.saveFreq) { + // Start autosave + saveTimer = setTimeout(save, H5PIntegration.saveFreq * 1000); + } + + // xAPI events will schedule a save in three seconds. + H5P.on(instance, 'xAPI', function (event) { + var verb = event.getVerb(); + if (verb === 'completed' || verb === 'progressed') { + clearTimeout(saveTimer); + saveTimer = setTimeout(save, 3000); + } + }); + } if (H5P.isFramed) { var resizeDelay; @@ -548,9 +601,10 @@ H5P.classFromName = function (name) { * @return {Object} Instance. */ H5P.newRunnable = function (library, contentId, $attachTo, skipResize, extras) { - var nameSplit, versionSplit; + var nameSplit, versionSplit, machineName; try { nameSplit = library.library.split(' ', 2); + machineName = nameSplit[0]; versionSplit = nameSplit[1].split('.', 2); } catch (err) { @@ -581,16 +635,23 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize, extras) { if (extras === undefined) { extras = {}; } - if (library.uuid) { - extras.uuid = library.uuid; + if (library.subContentId) { + extras.subContentId = library.subContentId; } - - // Some old library versions have their own custom third parameter. Make sure we don't send them the extras. They'll interpret it as something else + + if (library.userDatas && library.userDatas.state) { + extras.previousState = library.userDatas.state; + } + + var instance; + // Some old library versions have their own custom third parameter. + // Make sure we don't send them the extras. + // (they will interpret it as something else) if (H5P.jQuery.inArray(library.library, ['H5P.CoursePresentation 1.0', 'H5P.CoursePresentation 1.1', 'H5P.CoursePresentation 1.2', 'H5P.CoursePresentation 1.3']) > -1) { - var instance = new constructor(library.params, contentId); + instance = new constructor(library.params, contentId); } else { - var instance = new constructor(library.params, contentId, extras); + instance = new constructor(library.params, contentId, extras); } if (instance.$ === undefined) { @@ -600,8 +661,8 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize, extras) { if (instance.contentId === undefined) { instance.contentId = contentId; } - if (instance.uuid === undefined && library.uuid) { - instance.uuid = library.uuid; + if (instance.subContentId === undefined && library.subContentId) { + instance.subContentId = library.subContentId; } if (instance.parent === undefined && extras && extras.parent) { instance.parent = extras.parent; @@ -609,6 +670,11 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize, extras) { if ($attachTo !== undefined) { instance.attach($attachTo); + H5P.trigger(instance, 'domChanged', { + '$target': $attachTo, + 'library': machineName, + 'key': 'newLibrary' + }, {'bubbles': true, 'external': true}); if (skipResize === undefined || !skipResize) { // Resize content. @@ -1378,10 +1444,10 @@ if (String.prototype.trim === undefined) { * @param {string} eventType * The event type */ -H5P.trigger = function(instance, eventType) { +H5P.trigger = function(instance, eventType, data, extras) { // Try new event system first if (instance.trigger !== undefined) { - instance.trigger(eventType); + instance.trigger(eventType, data, extras); } // Try deprecated event system else if (instance.$ !== undefined && instance.$.trigger !== undefined) { @@ -1415,7 +1481,7 @@ H5P.on = function(instance, eventType, handler) { /** * Create UUID - * + * * @returns {String} UUID */ H5P.createUUID = function() { @@ -1441,9 +1507,209 @@ H5P.createH5PTitle = function(rawTitle, maxLength) { return title; }; -H5P.jQuery(document).ready(function () { - if (!H5P.preventInit) { - // Start script need to be an external resource to load in correct order for IE9. - H5P.init(document.body); +// Wrap in privates +(function ($) { + + /** + * Creates ajax requests for inserting, updateing and deleteing + * content user data. + * + * @private + * @param {number} contentId What content to store the data for. + * @param {string} dataType Identifies the set of data for this content. + * @param {string} subContentId Identifies sub content + * @param {function} [done] Callback when ajax is done. + * @param {object} [data] To be stored for future use. + * @param {boolean} [preload=false] Data is loaded when content is loaded. + * @param {boolean} [invalidate=false] Data is invalidated when content changes. + * @param {boolean} [async=true] + */ + function contentUserDataAjax(contentId, dataType, subContentId, done, data, preload, invalidate, async) { + var options = { + url: H5PIntegration.ajaxPath + 'content-user-data/' + contentId + '/' + dataType + '/' + (subContentId ? subContentId : 0), + dataType: 'json', + async: async === undefined ? true : async + }; + if (data !== undefined) { + options.type = 'POST'; + options.data = { + data: (data === null ? 0 : data), + preload: (preload ? 1 : 0), + invalidate: (invalidate ? 1 : 0) + }; + } + else { + options.type = 'GET'; + } + if (done !== undefined) { + options.error = function (xhr, error) { + done(error); + }; + options.success = function (response) { + if (!response.success) { + done(response.error); + return; + } + + if (response.data === false || response.data === undefined) { + done(); + return; + } + + done(undefined, response.data); + }; + } + + $.ajax(options); } -}); + + /** + * Get user data for given content. + * + * @public + * @param {number} contentId What content to get data for. + * @param {string} dataId Identifies the set of data for this content. + * @param {function} done Callback with error and data parameters. + * @param {string} [subContentId] Identifies which data belongs to sub content. + */ + H5P.getUserData = function (contentId, dataId, done, subContentId) { + if (!subContentId) { + subContentId = 0; // Default + } + + var content = H5PIntegration.contents['cid-' + contentId]; + var preloadedData = content.contentUserData; + if (preloadedData && preloadedData[subContentId] && preloadedData[subContentId][dataId]) { + if (preloadedData[subContentId][dataId] === 'RESET') { + done(undefined, null); + return; + } + try { + done(undefined, JSON.parse(preloadedData[subContentId][dataId])); + } + catch (err) { + done(err); + } + } + else { + contentUserDataAjax(contentId, dataId, subContentId, function (err, data) { + if (err || data === undefined) { + done(err, data); + return; // Error or no data + } + + // Cache in preloaded + if (content.contentUserData === undefined) { + content.contentUserData = preloaded = {}; + } + if (preloadedData[subContentId] === undefined) { + preloadedData[subContentId] = {}; + } + preloadedData[subContentId][dataId] = data; + + // Done. Try to decode JSON + try { + done(undefined, JSON.parse(data)); + } + catch (e) { + done(e); + } + }); + } + }; + + /** + * Set user data for given content. + * + * @public + * @param {number} contentId What content to get data for. + * @param {string} dataId Identifies the set of data for this content. + * @param {object} data The data that is to be stored. + * @param {string} [subContentId] Identifies which data belongs to sub content. + * @param {boolean} [preloaded=false] If the data should be loaded when content is loaded. + * @param {boolean} [deleteOnChange=false] If the data should be invalidated when the content changes. + * @param {function} [errorCallback] Callback with error as parameters. + */ + H5P.setUserData = function (contentId, dataId, data, subContentId, preloaded, deleteOnChange, errorCallback, async) { + if (!subContentId) { + subContentId = 0; // Default + } + + try { + data = JSON.stringify(data); + } + catch (err) { + errorCallback(err); + return; // Failed to serialize. + } + + var content = H5PIntegration.contents['cid-' + contentId]; + if (!content.contentUserData) { + content.contentUserData = {}; + } + var preloadedData = content.contentUserData; + if (preloadedData[subContentId] === undefined) { + preloadedData[subContentId] = {}; + } + if (data === preloadedData[subContentId][dataId]) { + return; // No need to save this twice. + } + + preloadedData[subContentId][dataId] = data; + contentUserDataAjax(contentId, dataId, subContentId, function (error, data) { + if (errorCallback && error) { + errorCallback(error); + } + }, data, preloaded, deleteOnChange, async); + }; + + /** + * Delete user data for given content. + * + * @public + * @param {number} contentId What content to remove data for. + * @param {string} dataId Identifies the set of data for this content. + * @param {string} [subContentId] Identifies which data belongs to sub content. + */ + H5P.deleteUserData = function (contentId, dataId, subContentId) { + if (!subContentId) { + subContentId = 0; // Default + } + + // Remove from preloaded/cache + var preloadedData = H5PIntegration.contents['cid-' + contentId].contentUserData; + if (preloadedData && preloadedData[subContentId] && preloadedData[subContentId][dataId]) { + delete preloadedData[subContentId][dataId]; + } + + contentUserDataAjax(contentId, dataId, subContentId, undefined, null); + }; + + // Init H5P when page is fully loadded + $(document).ready(function () { + if (!H5P.preventInit) { + // Note that this start script has to be an external resource for it to + // load in correct order in IE9. + H5P.init(document.body); + } + + if (H5PIntegration.saveFreq !== false) { + // Store the current state of the H5P when leaving the page. + H5P.$window.on('beforeunload', function () { + for (var i = 0; i < H5P.instances.length; i++) { + var instance = H5P.instances[i]; + if (instance.getCurrentState instanceof Function || + typeof instance.getCurrentState === 'function') { + var state = instance.getCurrentState(); + if (state !== undefined) { + // Async is not used to prevent the request from being cancelled. + H5P.setUserData(instance.contentId, 'state', state, undefined, true, true, undefined, false); + + } + } + } + }); + } + }); + +})(H5P.jQuery); From 2dfdc04217de662434d73d2864972bb329a4999b Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Fri, 27 Mar 2015 11:51:17 +0100 Subject: [PATCH 69/75] Fixed exports with development mode enabled. --- h5p.classes.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/h5p.classes.php b/h5p.classes.php index 9a70cc2..62f3011 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -1499,7 +1499,7 @@ Class H5PExport { $library = $dependency['library']; // Copy library to h5p - $source = isset($library['path']) ? $library['path'] : $h5pDir . 'libraries' . DIRECTORY_SEPARATOR . H5PCore::libraryToString($library, TRUE); + $source = $h5pDir . (isset($library['path']) ? $library['path'] : 'libraries' . DIRECTORY_SEPARATOR . H5PCore::libraryToString($library, TRUE)); $destination = $tempPath . DIRECTORY_SEPARATOR . $library['machineName']; $this->h5pC->copyFileTree($source, $destination); From cfa747f20cb45058e395d044f3c284ca1d0fe658 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Tue, 7 Apr 2015 19:30:46 +0200 Subject: [PATCH 70/75] Add path fixes --- h5p-development.class.php | 10 +++++----- h5p.classes.php | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/h5p-development.class.php b/h5p-development.class.php index 491c456..1eb793f 100644 --- a/h5p-development.class.php +++ b/h5p-development.class.php @@ -9,7 +9,7 @@ class H5PDevelopment { const MODE_CONTENT = 1; const MODE_LIBRARY = 2; - private $h5pF, $libraries, $language; + private $h5pF, $libraries, $language, $filesPath; /** * Constructor. @@ -23,6 +23,7 @@ class H5PDevelopment { public function __construct($H5PFramework, $filesPath, $language, $libraries = NULL) { $this->h5pF = $H5PFramework; $this->language = $language; + $this->filesPath = $filesPath; if ($libraries !== NULL) { $this->libraries = $libraries; } @@ -86,7 +87,7 @@ class H5PDevelopment { $library['libraryId'] = $this->h5pF->getLibraryId($library['machineName'], $library['majorVersion'], $library['minorVersion']); $this->h5pF->saveLibraryData($library, $library['libraryId'] === FALSE); - $library['path'] = $path . '/' . $contents[$i]; + $library['path'] = 'development/' . $contents[$i]; $this->libraries[H5PDevelopment::libraryToString($library['machineName'], $library['majorVersion'], $library['minorVersion'])] = $library; } @@ -142,8 +143,7 @@ class H5PDevelopment { if (isset($this->libraries[$library]) === FALSE) { return NULL; } - - return $this->getFileContents($this->libraries[$library]['path'] . '/semantics.json'); + return $this->getFileContents($this->filesPath . $this->libraries[$library]['path'] . '/semantics.json'); } /** @@ -161,7 +161,7 @@ class H5PDevelopment { return NULL; } - return $this->getFileContents($this->libraries[$library]['path'] . '/language/' . $language . '.json'); + return $this->getFileContents($this->filesPath . $this->libraries[$library]['path'] . '/language/' . $language . '.json'); } /** diff --git a/h5p.classes.php b/h5p.classes.php index 1bef34d..0d28aa0 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -1492,7 +1492,7 @@ Class H5PExport { $library = $dependency['library']; // Copy library to h5p - $source = isset($library['path']) ? $library['path'] : $h5pDir . 'libraries' . DIRECTORY_SEPARATOR . H5PCore::libraryToString($library, TRUE); + $source = $h5pDir . (isset($library['path']) ? $library['path'] : 'libraries' . DIRECTORY_SEPARATOR . H5PCore::libraryToString($library, TRUE)); $destination = $tempPath . DIRECTORY_SEPARATOR . $library['machineName']; $this->h5pC->copyFileTree($source, $destination); @@ -1644,7 +1644,7 @@ class H5PCore { $this->development_mode = $development_mode; if ($development_mode & H5PDevelopment::MODE_LIBRARY) { - $this->h5pD = new H5PDevelopment($this->h5pF, $path, $language); + $this->h5pD = new H5PDevelopment($this->h5pF, $path . '/', $language); } } @@ -1787,7 +1787,7 @@ class H5PCore { } foreach ($dependency[$type] as $file) { $assets[] = (object) array( - 'path' => /*$prefix .*/ $dependency['path'] . '/' . trim(is_array($file) ? $file['path'] : $file), + 'path' => $prefix . '/' . $dependency['path'] . '/' . trim(is_array($file) ? $file['path'] : $file), 'version' => $dependency['version'] ); } @@ -1835,7 +1835,7 @@ class H5PCore { ); foreach ($dependencies as $dependency) { if (isset($dependency['path']) === FALSE) { - $dependency['path'] = '/libraries/' . H5PCore::libraryToString($dependency, TRUE); + $dependency['path'] = 'libraries/' . H5PCore::libraryToString($dependency, TRUE); $dependency['preloadedJs'] = explode(',', $dependency['preloadedJs']); $dependency['preloadedCss'] = explode(',', $dependency['preloadedCss']); } From efbe56001ddb332e674f2122374e07ad8d4f0ed0 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Tue, 7 Apr 2015 19:32:44 +0200 Subject: [PATCH 71/75] User data fixes --- h5p.classes.php | 10 ++ js/h5p.js | 302 +++++++++++++++++++++++++++++++++++++++++++++--- styles/h5p.css | 15 +++ 3 files changed, 309 insertions(+), 18 deletions(-) diff --git a/h5p.classes.php b/h5p.classes.php index 0d28aa0..681cb23 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -247,6 +247,13 @@ interface H5PFrameworkInterface { */ public function updateContent($content, $contentMainId = NULL); + /** + * Resets marked user data for the given content. + * + * @param int $contentId + */ + public function resetContentUserData($contentId); + /** * Save what libraries a library is dependending on * @@ -1662,6 +1669,9 @@ class H5PCore { $content['id'] = $this->h5pF->insertContent($content, $contentMainId); } + // Some user data for content has to be reset when the content changes. + $this->h5pF->resetContentUserData($contentMainId ? $contentMainId : $content['id']); + return $content['id']; } diff --git a/js/h5p.js b/js/h5p.js index 3425a03..c359c1b 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -62,6 +62,29 @@ H5P.init = function (target) { params: JSON.parse(contentData.jsonContent) }; + H5P.getUserData(contentId, 'state', function (err, previousState) { + if (previousState) { + library.userDatas = { + state: previousState + }; + } + else if (previousState === null) { + // Content has been reset. Display dialog. + delete contentData.contentUserData; + var dialog = new H5P.Dialog('content-user-data-reset', 'Data Reset', '

    ' + H5P.t('contentChanged') + '

    ' + H5P.t('startingOver') + '

    OK
    ', $container); + H5P.jQuery(dialog).on('dialog-opened', function (event, $dialog) { + $dialog.find('.h5p-dialog-ok-button').click(function () { + dialog.close(); + }).keypress(function (event) { + if (event.which === 32) { + dialog.close(); + } + }); + }); + dialog.open(); + } + }); + // Create new instance. var instance = H5P.newRunnable(library, contentId, $container, true); @@ -111,7 +134,37 @@ H5P.init = function (target) { // Listen for xAPI events. H5P.on(instance, 'xAPI', H5P.xAPICompletedListener); - H5P.on(instance, 'xAPI', H5P.externalDispatcher.trigger); + + // Auto save current state if supported + if (H5PIntegration.saveFreq !== false && ( + instance.getCurrentState instanceof Function || + typeof instance.getCurrentState === 'function')) { + + var saveTimer, save = function () { + var state = instance.getCurrentState(); + if (state !== undefined) { + H5P.setUserData(contentId, 'state', state, undefined, true, true); + } + if (H5PIntegration.saveFreq) { + // Continue autosave + saveTimer = setTimeout(save, H5PIntegration.saveFreq * 1000); + } + }; + + if (H5PIntegration.saveFreq) { + // Start autosave + saveTimer = setTimeout(save, H5PIntegration.saveFreq * 1000); + } + + // xAPI events will schedule a save in three seconds. + H5P.on(instance, 'xAPI', function (event) { + var verb = event.getVerb(); + if (verb === 'completed' || verb === 'progressed') { + clearTimeout(saveTimer); + saveTimer = setTimeout(save, 3000); + } + }); + } if (H5P.isFramed) { var resizeDelay; @@ -548,9 +601,10 @@ H5P.classFromName = function (name) { * @return {Object} Instance. */ H5P.newRunnable = function (library, contentId, $attachTo, skipResize, extras) { - var nameSplit, versionSplit; + var nameSplit, versionSplit, machineName; try { nameSplit = library.library.split(' ', 2); + machineName = nameSplit[0]; versionSplit = nameSplit[1].split('.', 2); } catch (err) { @@ -581,16 +635,23 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize, extras) { if (extras === undefined) { extras = {}; } - if (library.uuid) { - extras.uuid = library.uuid; + if (library.subContentId) { + extras.subContentId = library.subContentId; } - - // Some old library versions have their own custom third parameter. Make sure we don't send them the extras. They'll interpret it as something else + + if (library.userDatas && library.userDatas.state) { + extras.previousState = library.userDatas.state; + } + + var instance; + // Some old library versions have their own custom third parameter. + // Make sure we don't send them the extras. + // (they will interpret it as something else) if (H5P.jQuery.inArray(library.library, ['H5P.CoursePresentation 1.0', 'H5P.CoursePresentation 1.1', 'H5P.CoursePresentation 1.2', 'H5P.CoursePresentation 1.3']) > -1) { - var instance = new constructor(library.params, contentId); + instance = new constructor(library.params, contentId); } else { - var instance = new constructor(library.params, contentId, extras); + instance = new constructor(library.params, contentId, extras); } if (instance.$ === undefined) { @@ -600,8 +661,8 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize, extras) { if (instance.contentId === undefined) { instance.contentId = contentId; } - if (instance.uuid === undefined && library.uuid) { - instance.uuid = library.uuid; + if (instance.subContentId === undefined && library.subContentId) { + instance.subContentId = library.subContentId; } if (instance.parent === undefined && extras && extras.parent) { instance.parent = extras.parent; @@ -609,6 +670,11 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize, extras) { if ($attachTo !== undefined) { instance.attach($attachTo); + H5P.trigger(instance, 'domChanged', { + '$target': $attachTo, + 'library': machineName, + 'key': 'newLibrary' + }, {'bubbles': true, 'external': true}); if (skipResize === undefined || !skipResize) { // Resize content. @@ -1378,10 +1444,10 @@ if (String.prototype.trim === undefined) { * @param {string} eventType * The event type */ -H5P.trigger = function(instance, eventType) { +H5P.trigger = function(instance, eventType, data, extras) { // Try new event system first if (instance.trigger !== undefined) { - instance.trigger(eventType); + instance.trigger(eventType, data, extras); } // Try deprecated event system else if (instance.$ !== undefined && instance.$.trigger !== undefined) { @@ -1415,7 +1481,7 @@ H5P.on = function(instance, eventType, handler) { /** * Create UUID - * + * * @returns {String} UUID */ H5P.createUUID = function() { @@ -1441,9 +1507,209 @@ H5P.createH5PTitle = function(rawTitle, maxLength) { return title; }; -H5P.jQuery(document).ready(function () { - if (!H5P.preventInit) { - // Start script need to be an external resource to load in correct order for IE9. - H5P.init(document.body); +// Wrap in privates +(function ($) { + + /** + * Creates ajax requests for inserting, updateing and deleteing + * content user data. + * + * @private + * @param {number} contentId What content to store the data for. + * @param {string} dataType Identifies the set of data for this content. + * @param {string} subContentId Identifies sub content + * @param {function} [done] Callback when ajax is done. + * @param {object} [data] To be stored for future use. + * @param {boolean} [preload=false] Data is loaded when content is loaded. + * @param {boolean} [invalidate=false] Data is invalidated when content changes. + * @param {boolean} [async=true] + */ + function contentUserDataAjax(contentId, dataType, subContentId, done, data, preload, invalidate, async) { + var options = { + url: H5PIntegration.ajaxPath + 'content-user-data/' + contentId + '/' + dataType + '/' + (subContentId ? subContentId : 0), + dataType: 'json', + async: async === undefined ? true : async + }; + if (data !== undefined) { + options.type = 'POST'; + options.data = { + data: (data === null ? 0 : data), + preload: (preload ? 1 : 0), + invalidate: (invalidate ? 1 : 0) + }; + } + else { + options.type = 'GET'; + } + if (done !== undefined) { + options.error = function (xhr, error) { + done(error); + }; + options.success = function (response) { + if (!response.success) { + done(response.error); + return; + } + + if (response.data === false || response.data === undefined) { + done(); + return; + } + + done(undefined, response.data); + }; + } + + $.ajax(options); } -}); + + /** + * Get user data for given content. + * + * @public + * @param {number} contentId What content to get data for. + * @param {string} dataId Identifies the set of data for this content. + * @param {function} done Callback with error and data parameters. + * @param {string} [subContentId] Identifies which data belongs to sub content. + */ + H5P.getUserData = function (contentId, dataId, done, subContentId) { + if (!subContentId) { + subContentId = 0; // Default + } + + var content = H5PIntegration.contents['cid-' + contentId]; + var preloadedData = content.contentUserData; + if (preloadedData && preloadedData[subContentId] && preloadedData[subContentId][dataId]) { + if (preloadedData[subContentId][dataId] === 'RESET') { + done(undefined, null); + return; + } + try { + done(undefined, JSON.parse(preloadedData[subContentId][dataId])); + } + catch (err) { + done(err); + } + } + else { + contentUserDataAjax(contentId, dataId, subContentId, function (err, data) { + if (err || data === undefined) { + done(err, data); + return; // Error or no data + } + + // Cache in preloaded + if (content.contentUserData === undefined) { + content.contentUserData = preloaded = {}; + } + if (preloadedData[subContentId] === undefined) { + preloadedData[subContentId] = {}; + } + preloadedData[subContentId][dataId] = data; + + // Done. Try to decode JSON + try { + done(undefined, JSON.parse(data)); + } + catch (e) { + done(e); + } + }); + } + }; + + /** + * Set user data for given content. + * + * @public + * @param {number} contentId What content to get data for. + * @param {string} dataId Identifies the set of data for this content. + * @param {object} data The data that is to be stored. + * @param {string} [subContentId] Identifies which data belongs to sub content. + * @param {boolean} [preloaded=false] If the data should be loaded when content is loaded. + * @param {boolean} [deleteOnChange=false] If the data should be invalidated when the content changes. + * @param {function} [errorCallback] Callback with error as parameters. + */ + H5P.setUserData = function (contentId, dataId, data, subContentId, preloaded, deleteOnChange, errorCallback, async) { + if (!subContentId) { + subContentId = 0; // Default + } + + try { + data = JSON.stringify(data); + } + catch (err) { + errorCallback(err); + return; // Failed to serialize. + } + + var content = H5PIntegration.contents['cid-' + contentId]; + if (!content.contentUserData) { + content.contentUserData = {}; + } + var preloadedData = content.contentUserData; + if (preloadedData[subContentId] === undefined) { + preloadedData[subContentId] = {}; + } + if (data === preloadedData[subContentId][dataId]) { + return; // No need to save this twice. + } + + preloadedData[subContentId][dataId] = data; + contentUserDataAjax(contentId, dataId, subContentId, function (error, data) { + if (errorCallback && error) { + errorCallback(error); + } + }, data, preloaded, deleteOnChange, async); + }; + + /** + * Delete user data for given content. + * + * @public + * @param {number} contentId What content to remove data for. + * @param {string} dataId Identifies the set of data for this content. + * @param {string} [subContentId] Identifies which data belongs to sub content. + */ + H5P.deleteUserData = function (contentId, dataId, subContentId) { + if (!subContentId) { + subContentId = 0; // Default + } + + // Remove from preloaded/cache + var preloadedData = H5PIntegration.contents['cid-' + contentId].contentUserData; + if (preloadedData && preloadedData[subContentId] && preloadedData[subContentId][dataId]) { + delete preloadedData[subContentId][dataId]; + } + + contentUserDataAjax(contentId, dataId, subContentId, undefined, null); + }; + + // Init H5P when page is fully loadded + $(document).ready(function () { + if (!H5P.preventInit) { + // Note that this start script has to be an external resource for it to + // load in correct order in IE9. + H5P.init(document.body); + } + + if (H5PIntegration.saveFreq !== false) { + // Store the current state of the H5P when leaving the page. + H5P.$window.on('beforeunload', function () { + for (var i = 0; i < H5P.instances.length; i++) { + var instance = H5P.instances[i]; + if (instance.getCurrentState instanceof Function || + typeof instance.getCurrentState === 'function') { + var state = instance.getCurrentState(); + if (state !== undefined) { + // Async is not used to prevent the request from being cancelled. + H5P.setUserData(instance.contentId, 'state', state, undefined, true, true, undefined, false); + + } + } + } + }); + } + }); + +})(H5P.jQuery); diff --git a/styles/h5p.css b/styles/h5p.css index e6bd6fd..a9ecaf9 100644 --- a/styles/h5p.css +++ b/styles/h5p.css @@ -390,3 +390,18 @@ div.h5p-fullscreen { min-height: 30px; line-height: 30px; } +.h5p-dialog-ok-button { + cursor: default; + float: right; + outline: none; + border: 2px solid #ccc; + padding: 0.25em 0.75em 0.125em; + background: #eee; +} +.h5p-dialog-ok-button:hover, +.h5p-dialog-ok-button:focus { + background: #fafafa; +} +.h5p-dialog-ok-button:active { + background: #eeffee; +} From 5c3620c637085a5dcdcaa6ddc8e845a905a4b5ec Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Tue, 7 Apr 2015 19:33:21 +0200 Subject: [PATCH 72/75] Bubbling for event system --- h5p.classes.php | 8 ++--- js/h5p-event-dispatcher.js | 68 +++++++++++++++++++++++++++++++++----- js/h5p-x-api-event.js | 14 ++++---- js/h5p-x-api.js | 2 +- 4 files changed, 72 insertions(+), 20 deletions(-) diff --git a/h5p.classes.php b/h5p.classes.php index 681cb23..62f3011 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -1606,7 +1606,7 @@ class H5PCore { public static $coreApi = array( 'majorVersion' => 1, - 'minorVersion' => 4 + 'minorVersion' => 5 ); public static $styles = array( 'styles/h5p.css', @@ -2749,13 +2749,13 @@ class H5PContentValidator { 'type' => 'group', 'fields' => $library['semantics'], ), FALSE); - $validkeys = array('library', 'params', 'uuid'); + $validkeys = array('library', 'params', 'subContentId'); if (isset($semantics->extraAttributes)) { $validkeys = array_merge($validkeys, $semantics->extraAttributes); } $this->filterParams($value, $validkeys); - if (isset($value->uuid) && ! preg_match('/^\{?[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\}?$/', $value->uuid)) { - unset($value->uuid); + if (isset($value->subContentId) && ! preg_match('/^\{?[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\}?$/', $value->subContentId)) { + unset($value->subContentId); } // Find all dependencies for this library diff --git a/js/h5p-event-dispatcher.js b/js/h5p-event-dispatcher.js index d67ee10..1d52ef7 100644 --- a/js/h5p-event-dispatcher.js +++ b/js/h5p-event-dispatcher.js @@ -9,18 +9,55 @@ 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 + * + * @returns {undefined} + */ 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 + * - false otherwise + */ + this.scheduleForExternal = function() { + if (external && !scheduledForExternal) { + scheduledForExternal = true; + return true; + } + return false; + }; }; H5P.EventDispatcher = (function () { @@ -144,27 +181,42 @@ H5P.EventDispatcher = (function () { * Custom event data(used when event type as string is used as first * argument */ - this.trigger = function (event, eventData) { + this.trigger = function (event, eventData, extras) { if (event === undefined) { return; } if (typeof event === 'string') { - event = new H5P.Event(event, eventData); + event = new H5P.Event(event, eventData, extras); } else if (eventData !== undefined) { event.data = eventData; } - if (triggers[event.type] === undefined) { - return; + + // 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); + } } - // 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 i = 0; i < triggers['*'].length; i++) { + triggers['*'][i].listener.call(triggers['*'][i].thisArg, event); + } } + // Bubble if (event.getBubbles() && self.parent instanceof H5P.EventDispatcher && typeof self.parent.trigger === 'function') { self.parent.trigger(event); - } + } + + if (scheduledForExternal) { + H5P.externalDispatcher.trigger(event); + } }; } diff --git a/js/h5p-x-api-event.js b/js/h5p-x-api-event.js index 9b30675..a8513a4 100644 --- a/js/h5p-x-api-event.js +++ b/js/h5p-x-api-event.js @@ -6,7 +6,7 @@ var H5P = H5P || {}; * @class */ H5P.XAPIEvent = function() { - H5P.Event.call(this, 'xAPI', {'statement': {}}, {bubbles: true}); + H5P.Event.call(this, 'xAPI', {'statement': {}}, {bubbles: true, external: true}); }; H5P.XAPIEvent.prototype = Object.create(H5P.Event.prototype); @@ -87,8 +87,8 @@ H5P.XAPIEvent.prototype.setObject = function(instance) { } } }; - if (instance.uuid) { - this.data.statement.object.definition.extensions['http://h5p.org/x-api/h5p-uuid'] = instance.uuid; + if (instance.subContentId) { + this.data.statement.object.definition.extensions['http://h5p.org/x-api/h5p-subContentId'] = instance.subContentId; // Don't set titles on main content, title should come from publishing platform if (typeof instance.getH5PTitle === 'function') { this.data.statement.object.definition.name = { @@ -112,8 +112,8 @@ H5P.XAPIEvent.prototype.setObject = function(instance) { * @param {object} instance - the H5P instance */ H5P.XAPIEvent.prototype.setContext = function(instance) { - if (instance.parent && (instance.parent.contentId || instance.parent.uuid)) { - var parentId = instance.parent.uuid === undefined ? instance.parent.contentId : instance.parent.uuid; + if (instance.parent && (instance.parent.contentId || instance.parent.subContentId)) { + var parentId = instance.parent.subContentId === undefined ? instance.parent.contentId : instance.parent.subContentId; this.data.statement.context = { "contextActivities": { "parent": [ @@ -179,8 +179,8 @@ H5P.XAPIEvent.prototype.getContentXAPIId = function (instance) { var xAPIId; if (instance.contentId && H5PIntegration && H5PIntegration.contents) { xAPIId = H5PIntegration.contents['cid-' + instance.contentId].url; - if (instance.uuid) { - xAPIId += '?uuid=' + instance.uuid; + if (instance.subContentId) { + xAPIId += '?subContentId=' + instance.subContentId; } } return xAPIId; diff --git a/js/h5p-x-api.js b/js/h5p-x-api.js index 9d0e7cd..fa5e51a 100644 --- a/js/h5p-x-api.js +++ b/js/h5p-x-api.js @@ -4,7 +4,7 @@ var H5P = H5P || {}; H5P.externalDispatcher = new H5P.EventDispatcher(); if (H5P.isFramed && H5P.externalEmbed !== true) { - H5P.externalDispatcher.on('xAPI', window.top.H5P.externalDispatcher.trigger); + H5P.externalDispatcher.on('*', window.top.H5P.externalDispatcher.trigger); } // EventDispatcher extensions From 5b55b78ded471d670f7145bf10eb4017c3e50b7b Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Tue, 7 Apr 2015 19:52:18 +0200 Subject: [PATCH 73/75] createH5PTitle->createTitle --- js/h5p-x-api-event.js | 2 +- js/h5p.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/js/h5p-x-api-event.js b/js/h5p-x-api-event.js index a8513a4..d26a3b7 100644 --- a/js/h5p-x-api-event.js +++ b/js/h5p-x-api-event.js @@ -99,7 +99,7 @@ H5P.XAPIEvent.prototype.setObject = function(instance) { else { if (H5PIntegration && H5PIntegration.contents && H5PIntegration.contents['cid-' + instance.contentId].title) { this.data.statement.object.definition.name = { - "en-US": H5P.createH5PTitle(H5PIntegration.contents['cid-' + instance.contentId].title) + "en-US": H5P.createTitle(H5PIntegration.contents['cid-' + instance.contentId].title) }; } } diff --git a/js/h5p.js b/js/h5p.js index c359c1b..918b0c5 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -1491,7 +1491,7 @@ H5P.createUUID = function() { }); }; -H5P.createH5PTitle = function(rawTitle, maxLength) { +H5P.createTitle = function(rawTitle, maxLength) { if (maxLength === undefined) { maxLength = 60; } From 5b36e468acc3fa9d77258a5ba6ed5aef3f473396 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Thu, 9 Apr 2015 14:00:00 +0200 Subject: [PATCH 74/75] Rewrite so that we add options as an object instead of a very long list of parameters --- js/h5p.js | 45 ++++++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index 918b0c5..15889b7 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -143,7 +143,7 @@ H5P.init = function (target) { var saveTimer, save = function () { var state = instance.getCurrentState(); if (state !== undefined) { - H5P.setUserData(contentId, 'state', state, undefined, true, true); + H5P.setUserData(contentId, 'state', state, {deleteOnChange: true}); } if (H5PIntegration.saveFreq) { // Continue autosave @@ -1625,21 +1625,28 @@ H5P.createTitle = function(rawTitle, maxLength) { * @param {number} contentId What content to get data for. * @param {string} dataId Identifies the set of data for this content. * @param {object} data The data that is to be stored. - * @param {string} [subContentId] Identifies which data belongs to sub content. - * @param {boolean} [preloaded=false] If the data should be loaded when content is loaded. - * @param {boolean} [deleteOnChange=false] If the data should be invalidated when the content changes. - * @param {function} [errorCallback] Callback with error as parameters. + * @param {object} extras - object holding the following properties: + * - {string} [subContentId] Identifies which data belongs to sub content. + * - {boolean} [preloaded=true] If the data should be loaded when content is loaded. + * - {boolean} [deleteOnChange=false] If the data should be invalidated when the content changes. + * - {function} [errorCallback] Callback with error as parameters. + * - {boolean} [async=true] */ - H5P.setUserData = function (contentId, dataId, data, subContentId, preloaded, deleteOnChange, errorCallback, async) { - if (!subContentId) { - subContentId = 0; // Default - } + H5P.setUserData = function (contentId, dataId, data, extras) { + var options = H5P.jQuery.extend(true, {}, { + subContentId: 0, + preloaded: true, + deleteOnChange: false, + async: true + }, extras); try { data = JSON.stringify(data); } catch (err) { - errorCallback(err); + if (options.errorCallback) { + options.errorCallback(err); + } return; // Failed to serialize. } @@ -1648,19 +1655,19 @@ H5P.createTitle = function(rawTitle, maxLength) { content.contentUserData = {}; } var preloadedData = content.contentUserData; - if (preloadedData[subContentId] === undefined) { - preloadedData[subContentId] = {}; + if (preloadedData[options.subContentId] === undefined) { + preloadedData[options.subContentId] = {}; } - if (data === preloadedData[subContentId][dataId]) { + if (data === preloadedData[options.subContentId][dataId]) { return; // No need to save this twice. } - preloadedData[subContentId][dataId] = data; - contentUserDataAjax(contentId, dataId, subContentId, function (error, data) { - if (errorCallback && error) { - errorCallback(error); + preloadedData[options.subContentId][dataId] = data; + contentUserDataAjax(contentId, dataId, options.subContentId, function (error, data) { + if (options.errorCallback && error) { + options.errorCallback(error); } - }, data, preloaded, deleteOnChange, async); + }, data, options.preloaded, options.deleteOnChange, options.async); }; /** @@ -1703,7 +1710,7 @@ H5P.createTitle = function(rawTitle, maxLength) { var state = instance.getCurrentState(); if (state !== undefined) { // Async is not used to prevent the request from being cancelled. - H5P.setUserData(instance.contentId, 'state', state, undefined, true, true, undefined, false); + H5P.setUserData(instance.contentId, 'state', state, {deleteOnChange: true, async: false}); } } From 497301d6adc34088244d5f8269739c0494de0bc9 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Thu, 9 Apr 2015 19:42:05 +0200 Subject: [PATCH 75/75] Add triggerXAPIScored and deprecate triggerXAPICompleted --- js/h5p-x-api.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/js/h5p-x-api.js b/js/h5p-x-api.js index fa5e51a..ef7ccbd 100644 --- a/js/h5p-x-api.js +++ b/js/h5p-x-api.js @@ -51,11 +51,25 @@ H5P.EventDispatcher.prototype.createXAPIEventTemplate = function(verb, extra) { /** * Helper function to create xAPI completed events * + * DEPRECATED - USE triggerXAPIScored instead + * * @param {int} score - will be set as the 'raw' value of the score object * @param {int} maxScore - will be set as the "max" value of the score object */ H5P.EventDispatcher.prototype.triggerXAPICompleted = function(score, maxScore) { - var event = this.createXAPIEventTemplate('completed'); + this.triggerXAPIScored(score, maxScore, 'completed'); +}; + +/** + * Helper function to create scored xAPI events + * + * + * @param {int} score - will be set as the 'raw' value of the score object + * @param {int} maxScore - will be set as the "max" value of the score object + * @param {string} verb - short form of adl verb + */ +H5P.EventDispatcher.prototype.triggerXAPIScored = function(score, maxScore, verb) { + var event = this.createXAPIEventTemplate(verb); event.setScoredResult(score, maxScore); this.trigger(event); };