diff --git a/h5p.classes.php b/h5p.classes.php index 45329b0..a4436ae 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -1632,7 +1632,7 @@ class H5PCore { public static $coreApi = array( 'majorVersion' => 1, - 'minorVersion' => 5 + 'minorVersion' => 6 ); public static $styles = array( 'styles/h5p.css', @@ -1643,6 +1643,7 @@ class H5PCore { 'js/h5p-event-dispatcher.js', 'js/h5p-x-api-event.js', 'js/h5p-x-api.js', + 'js/h5p-content-type.js', ); public static $adminScripts = array( 'js/jquery.js', diff --git a/js/h5p-content-type.js b/js/h5p-content-type.js new file mode 100644 index 0000000..afe4136 --- /dev/null +++ b/js/h5p-content-type.js @@ -0,0 +1,36 @@ +/** + * H5P.ContentType is a base class for all content types. Used by newRunnable() + * + * Functions here may be overridable by the libraries. In special cases, + * it is also possible to override H5P.ContentType on a global level. + * */ +H5P.ContentType = function (isRootLibrary, library) { + + function ContentType() {}; + + // Inherit from EventDispatcher. + ContentType.prototype = new H5P.EventDispatcher(); + + /** + * Is library standalone or not? Not beeing standalone, means it is + * included in another library + * + * @method isStandalone + * @return {Boolean} + */ + ContentType.prototype.isRoot = function () { + return isRootLibrary; + }; + + /** + * Returns the file path of a file in the current library + * @method getLibraryFilePath + * @param {string} filePath The path to the file relative to the library folder + * @return {string} The full path to the file + */ + ContentType.prototype.getLibraryFilePath = function (filePath) { + return H5P.getLibraryPath(this.libraryInfo.versionedNameNoSpaces) + '/' + filePath; + }; + + return ContentType; +}; diff --git a/js/h5p-x-api-event.js b/js/h5p-x-api-event.js index a973dee..98b287a 100644 --- a/js/h5p-x-api-event.js +++ b/js/h5p-x-api-event.js @@ -19,7 +19,7 @@ H5P.XAPIEvent.prototype.constructor = H5P.XAPIEvent; * @param {number} score * @param {number} maxScore */ -H5P.XAPIEvent.prototype.setScoredResult = function (score, maxScore) { +H5P.XAPIEvent.prototype.setScoredResult = function (score, maxScore, instance) { this.data.statement.result = { 'score': { 'min': 0, @@ -27,6 +27,15 @@ H5P.XAPIEvent.prototype.setScoredResult = function (score, maxScore) { 'raw': score } }; + if (maxScore > 0) { + this.data.statement.result.score.scaled = Math.round(score / maxScore * 10000) / 10000; + } + if (instance && instance.activityStartTime) { + var duration = Math.round((Date.now() - instance.activityStartTime ) / 10) / 100; + // xAPI spec allows a precision of 0.01 seconds + + this.data.statement.result.duration = 'PT' + duration + 'S'; + } }; /** @@ -131,6 +140,17 @@ H5P.XAPIEvent.prototype.setContext = function (instance) { } }; } + if (instance.libraryInfo) { + if (this.data.statement.context === undefined) { + this.data.statement.context = {"contextActivities":{}}; + } + this.data.statement.context.contextActivities.category = [ + { + "id": "http://h5p.org/libraries/" + instance.libraryInfo.versionedNameNoSpaces, + "objectType": "Activity" + } + ]; + } }; /** diff --git a/js/h5p-x-api.js b/js/h5p-x-api.js index ea5ba79..e287c1a 100644 --- a/js/h5p-x-api.js +++ b/js/h5p-x-api.js @@ -82,10 +82,14 @@ H5P.EventDispatcher.prototype.triggerXAPICompleted = function (score, maxScore) */ H5P.EventDispatcher.prototype.triggerXAPIScored = function (score, maxScore, verb) { var event = this.createXAPIEventTemplate(verb); - event.setScoredResult(score, maxScore); + event.setScoredResult(score, maxScore, this); this.trigger(event); }; +H5P.EventDispatcher.prototype.setActivityStarted = function() { + this.activityStartTime = Date.now(); +}; + /** * Internal H5P function listening for xAPI completed events and stores scores * diff --git a/js/h5p.js b/js/h5p.js index 9bf7c21..0d1ff2f 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -135,7 +135,7 @@ H5P.init = function (target) { }); // Create new instance. - var instance = H5P.newRunnable(library, contentId, $container, true); + var instance = H5P.newRunnable(library, contentId, $container, true, {standalone: true}); // Check if we should add and display a fullscreen button for this H5P. if (contentData.fullScreen == 1 && H5P.canHasFullScreen) { @@ -182,6 +182,7 @@ H5P.init = function (target) { // Insert action bar if it has any content if (!(contentData.disable & H5P.DISABLE_FRAME) && $actions.children().length) { $actions.insertAfter($container); + $element.addClass('h5p-frame'); } else { $element.addClass('h5p-no-frame'); @@ -682,9 +683,6 @@ H5P.classFromName = function (name) { * Instance. */ H5P.newRunnable = function (library, contentId, $attachTo, skipResize, extras) { - // TODO: Should we check if version matches the library? - // TODO: Dynamically try to load libraries currently not loaded? That will require a callback. - var nameSplit, versionSplit, machineName; try { nameSplit = library.library.split(' ', 2); @@ -727,6 +725,11 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize, extras) { extras.previousState = library.userDatas.state; } + // Makes all H5P libraries extend H5P.ContentType: + var standalone = extras.standalone || false; + // This order makes it possible for an H5P library to override H5P.ContentType functions! + constructor.prototype = H5P.jQuery.extend({}, H5P.ContentType(standalone).prototype, constructor.prototype); + var instance; // Some old library versions have their own custom third parameter. // Make sure we don't send them the extras. @@ -751,8 +754,18 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize, extras) { if (instance.parent === undefined && extras && extras.parent) { instance.parent = extras.parent; } + if (instance.libraryInfo === undefined) { + instance.libraryInfo = { + versionedName: library.library, + versionedNameNoSpaces: machineName + '-' + versionSplit[0] + '.' + versionSplit[1], + machineName: machineName, + majorVersion: versionSplit[0], + minorVersion: versionSplit[1] + }; + } if ($attachTo !== undefined) { + $attachTo.toggleClass('h5p-standalone', standalone); instance.attach($attachTo); H5P.trigger(instance, 'domChanged', { '$target': $attachTo,