From ad9a2fea011c4128aab25412cf0c11d120605d3d Mon Sep 17 00:00:00 2001 From: Oliver Tacke Date: Tue, 6 Oct 2020 17:32:06 +0200 Subject: [PATCH 01/16] KL-1519 Fix crash on missing background image --- image-hotspot-question.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/image-hotspot-question.js b/image-hotspot-question.js index 31d2d84..38cac56 100644 --- a/image-hotspot-question.js +++ b/image-hotspot-question.js @@ -149,7 +149,7 @@ H5P.ImageHotspotQuestion = (function ($, Question) { this.$img = $('', { 'class': 'hotspot-image', - 'src': H5P.getPath(this.imageSettings.path, this.contentId) + 'src': (this.imageSettings.path && this.imageSettings.path !== '') ? H5P.getPath(this.imageSettings.path, this.contentId) : '' }); // Resize image once loaded From 26831e401de85b7fee48537e0f05d7cf94aaf772 Mon Sep 17 00:00:00 2001 From: Oliver Tacke Date: Tue, 6 Oct 2020 20:00:08 +0200 Subject: [PATCH 02/16] KL-1519 Fix score on reset --- image-hotspot-question.js | 1 + 1 file changed, 1 insertion(+) diff --git a/image-hotspot-question.js b/image-hotspot-question.js index 38cac56..2521b0d 100644 --- a/image-hotspot-question.js +++ b/image-hotspot-question.js @@ -366,6 +366,7 @@ H5P.ImageHotspotQuestion = (function ($, Question) { this.hotspotFeedback.$element.remove(); } this.hotspotFeedback.hotspotChosen = false; + this.score = 0; // Hide retry button this.hideButton('retry-button'); From 992fdd1d117c2c31f411ffa705202c6a370b4160 Mon Sep 17 00:00:00 2001 From: Oliver Tacke Date: Tue, 6 Oct 2020 20:27:36 +0200 Subject: [PATCH 03/16] KL-1519 Fix showing solutions triggering scoring --- image-hotspot-question.js | 77 +++++++++++++++++++++++++++++++++------ 1 file changed, 66 insertions(+), 11 deletions(-) diff --git a/image-hotspot-question.js b/image-hotspot-question.js index 2521b0d..593f558 100644 --- a/image-hotspot-question.js +++ b/image-hotspot-question.js @@ -290,6 +290,70 @@ H5P.ImageHotspotQuestion = (function ($, Question) { this.triggerXAPIScored(this.getScore(), this.getMaxScore(), 'answered'); }; + /** + * Show first correct hotspot. + */ + ImageHotspotQuestion.prototype.showCorrectHotspot = function () { + const self = this; + + if (this.getScore() === this.getMaxScore()) { + return; // Already showing a correct hotspot + } + + this.resetTask(); + + let foundSolution = false; + + let $clickedElement, mouseEvent, hotspot; + + this.hotspotSettings.hotspot.forEach(function (spot, index) { + if (spot.userSettings.correct && !foundSolution) { + var $correctHotspot = self.$hotspots[index]; + + $clickedElement = $correctHotspot; + mouseEvent = {offsetX: ($correctHotspot.width() / 2), offsetY: ($correctHotspot.height() / 2)}; + hotspot = spot; + + foundSolution = true; + } + }); + + // Do not create new hotspot if one exists + if (this.hotspotFeedback.hotspotChosen) { + return; + } + + this.hotspotFeedback.$element = $('
', { + 'class': 'hotspot-feedback' + }).appendTo(this.$imageWrapper); + + this.hotspotFeedback.hotspotChosen = true; + + // Center hotspot feedback on mouse click with fallback for firefox + var feedbackPosX = (mouseEvent.offsetX || mouseEvent.pageX - $(mouseEvent.target).offset().left); + var feedbackPosY = (mouseEvent.offsetY || mouseEvent.pageY - $(mouseEvent.target).offset().top); + + // Apply clicked element offset if click was not in wrapper + if (!$clickedElement.hasClass('image-wrapper')) { + feedbackPosX += $clickedElement.position().left; + feedbackPosY += $clickedElement.position().top; + } + + // Keep position and pixel offsets for resizing + this.hotspotFeedback.percentagePosX = feedbackPosX / (this.$imageWrapper.width() / 100); + this.hotspotFeedback.percentagePosY = feedbackPosY / (this.$imageWrapper.height() / 100); + this.hotspotFeedback.pixelOffsetX = (this.hotspotFeedback.$element.width() / 2); + this.hotspotFeedback.pixelOffsetY = (this.hotspotFeedback.$element.height() / 2); + + // Position feedback + this.resizeHotspotFeedback(); + + this.hotspotFeedback.$element.addClass('correct'); + + // Finally add fade in animation to hotspot feedback + this.hotspotFeedback.$element.addClass('fade-in'); + }; + /** * Create retry button and add it to button bar. */ @@ -340,20 +404,11 @@ H5P.ImageHotspotQuestion = (function ($, Question) { }; /** - * Display the first found solution for this question. + * Display solution for this question. * Used in contracts */ ImageHotspotQuestion.prototype.showSolutions = function () { - var self = this; - var foundSolution = false; - - this.hotspotSettings.hotspot.forEach(function (hotspot, index) { - if (hotspot.userSettings.correct && !foundSolution) { - var $correctHotspot = self.$hotspots[index]; - self.createHotspotFeedback($correctHotspot, {offsetX: ($correctHotspot.width() / 2), offsetY: ($correctHotspot.height() / 2)}, hotspot); - foundSolution = true; - } - }); + this.showCorrectHotspot(); }; /** From 29c0c8884de2c956c8da7f2bb3b0dbfae1279324 Mon Sep 17 00:00:00 2001 From: Oliver Tacke Date: Tue, 6 Oct 2020 20:29:54 +0200 Subject: [PATCH 04/16] KL-1519 Fix missing question type contract variable --- image-hotspot-question.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/image-hotspot-question.js b/image-hotspot-question.js index 593f558..2b06593 100644 --- a/image-hotspot-question.js +++ b/image-hotspot-question.js @@ -30,7 +30,8 @@ H5P.ImageHotspotQuestion = (function ($, Question) { } }, behaviour: { - enableRetry: true + enableRetry: true, + enableSolutionsButton: false }, scoreBarLabel: 'You got :num out of :total points', a11yRetry: 'Retry the task. Reset all responses and start the task over again.', From ac399b66a0aa7bf9e4140ab7362566f9b24e0bb4 Mon Sep 17 00:00:00 2001 From: Oliver Tacke Date: Tue, 6 Oct 2020 21:01:25 +0200 Subject: [PATCH 05/16] KL-1519 Fix getAnswerGiven --- image-hotspot-question.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/image-hotspot-question.js b/image-hotspot-question.js index 2b06593..36f3063 100644 --- a/image-hotspot-question.js +++ b/image-hotspot-question.js @@ -383,7 +383,7 @@ H5P.ImageHotspotQuestion = (function ($, Question) { * @returns {boolean} */ ImageHotspotQuestion.prototype.getAnswerGiven = function () { - return this.hotspotChosen; + return this.hotspotFeedback.hotspotChosen; }; /** From 0e44674b06c2d5d2ae27987d40184b6b5b4a386f Mon Sep 17 00:00:00 2001 From: Oliver Tacke Date: Tue, 6 Oct 2020 21:26:13 +0200 Subject: [PATCH 06/16] KL-1519 Add getXAPIData function for Question Type contract --- image-hotspot-question.js | 84 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 83 insertions(+), 1 deletion(-) diff --git a/image-hotspot-question.js b/image-hotspot-question.js index 36f3063..042c5ab 100644 --- a/image-hotspot-question.js +++ b/image-hotspot-question.js @@ -46,6 +46,12 @@ H5P.ImageHotspotQuestion = (function ($, Question) { */ this.contentId = id; + /** + * Extra data. + * @type {object} + */ + this.contentData = contentData; + /** * Keeps track of current score. * @type {number} @@ -288,7 +294,8 @@ H5P.ImageHotspotQuestion = (function ($, Question) { this.hotspotFeedback.$element.addClass('fade-in'); // Trigger xAPI completed event - this.triggerXAPIScored(this.getScore(), this.getMaxScore(), 'answered'); + this.trigger(this.getXAPIAnswerEvent()); + // this.triggerXAPIScored(this.getScore(), this.getMaxScore(), 'answered'); }; /** @@ -431,6 +438,79 @@ H5P.ImageHotspotQuestion = (function ($, Question) { this.removeFeedback(); }; + /** + * Get xAPI data. + * @return {object} XAPI statement. + * @see contract at {@link https://h5p.org/documentation/developers/contracts#guides-header-6} + */ + ImageHotspotQuestion.prototype.getXAPIData = function () { + return ({statement: this.getXAPIAnswerEvent().data.statement}); + }; + + /** + * Build xAPI answer event. + * @return {H5P.XAPIEvent} XAPI answer event. + */ + ImageHotspotQuestion.prototype.getXAPIAnswerEvent = function () { + const xAPIEvent = this.createImageHotspotQuestionXAPIEvent('answered'); + + // Set reporting module version if alternative extension is used + const definition = xAPIEvent.getVerifiedStatementValue(['object', 'definition']); + + xAPIEvent.setScoredResult(this.getScore(), this.getMaxScore(), this, + true, this.getScore() === this.getMaxScore()); + + return xAPIEvent; + }; + + /** + * Create an xAPI event for ImageHotspotQuestion. + * @param {string} verb Short id of the verb we want to trigger. + * @return {H5P.XAPIEvent} Event template. + */ + ImageHotspotQuestion.prototype.createImageHotspotQuestionXAPIEvent = function (verb) { + const xAPIEvent = this.createXAPIEventTemplate(verb); + + $.extend(true, xAPIEvent.getVerifiedStatementValue(['object', 'definition']), this.getxAPIDefinition()); + + return xAPIEvent; + }; + + /** + * Get the xAPI definition for the xAPI object. + * @return {object} XAPI definition. + */ + ImageHotspotQuestion.prototype.getxAPIDefinition = function () { + return { + name: {'en-US': this.getTitle()}, + description: {'en-US': this.getDescription()}, + type: 'http://adlnet.gov/expapi/activities/cmi.interaction', + interactionType: 'choice' + } + }; + + /** + * Get tasks title. + * @return {string} Title. + */ + ImageHotspotQuestion.prototype.getTitle = function () { + let raw; + if (this.contentData && this.contentData.metadata) { + raw = this.contentData.metadata.title; + } + raw = raw || ImageHotspotQuestion.DEFAULT_DESCRIPTION; + + return H5P.createTitle(raw); + }; + + /** + * Get tasks description. + * @return {string} Description. + */ + ImageHotspotQuestion.prototype.getDescription = function () { + return this.params.imageHotspotQuestion.hotspotSettings.taskDescription || ImageHotspotQuestion.DEFAULT_DESCRIPTION; + }; + /** * Resize image and wrapper */ @@ -496,5 +576,7 @@ H5P.ImageHotspotQuestion = (function ($, Question) { }); }; + ImageHotspotQuestion.DEFAULT_DESCRIPTION = 'Image Hotspot Question'; + return ImageHotspotQuestion; }(H5P.jQuery, H5P.Question)); From 133342c998cd96be224f851053bcea191676f23c Mon Sep 17 00:00:00 2001 From: Oliver Tacke Date: Tue, 6 Oct 2020 21:41:37 +0200 Subject: [PATCH 07/16] Make comment match original comment --- image-hotspot-question.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/image-hotspot-question.js b/image-hotspot-question.js index 042c5ab..6371c94 100644 --- a/image-hotspot-question.js +++ b/image-hotspot-question.js @@ -412,7 +412,7 @@ H5P.ImageHotspotQuestion = (function ($, Question) { }; /** - * Display solution for this question. + * Display the first found solution for this question. * Used in contracts */ ImageHotspotQuestion.prototype.showSolutions = function () { From 25afe9c62a97f9c79b19723916a8002db77f72e0 Mon Sep 17 00:00:00 2001 From: Oliver Tacke Date: Tue, 6 Oct 2020 22:26:32 +0200 Subject: [PATCH 08/16] KL-1519 Fix answer given state on showing solution --- image-hotspot-question.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/image-hotspot-question.js b/image-hotspot-question.js index 6371c94..f937b51 100644 --- a/image-hotspot-question.js +++ b/image-hotspot-question.js @@ -335,8 +335,6 @@ H5P.ImageHotspotQuestion = (function ($, Question) { 'class': 'hotspot-feedback' }).appendTo(this.$imageWrapper); - this.hotspotFeedback.hotspotChosen = true; - // Center hotspot feedback on mouse click with fallback for firefox var feedbackPosX = (mouseEvent.offsetX || mouseEvent.pageX - $(mouseEvent.target).offset().left); var feedbackPosY = (mouseEvent.offsetY || mouseEvent.pageY - $(mouseEvent.target).offset().top); From 7e9d51463b55fda491a071a547d6dcf4a2c26574 Mon Sep 17 00:00:00 2001 From: Oliver Tacke Date: Tue, 6 Oct 2020 23:09:37 +0200 Subject: [PATCH 09/16] KL-1519 Fix showSolutions having mixed use variable --- image-hotspot-question.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/image-hotspot-question.js b/image-hotspot-question.js index f937b51..8b34eb6 100644 --- a/image-hotspot-question.js +++ b/image-hotspot-question.js @@ -335,6 +335,9 @@ H5P.ImageHotspotQuestion = (function ($, Question) { 'class': 'hotspot-feedback' }).appendTo(this.$imageWrapper); + // Required for resizing, although mixed purpose. Revert down below. + this.hotspotFeedback.hotspotChosen = true; + // Center hotspot feedback on mouse click with fallback for firefox var feedbackPosX = (mouseEvent.offsetX || mouseEvent.pageX - $(mouseEvent.target).offset().left); var feedbackPosY = (mouseEvent.offsetY || mouseEvent.pageY - $(mouseEvent.target).offset().top); @@ -354,6 +357,9 @@ H5P.ImageHotspotQuestion = (function ($, Question) { // Position feedback this.resizeHotspotFeedback(); + // Not needed here + this.hotspotFeedback.hotspotChosen = false; + this.hotspotFeedback.$element.addClass('correct'); // Finally add fade in animation to hotspot feedback From 17b120dcb9a0b86aed197ef77fb706d5c4ab4c8e Mon Sep 17 00:00:00 2001 From: Oliver Tacke Date: Wed, 7 Oct 2020 14:44:43 +0200 Subject: [PATCH 10/16] KL-1519 Add disabled state --- image-hotspot-question.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/image-hotspot-question.js b/image-hotspot-question.js index 8b34eb6..4ec6392 100644 --- a/image-hotspot-question.js +++ b/image-hotspot-question.js @@ -64,6 +64,12 @@ H5P.ImageHotspotQuestion = (function ($, Question) { */ this.maxScore = 1; + /** + * State for not accepting clicks + * @type {boolean} + */ + this.disabled = false; + /** * Keeps track of parameters */ @@ -185,6 +191,10 @@ H5P.ImageHotspotQuestion = (function ($, Question) { var self = this; this.$imageWrapper.click(function (mouseEvent) { + if (self.disabled) { + return false; + } + // Create new hotspot feedback self.createHotspotFeedback($(this), mouseEvent); }); @@ -215,6 +225,9 @@ H5P.ImageHotspotQuestion = (function ($, Question) { width: hotspot.computedSettings.width + '%', height: hotspot.computedSettings.height + '%' }).click(function (mouseEvent) { + if (self.disabled) { + return false; + } // Create new hotspot feedback self.createHotspotFeedback($(this), mouseEvent, hotspot); @@ -421,6 +434,7 @@ H5P.ImageHotspotQuestion = (function ($, Question) { */ ImageHotspotQuestion.prototype.showSolutions = function () { this.showCorrectHotspot(); + this.disabled = true; }; /** @@ -440,6 +454,8 @@ H5P.ImageHotspotQuestion = (function ($, Question) { // Clear feedback this.removeFeedback(); + + this.disabled = false; }; /** From e7c79d0626f20b04af8bf35407760f28b3543340 Mon Sep 17 00:00:00 2001 From: Oliver Tacke Date: Wed, 7 Oct 2020 15:05:55 +0200 Subject: [PATCH 11/16] KL-1519 Fix showSolutions --- image-hotspot-question.js | 76 ++++++++++++++++----------------------- 1 file changed, 31 insertions(+), 45 deletions(-) diff --git a/image-hotspot-question.js b/image-hotspot-question.js index 4ec6392..f8742bb 100644 --- a/image-hotspot-question.js +++ b/image-hotspot-question.js @@ -313,6 +313,7 @@ H5P.ImageHotspotQuestion = (function ($, Question) { /** * Show first correct hotspot. + * TODO: Wouldn't showing all solutions make more sense? */ ImageHotspotQuestion.prototype.showCorrectHotspot = function () { const self = this; @@ -321,62 +322,47 @@ H5P.ImageHotspotQuestion = (function ($, Question) { return; // Already showing a correct hotspot } - this.resetTask(); + // Remove old feedback + this.$wrapper.find('.hotspot-feedback').remove(); - let foundSolution = false; + // Find index of first solution + const index = self.hotspotSettings.hotspot.reduce(function (result, spot, index) { + return (result !== -1 || !spot.userSettings.correct) ? result : index; + }, -1); - let $clickedElement, mouseEvent, hotspot; - - this.hotspotSettings.hotspot.forEach(function (spot, index) { - if (spot.userSettings.correct && !foundSolution) { - var $correctHotspot = self.$hotspots[index]; - - $clickedElement = $correctHotspot; - mouseEvent = {offsetX: ($correctHotspot.width() / 2), offsetY: ($correctHotspot.height() / 2)}; - hotspot = spot; - - foundSolution = true; - } - }); - - // Do not create new hotspot if one exists - if (this.hotspotFeedback.hotspotChosen) { + if (index === -1) { return; } - this.hotspotFeedback.$element = $('
', { - 'class': 'hotspot-feedback' - }).appendTo(this.$imageWrapper); - - // Required for resizing, although mixed purpose. Revert down below. - this.hotspotFeedback.hotspotChosen = true; - - // Center hotspot feedback on mouse click with fallback for firefox - var feedbackPosX = (mouseEvent.offsetX || mouseEvent.pageX - $(mouseEvent.target).offset().left); - var feedbackPosY = (mouseEvent.offsetY || mouseEvent.pageY - $(mouseEvent.target).offset().top); + // Wrapper may not yet be visible if used as subcontent + setTimeout(function () { + self.hotspotFeedback.$element = $('
', { + 'class': 'hotspot-feedback' + }).appendTo(self.$imageWrapper); - // Apply clicked element offset if click was not in wrapper - if (!$clickedElement.hasClass('image-wrapper')) { - feedbackPosX += $clickedElement.position().left; - feedbackPosY += $clickedElement.position().top; - } + const $correctHotspot = self.$hotspots[index]; + const feedbackPosX = $correctHotspot.position().left + $correctHotspot.width() / 2; + const feedbackPosY = $correctHotspot.position().top + $correctHotspot.height() / 2; - // Keep position and pixel offsets for resizing - this.hotspotFeedback.percentagePosX = feedbackPosX / (this.$imageWrapper.width() / 100); - this.hotspotFeedback.percentagePosY = feedbackPosY / (this.$imageWrapper.height() / 100); - this.hotspotFeedback.pixelOffsetX = (this.hotspotFeedback.$element.width() / 2); - this.hotspotFeedback.pixelOffsetY = (this.hotspotFeedback.$element.height() / 2); + // Keep position and pixel offsets for resizing + self.hotspotFeedback.percentagePosX = feedbackPosX / (self.$imageWrapper.width() / 100); + self.hotspotFeedback.percentagePosY = feedbackPosY / (self.$imageWrapper.height() / 100); + self.hotspotFeedback.pixelOffsetX = (self.hotspotFeedback.$element.width() / 2); + self.hotspotFeedback.pixelOffsetY = (self.hotspotFeedback.$element.height() / 2); - // Position feedback - this.resizeHotspotFeedback(); + // Required for resizing, although mixed purpose. Revert down below. + self.hotspotFeedback.hotspotChosen = true; - // Not needed here - this.hotspotFeedback.hotspotChosen = false; + // Position feedback + self.resizeHotspotFeedback(); - this.hotspotFeedback.$element.addClass('correct'); + // Not needed here + self.hotspotFeedback.hotspotChosen = false; - // Finally add fade in animation to hotspot feedback - this.hotspotFeedback.$element.addClass('fade-in'); + // Finally add fade in animation to hotspot feedback + self.hotspotFeedback.$element.addClass('correct'); + self.hotspotFeedback.$element.addClass('fade-in'); + }, 0); }; /** From 7aa0268b17d9d91be7f27279b225a2f546644037 Mon Sep 17 00:00:00 2001 From: Oliver Tacke Date: Wed, 7 Oct 2020 16:20:23 +0200 Subject: [PATCH 12/16] KL-1519 Separate getAnswerGiven from feedback display --- image-hotspot-question.js | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/image-hotspot-question.js b/image-hotspot-question.js index f8742bb..221e7cc 100644 --- a/image-hotspot-question.js +++ b/image-hotspot-question.js @@ -65,11 +65,17 @@ H5P.ImageHotspotQuestion = (function ($, Question) { this.maxScore = 1; /** - * State for not accepting clicks + * State for not accepting clicks. * @type {boolean} */ this.disabled = false; + /** + * State for answer given. + * @type {boolean} + */ + this.answerGiven = false; + /** * Keeps track of parameters */ @@ -208,7 +214,6 @@ H5P.ImageHotspotQuestion = (function ($, Question) { this.hotspotSettings.hotspot.forEach(function (hotspot) { self.attachHotspot(hotspot); }); - }; /** @@ -253,6 +258,8 @@ H5P.ImageHotspotQuestion = (function ($, Question) { return; } + this.answerGiven = true; + this.hotspotFeedback.$element = $('
', { 'class': 'hotspot-feedback' }).appendTo(this.$imageWrapper); @@ -308,7 +315,6 @@ H5P.ImageHotspotQuestion = (function ($, Question) { // Trigger xAPI completed event this.trigger(this.getXAPIAnswerEvent()); - // this.triggerXAPIScored(this.getScore(), this.getMaxScore(), 'answered'); }; /** @@ -350,15 +356,10 @@ H5P.ImageHotspotQuestion = (function ($, Question) { self.hotspotFeedback.pixelOffsetX = (self.hotspotFeedback.$element.width() / 2); self.hotspotFeedback.pixelOffsetY = (self.hotspotFeedback.$element.height() / 2); - // Required for resizing, although mixed purpose. Revert down below. - self.hotspotFeedback.hotspotChosen = true; - // Position feedback + self.hotspotFeedback.hotspotChosen = true; self.resizeHotspotFeedback(); - // Not needed here - self.hotspotFeedback.hotspotChosen = false; - // Finally add fade in animation to hotspot feedback self.hotspotFeedback.$element.addClass('correct'); self.hotspotFeedback.$element.addClass('fade-in'); @@ -393,7 +394,7 @@ H5P.ImageHotspotQuestion = (function ($, Question) { * @returns {boolean} */ ImageHotspotQuestion.prototype.getAnswerGiven = function () { - return this.hotspotFeedback.hotspotChosen; + return this.answerGiven; }; /** @@ -442,6 +443,8 @@ H5P.ImageHotspotQuestion = (function ($, Question) { this.removeFeedback(); this.disabled = false; + + this.answerGiven = false; }; /** From 694b59fdb8641419191fc3b02cea574abcb8af31 Mon Sep 17 00:00:00 2001 From: Oliver Tacke Date: Wed, 7 Oct 2020 16:21:14 +0200 Subject: [PATCH 13/16] KL-1519 Show score bar on showing solution --- image-hotspot-question.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/image-hotspot-question.js b/image-hotspot-question.js index 221e7cc..dfd607f 100644 --- a/image-hotspot-question.js +++ b/image-hotspot-question.js @@ -420,7 +420,9 @@ H5P.ImageHotspotQuestion = (function ($, Question) { * Used in contracts */ ImageHotspotQuestion.prototype.showSolutions = function () { + this.hideButton('retry-button'); this.showCorrectHotspot(); + this.setFeedback('', this.getScore(), this.getMaxScore(), this.params.scoreBarLabel); this.disabled = true; }; From 7500b12df65436083912c2b92f0afbf1ed901c4b Mon Sep 17 00:00:00 2001 From: Oliver Tacke Date: Wed, 7 Oct 2020 17:33:25 +0200 Subject: [PATCH 14/16] KL-1519 Make showSolution work without DOM being visible --- image-hotspot-question.js | 62 ++++++++++++++------------------------- 1 file changed, 22 insertions(+), 40 deletions(-) diff --git a/image-hotspot-question.js b/image-hotspot-question.js index dfd607f..45e8ee1 100644 --- a/image-hotspot-question.js +++ b/image-hotspot-question.js @@ -318,52 +318,35 @@ H5P.ImageHotspotQuestion = (function ($, Question) { }; /** - * Show first correct hotspot. - * TODO: Wouldn't showing all solutions make more sense? + * Show all correct hotspots. */ - ImageHotspotQuestion.prototype.showCorrectHotspot = function () { + ImageHotspotQuestion.prototype.showCorrectHotspots = function () { const self = this; - if (this.getScore() === this.getMaxScore()) { - return; // Already showing a correct hotspot - } - // Remove old feedback this.$wrapper.find('.hotspot-feedback').remove(); - // Find index of first solution - const index = self.hotspotSettings.hotspot.reduce(function (result, spot, index) { - return (result !== -1 || !spot.userSettings.correct) ? result : index; - }, -1); - - if (index === -1) { - return; - } + this.hotspotSettings.hotspot.forEach(function (hotspot) { + if (!hotspot.userSettings.correct) { + return; // Skip, wrong hotspot + } - // Wrapper may not yet be visible if used as subcontent - setTimeout(function () { - self.hotspotFeedback.$element = $('
', { + // Compute and set position of feedback circle + const $element = $('
', { 'class': 'hotspot-feedback' }).appendTo(self.$imageWrapper); - const $correctHotspot = self.$hotspots[index]; - const feedbackPosX = $correctHotspot.position().left + $correctHotspot.width() / 2; - const feedbackPosY = $correctHotspot.position().top + $correctHotspot.height() / 2; + const centerX = (hotspot.computedSettings.x + hotspot.computedSettings.width / 2) + '%'; + const centerY = (hotspot.computedSettings.y + hotspot.computedSettings.height / 2) + '%'; - // Keep position and pixel offsets for resizing - self.hotspotFeedback.percentagePosX = feedbackPosX / (self.$imageWrapper.width() / 100); - self.hotspotFeedback.percentagePosY = feedbackPosY / (self.$imageWrapper.height() / 100); - self.hotspotFeedback.pixelOffsetX = (self.hotspotFeedback.$element.width() / 2); - self.hotspotFeedback.pixelOffsetY = (self.hotspotFeedback.$element.height() / 2); - - // Position feedback - self.hotspotFeedback.hotspotChosen = true; - self.resizeHotspotFeedback(); - - // Finally add fade in animation to hotspot feedback - self.hotspotFeedback.$element.addClass('correct'); - self.hotspotFeedback.$element.addClass('fade-in'); - }, 0); + $element + .css({ + left: 'calc(' + centerX + ' - ' + $element.width() / 2 + 'px' + ')', + top: 'calc(' + centerY + ' - ' + $element.height() / 2 + 'px' + ')' + }) + .addClass('correct') + .addClass('fade-in'); + }); }; /** @@ -421,7 +404,7 @@ H5P.ImageHotspotQuestion = (function ($, Question) { */ ImageHotspotQuestion.prototype.showSolutions = function () { this.hideButton('retry-button'); - this.showCorrectHotspot(); + this.showCorrectHotspots(); this.setFeedback('', this.getScore(), this.getMaxScore(), this.params.scoreBarLabel); this.disabled = true; }; @@ -431,10 +414,9 @@ H5P.ImageHotspotQuestion = (function ($, Question) { * Used in contracts. */ ImageHotspotQuestion.prototype.resetTask = function () { - // Remove hotspot feedback - if (this.hotspotFeedback.$element) { - this.hotspotFeedback.$element.remove(); - } + // Remove old feedback + this.$wrapper.find('.hotspot-feedback').remove(); + this.hotspotFeedback.hotspotChosen = false; this.score = 0; From 134ee5de95fe1893ab012fd5c56bea60f559b9ef Mon Sep 17 00:00:00 2001 From: Oliver Tacke Date: Wed, 7 Oct 2020 18:21:55 +0200 Subject: [PATCH 15/16] KL-1519 Add linter configuration --- .eslintrc.json | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .eslintrc.json diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..c92b651 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,32 @@ +{ + "globals": { + "H5P": true, + "H5PIntegration": true, + "H5PEditor": true + }, + "env": { + "browser": true + }, + "extends": "eslint:recommended", + "parserOptions": { + "sourceType": "module" + }, + "rules": { + "valid-jsdoc": "off", + "semi": [2, "always"], + "indent": ["error", 2, {"SwitchCase": 1}], + "brace-style": ["error", "stroustrup"], + "keyword-spacing": ["error", {"after": true}], + "comma-spacing": ["error", { "before": false, "after": true }], + "space-infix-ops": ["error", {"int32Hint": false}], + "eqeqeq": [2, "smart"], + "space-before-blocks": "error", + "space-before-function-paren": ["error", { + "anonymous": "always", + "named": "never", + "asyncArrow": "always" + }], + "no-extra-boolean-cast": "off", + "no-console": ["error", {"allow": ["warn", "error"]}] + } +} From 1d8eb45e3a20d0e14ab8829c598dfc30a60fb7a2 Mon Sep 17 00:00:00 2001 From: Oliver Tacke Date: Wed, 7 Oct 2020 18:22:20 +0200 Subject: [PATCH 16/16] KL-1519 Make eslint happy --- image-hotspot-question.js | 69 +++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 35 deletions(-) diff --git a/image-hotspot-question.js b/image-hotspot-question.js index 45e8ee1..0c86de5 100644 --- a/image-hotspot-question.js +++ b/image-hotspot-question.js @@ -11,9 +11,9 @@ H5P.ImageHotspotQuestion = (function ($, Question) { * @param {Object} contentData Task specific content data */ function ImageHotspotQuestion(params, id, contentData) { - var self = this; + const self = this; - var defaults = { + const defaults = { imageHotspotQuestion: { backgroundImageSettings: { backgroundImage: { @@ -145,13 +145,13 @@ H5P.ImageHotspotQuestion = (function ($, Question) { * @returns {H5P.jQuery} Wrapper */ ImageHotspotQuestion.prototype.createContent = function () { - var self = this; + const self = this; this.$wrapper = $('
', { 'class': 'image-hotspot-question' }); this.$wrapper.ready(function () { - var imageHeight = self.$wrapper.width() * (self.imageSettings.height / self.imageSettings.width); + const imageHeight = self.$wrapper.width() * (self.imageSettings.height / self.imageSettings.width); self.$wrapper.css('height', imageHeight + 'px'); }); @@ -161,7 +161,7 @@ H5P.ImageHotspotQuestion = (function ($, Question) { }).appendTo(this.$wrapper); // Image loader screen - var $loader = $('
', { + const $loader = $('
', { 'class': 'image-loader' }).appendTo(this.$imageWrapper) .addClass('loading'); @@ -182,7 +182,7 @@ H5P.ImageHotspotQuestion = (function ($, Question) { } else { - const $message = $('
') + $('
') .text('No background image was added!') .appendTo(this.$wrapper); } @@ -194,7 +194,7 @@ H5P.ImageHotspotQuestion = (function ($, Question) { * Initiate image click listener to capture clicks outside of defined hotspots. */ ImageHotspotQuestion.prototype.initImageClickListener = function () { - var self = this; + const self = this; this.$imageWrapper.click(function (mouseEvent) { if (self.disabled) { @@ -210,7 +210,7 @@ H5P.ImageHotspotQuestion = (function ($, Question) { * Attaches all hotspots. */ ImageHotspotQuestion.prototype.attachHotspots = function () { - var self = this; + const self = this; this.hotspotSettings.hotspot.forEach(function (hotspot) { self.attachHotspot(hotspot); }); @@ -221,8 +221,8 @@ H5P.ImageHotspotQuestion = (function ($, Question) { * @param {Object} hotspot Hotspot parameters */ ImageHotspotQuestion.prototype.attachHotspot = function (hotspot) { - var self = this; - var $hotspot = $('
', { + const self = this; + const $hotspot = $('
', { 'class': 'image-hotspot ' + hotspot.computedSettings.figure }).css({ left: hotspot.computedSettings.x + '%', @@ -267,8 +267,8 @@ H5P.ImageHotspotQuestion = (function ($, Question) { this.hotspotFeedback.hotspotChosen = true; // Center hotspot feedback on mouse click with fallback for firefox - var feedbackPosX = (mouseEvent.offsetX || mouseEvent.pageX - $(mouseEvent.target).offset().left); - var feedbackPosY = (mouseEvent.offsetY || mouseEvent.pageY - $(mouseEvent.target).offset().top); + let feedbackPosX = (mouseEvent.offsetX || mouseEvent.pageX - $(mouseEvent.target).offset().left); + let feedbackPosY = (mouseEvent.offsetY || mouseEvent.pageY - $(mouseEvent.target).offset().top); // Apply clicked element offset if click was not in wrapper if (!$clickedElement.hasClass('image-wrapper')) { @@ -289,20 +289,21 @@ H5P.ImageHotspotQuestion = (function ($, Question) { if (hotspot && hotspot.userSettings.correct) { this.hotspotFeedback.$element.addClass('correct'); this.finishQuestion(); - } else { + } + else { // Wrong answer, show retry button if (this.params.behaviour.enableRetry) { this.showButton('retry-button'); } } - var feedbackText = (hotspot && hotspot.userSettings.feedbackText ? hotspot.userSettings.feedbackText : this.params.imageHotspotQuestion.hotspotSettings.noneSelectedFeedback); + let feedbackText = (hotspot && hotspot.userSettings.feedbackText ? hotspot.userSettings.feedbackText : this.params.imageHotspotQuestion.hotspotSettings.noneSelectedFeedback); if (!feedbackText) { feedbackText = ' '; } // Send these settings into setFeedback to turn feedback into a popup. - var popupSettings = { + const popupSettings = { showAsPopup: this.params.imageHotspotQuestion.hotspotSettings.showFeedbackAsPopup, closeText: this.params.imageHotspotQuestion.hotspotSettings.l10n.closeText, click: this.hotspotFeedback @@ -353,7 +354,7 @@ H5P.ImageHotspotQuestion = (function ($, Question) { * Create retry button and add it to button bar. */ ImageHotspotQuestion.prototype.createRetryButton = function () { - var self = this; + const self = this; this.addButton('retry-button', this.params.imageHotspotQuestion.hotspotSettings.l10n.retryText, function () { self.resetTask(); @@ -447,9 +448,6 @@ H5P.ImageHotspotQuestion = (function ($, Question) { ImageHotspotQuestion.prototype.getXAPIAnswerEvent = function () { const xAPIEvent = this.createImageHotspotQuestionXAPIEvent('answered'); - // Set reporting module version if alternative extension is used - const definition = xAPIEvent.getVerifiedStatementValue(['object', 'definition']); - xAPIEvent.setScoredResult(this.getScore(), this.getMaxScore(), this, true, this.getScore() === this.getMaxScore()); @@ -479,7 +477,7 @@ H5P.ImageHotspotQuestion = (function ($, Question) { description: {'en-US': this.getDescription()}, type: 'http://adlnet.gov/expapi/activities/cmi.interaction', interactionType: 'choice' - } + }; }; /** @@ -497,12 +495,12 @@ H5P.ImageHotspotQuestion = (function ($, Question) { }; /** - * Get tasks description. - * @return {string} Description. - */ - ImageHotspotQuestion.prototype.getDescription = function () { - return this.params.imageHotspotQuestion.hotspotSettings.taskDescription || ImageHotspotQuestion.DEFAULT_DESCRIPTION; - }; + * Get tasks description. + * @return {string} Description. + */ + ImageHotspotQuestion.prototype.getDescription = function () { + return this.params.imageHotspotQuestion.hotspotSettings.taskDescription || ImageHotspotQuestion.DEFAULT_DESCRIPTION; + }; /** * Resize image and wrapper @@ -516,7 +514,7 @@ H5P.ImageHotspotQuestion = (function ($, Question) { * Resize image to fit parent width. */ ImageHotspotQuestion.prototype.resizeImage = function () { - var self = this; + const self = this; // Check that question has been attached if (!(this.$wrapper && this.$img)) { @@ -524,18 +522,19 @@ H5P.ImageHotspotQuestion = (function ($, Question) { } // Resize image to fit new container width. - var parentWidth = this.$wrapper.width(); + const parentWidth = this.$wrapper.width(); this.$img.width(parentWidth); // Find required height for new width. - var naturalWidth = this.$img.get(0).naturalWidth; - var naturalHeight = this.$img.get(0).naturalHeight; - var imageRatio = naturalHeight / naturalWidth; - var neededHeight = -1; + const naturalWidth = this.$img.get(0).naturalWidth; + const naturalHeight = this.$img.get(0).naturalHeight; + const imageRatio = naturalHeight / naturalWidth; + let neededHeight = -1; if (parentWidth < naturalWidth) { // Scale image down neededHeight = parentWidth * imageRatio; - } else { + } + else { // Scale image to natural size this.$img.width(naturalWidth); neededHeight = naturalHeight; @@ -559,8 +558,8 @@ H5P.ImageHotspotQuestion = (function ($, Question) { } // Calculate positions - var posX = (this.hotspotFeedback.percentagePosX * (this.$imageWrapper.width() / 100)) - this.hotspotFeedback.pixelOffsetX; - var posY = (this.hotspotFeedback.percentagePosY * (this.$imageWrapper.height() / 100)) - this.hotspotFeedback.pixelOffsetY; + const posX = (this.hotspotFeedback.percentagePosX * (this.$imageWrapper.width() / 100)) - this.hotspotFeedback.pixelOffsetX; + const posY = (this.hotspotFeedback.percentagePosY * (this.$imageWrapper.height() / 100)) - this.hotspotFeedback.pixelOffsetY; // Apply new positions this.hotspotFeedback.$element.css({