diff --git a/.gitignore b/.gitignore index e266972..c30f2fe 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *~ -/nbproject/private/ \ No newline at end of file +/nbproject/private/ +.idea \ No newline at end of file diff --git a/README.md b/README.md index 75e49ea..404ddce 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,16 @@ H5P Question Set ========== -Test your users with by putting together multiple tasks into a question set(quiz). +Test your users with by putting together multiple tasks into a question set(quiz) ## License (The MIT License) -Copyright (c) 2012-2014 Amendor AS - +Copyright (c) 2012-2014 Joubel AS + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/css/questionset.css b/css/questionset.css index 90cf581..f9dc83a 100644 --- a/css/questionset.css +++ b/css/questionset.css @@ -1,15 +1,3 @@ -/* IcoMoon font licensed under the GNU General Public License: http://www.gnu.org/licenses/gpl.html */ -@font-face { - font-family: 'icomoon-questionset'; - src:url('../fonts/icomoon.eot'); - src:url('../fonts/icomoon.eot?#iefix') format('embedded-opentype'), - url('../fonts/icomoon.woff') format('woff'), - url('../fonts/icomoon.ttf') format('truetype'), - url('../fonts/icomoon.svg#icomoon') format('svg'); - font-weight: normal; - font-style: normal; -} - .questionset-results h2 { font-size: 1.2em; font-weight: bold; @@ -52,7 +40,12 @@ background: #fff; background: #cecece; } -.progress-dot:hover { + +.progress-dot.disabled { + cursor: default; +} + +.progress-dot:not(.disabled):hover { box-shadow: 0 0 0.5em #c7c7c7; } .progress-dot.answered { @@ -61,6 +54,11 @@ .progress-dot.current { background: #285585; } + +.progress-dot.disabled:focus { + outline: none +} + .progress-dot:focus { outline-color: rgb(40, 130, 211); outline-width: thin; @@ -151,6 +149,12 @@ width: 2.1875em; } +.h5p-question .h5p-question-prev.truncated, +.h5p-question .h5p-question-next.truncated { + padding: 0; + line-height: 2.2335em; +} + .h5p-question .h5p-question-next, .h5p-question .h5p-question-finish { margin: 0 0 1.5em 0.5em; diff --git a/fonts/icomoon.eot b/fonts/icomoon.eot deleted file mode 100644 index d8cdc1a..0000000 Binary files a/fonts/icomoon.eot and /dev/null differ diff --git a/fonts/icomoon.svg b/fonts/icomoon.svg deleted file mode 100644 index 1a48364..0000000 --- a/fonts/icomoon.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - -Generated by IcoMoon - - - - - - - - - - \ No newline at end of file diff --git a/fonts/icomoon.ttf b/fonts/icomoon.ttf deleted file mode 100644 index 9649f7e..0000000 Binary files a/fonts/icomoon.ttf and /dev/null differ diff --git a/fonts/icomoon.woff b/fonts/icomoon.woff deleted file mode 100644 index 5d29f5d..0000000 Binary files a/fonts/icomoon.woff and /dev/null differ diff --git a/icon.svg b/icon.svg new file mode 100644 index 0000000..99a11b6 --- /dev/null +++ b/icon.svg @@ -0,0 +1,105 @@ + + + + + question-set + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/js/questionset.js b/js/questionset.js index bab8a12..a0acb78 100644 --- a/js/questionset.js +++ b/js/questionset.js @@ -1,4 +1,4 @@ -var H5P = H5P || {}; +H5P = H5P || {}; /** * Will render a Question with multiple choices for answers. @@ -39,14 +39,19 @@ H5P.QuestionSet = function (options, contentId, contentData) { ' <% } %>' + ' '; var defaults = { - randomOrder: false, initialQuestion: 0, progressType: 'dots', passPercentage: 50, @@ -126,18 +130,100 @@ H5P.QuestionSet = function (options, contentId, contentData) { var endTemplate = new EJS({text: resulttemplate}); var params = $.extend(true, {}, defaults, options); + var initialParams = $.extend(true, {}, defaults, options); + var poolOrder; // Order of questions in a pool var currentQuestion = 0; var questionInstances = []; + var questionOrder; //Stores order of questions to allow resuming of question set var $myDom; var scoreBar; var up; var renderSolutions = false; + var showingSolutions = false; contentData = contentData || {}; + + // Bring question set up to date when resuming if (contentData.previousState) { - currentQuestion = contentData.previousState.progress; + if (contentData.previousState.progress) { + currentQuestion = contentData.previousState.progress; + } + questionOrder = contentData.previousState.order; } + /** + * Randomizes questions in an array and updates an array containing their order + * @param {array} questions + * @return {Object.} questionOrdering + */ + var randomizeQuestionOrdering = function (questions) { + + // Save the original order of the questions in a multidimensional array [[question0,0],[question1,1]... + var questionOrdering = questions.map(function(questionInstance, index) { return [questionInstance, index] }); + + // Shuffle the multidimensional array + questionOrdering = H5P.shuffleArray(questionOrdering); + + // Retrieve question objects from the first index + var questions = []; + for (var i = 0; i < questionOrdering.length; i++) { + questions[i] = questionOrdering[i][0]; + } + + // Retrieve the new shuffled order from the second index + var newOrder = []; + for (var i = 0; i< questionOrdering.length; i++) { + + // Use a previous order if it exists + if(contentData.previousState && contentData.previousState.questionOrder) { + newOrder[i] = questionOrder[questionOrdering[i][1]]; + } + else { + newOrder[i] = questionOrdering[i][1]; + } + } + + // Return the questions in their new order *with* their new indexes + return { + questions: questions, + questionOrder: newOrder + }; + }; + + // Create a pool (a subset) of questions if necessary + if (params.poolSize > 0) { + + // If a previous pool exists, recreate it + if(contentData.previousState && contentData.previousState.poolOrder) { + poolOrder = contentData.previousState.poolOrder; + + // Recreate the pool from the saved data + var pool = []; + for (var i = 0; i < poolOrder.length; i++) { + pool[i] = params.questions[poolOrder[i]]; + } + + // Replace original questions with just the ones in the pool + params.questions = pool; + } + else { // Otherwise create a new pool + // Randomize and get the results + var poolResult = randomizeQuestionOrdering(params.questions); + var poolQuestions = poolResult.questions; + poolOrder = poolResult.questionOrder; + + // Discard extra questions + + poolQuestions = poolQuestions.slice(0, params.poolSize); + poolOrder = poolOrder.slice(0, params.poolSize); + + // Replace original questions with just the ones in the pool + params.questions = poolQuestions; + } + } + + // Create the html template for the question container var $template = $(template.render(params)); + // Set overrides for questions var override; if (params.override.showSolutionButton || params.override.retryButton) { @@ -155,29 +241,61 @@ H5P.QuestionSet = function (options, contentId, contentData) { } } - // Instantiate question instances - for (var i = 0; i < params.questions.length; i++) { - var question = params.questions[i]; + /** + * Generates question instances from H5P objects + * + * @param {object} questions H5P content types to be created as instances + * @return {array} Array of questions instances + */ + var createQuestionInstancesFromQuestions = function(questions) { + var result = []; + // Create question instances from questions + // Instantiate question instances + for (var i = 0; i < questions.length; i++) { - if (override) { - // Extend subcontent with the overrided settings. - $.extend(question.params.behaviour, override); + var question; + // If a previous order exists, use it + if (questionOrder !== undefined) { + question = questions[questionOrder[i]]; + } + else { + // Use a generic order when initialzing for the first time + question = questions[i]; + } + + if (override) { + // Extend subcontent with the overrided settings. + $.extend(question.params.behaviour, override); + } + + question.params = question.params || {}; + question.params.overrideSettings = question.params.overrideSettings || {}; + question.params.overrideSettings.$confirmationDialogParent = $template.last(); + question.params.overrideSettings.instance = this; + var hasAnswers = contentData.previousState && contentData.previousState.answers; + var questionInstance = H5P.newRunnable(question, contentId, undefined, undefined, + { + previousState: hasAnswers ? contentData.previousState.answers[i] : undefined, + parent: self + }); + questionInstance.on('resize', function () { + up = true; + self.trigger('resize'); + }); + result.push(questionInstance); } - question.params = question.params || {}; - question.params.overrideSettings = question.params.overrideSettings || {}; - question.params.overrideSettings.$confirmationDialogParent = $template.last(); - question.params.overrideSettings.instance = this; - var questionInstance = H5P.newRunnable(question, contentId, undefined, undefined, - { - previousState: contentData.previousState ? contentData.previousState.answers[i] : undefined, - parent: self - }); - questionInstance.on('resize', function () { - up = true; - self.trigger('resize'); - }); - questionInstances.push(questionInstance); + return result; + } + + // Create question instances from questions given by params + questionInstances = createQuestionInstancesFromQuestions(params.questions); + + // Randomize questions only on instantiation + if (params.randomQuestions && contentData.previousState === undefined) { + var result = randomizeQuestionOrdering(questionInstances); + questionInstances = result.questions; + questionOrder = result.questionOrder; } // Resize all interactions on resize @@ -195,6 +313,17 @@ H5P.QuestionSet = function (options, contentId, contentData) { // Update button state. var _updateButtons = function () { + // Verify that current question is answered when backward nav is disabled + if (params.disableBackwardsNavigation) { + if (questionInstances[currentQuestion].getAnswerGiven() + && questionInstances.length-1 !== currentQuestion) { + questionInstances[currentQuestion].showButton('next'); + } + else { + questionInstances[currentQuestion].hideButton('next'); + } + } + var answered = true; for (var i = questionInstances.length - 1; i >= 0; i--) { answered = answered && (questionInstances[i]).getAnswerGiven(); @@ -284,7 +413,18 @@ H5P.QuestionSet = function (options, contentId, contentData) { * @public */ var showSolutions = function () { + showingSolutions = true; for (var i = 0; i < questionInstances.length; i++) { + + // Enable back and forth navigation in solution mode + toggleDotsNavigation(true); + if (i < questionInstances.length - 1) { + questionInstances[i].showButton('next'); + } + if (i > 0) { + questionInstances[i].showButton('prev'); + } + try { // Do not read answers questionInstances[i].toggleReadSpeaker(true); @@ -298,15 +438,50 @@ H5P.QuestionSet = function (options, contentId, contentData) { } }; + /** + * Toggles whether dots are enabled for navigation + */ + var toggleDotsNavigation = function (enable) { + $('.progress-dot', $myDom).each(function () { + $(this).toggleClass('disabled', !enable); + $(this).attr('aria-disabled', enable ? 'false' : 'true'); + // Remove tabindex + if (!enable) { + $(this).attr('tabindex', '-1'); + } + }); + }; + /** * Resets the task and every subcontent task. * Used for contracts with integrated content. * @public */ var resetTask = function () { + + // Clear previous state to ensure questions are created cleanly + contentData.previousState = []; + + showingSolutions = false; + for (var i = 0; i < questionInstances.length; i++) { try { questionInstances[i].resetTask(); + + // Hide back and forth navigation in normal mode + if (params.disableBackwardsNavigation) { + toggleDotsNavigation(false); + + // Check if first question is answered by default + if (i === 0 && questionInstances[i].getAnswerGiven()) { + questionInstances[i].showButton('next'); + } + else { + questionInstances[i].hideButton('next'); + } + + questionInstances[i].hideButton('prev'); + } } catch(error) { H5P.error("subcontent does not contain a valid resetTask function"); @@ -324,6 +499,32 @@ H5P.QuestionSet = function (options, contentId, contentData) { //Force the last page to be reRendered rendered = false; + + if(params.poolSize > 0){ + + // Make new pool from params.questions + // Randomize and get the results + var poolResult = randomizeQuestionOrdering(initialParams.questions); + var poolQuestions = poolResult.questions; + poolOrder = poolResult.questionOrder; + + // Discard extra questions + poolQuestions = poolQuestions.slice(0, params.poolSize); + poolOrder = poolOrder.slice(0, params.poolSize); + + // Replace original questions with just the ones in the pool + params.questions = poolQuestions; + + // Recreate the question instances + questionInstances = createQuestionInstancesFromQuestions(params.questions); + + // Update buttons + initializeQuestion(); + + } else if (params.randomQuestions) { + randomizeQuestions(); + } + }; var rendered = false; @@ -332,18 +533,86 @@ H5P.QuestionSet = function (options, contentId, contentData) { rendered = false; }; + /** + * Randomizes question instances + */ + var randomizeQuestions = function () { + + var result = randomizeQuestionOrdering(questionInstances); + questionInstances = result.questions; + questionOrder = result.questionOrder; + + replaceQuestionsInDOM(questionInstances); + }; + + /** + * Empty the DOM of all questions, attach new questions and update buttons + * + * @param {type} questionInstances Array of questions to be attached to the DOM + */ + var replaceQuestionsInDOM = function (questionInstances) { + + // Find all question containers and detach questions from them + $('.question-container', $myDom).each(function (){ + $(this).children().detach(); + }); + + // Reattach questions and their buttons in the new order + for (var i = 0; i < questionInstances.length; i++) { + + var question = questionInstances[i]; + + // Make sure styles are not being added twice + $('.question-container:eq(' + i + ')', $myDom).attr('class', 'question-container'); + + question.attach($('.question-container:eq(' + i + ')', $myDom)); + + //Show buttons if necessary + if(questionInstances[questionInstances.length -1] === question + && question.hasButton('finish')) { + question.showButton('finish'); + } + + if(questionInstances[questionInstances.length -1] !== question + && question.hasButton('next')) { + question.showButton('next'); + } + + if(questionInstances[0] !== question + && question.hasButton('prev') + && !params.disableBackwardsNavigation) { + question.showButton('prev'); + } + + // Hide relevant buttons since the order has changed + if (questionInstances[0] === question) { + question.hideButton('prev'); + } + + if (questionInstances[questionInstances.length-1] === question) { + question.hideButton('next'); + } + + if (questionInstances[questionInstances.length-1] !== question) { + question.hideButton('finish'); + } + } + }; + var moveQuestion = function (direction) { + if (params.disableBackwardsNavigation && !questionInstances[currentQuestion].getAnswerGiven()) { + questionInstances[currentQuestion].hideButton('next'); + questionInstances[currentQuestion].hideButton('finish'); + return; + } + _stopQuestion(currentQuestion); if (currentQuestion + direction >= questionInstances.length) { _displayEndGame(); - - } - else if (!params.disableBackwardsNavigation || questionInstances[currentQuestion].getAnswerGiven()) { - // Allow movement if backward navigation enabled or answer given - _showQuestion(currentQuestion + direction); } else { - //TODO: Give an error message ? or disable/grey out previous button when not allowed + // Allow movement if backward navigation enabled or answer given + _showQuestion(currentQuestion + direction); } }; @@ -394,9 +663,10 @@ H5P.QuestionSet = function (options, contentId, contentData) { label += ', ' + texts.currentQuestionText; } + var disabledTabindex = params.disableBackwardsNavigation && !showingSolutions; $el.toggleClass('current', isCurrent) .attr('aria-label', label) - .attr('tabindex', isCurrent ? 0 : -1); + .attr('tabindex', isCurrent && !disabledTabindex ? 0 : -1); }; var _displayEndGame = function () { @@ -412,7 +682,7 @@ H5P.QuestionSet = function (options, contentId, contentData) { // Get total score. var finals = self.getScore(); - var totals = self.totalScore(); + var totals = self.getMaxScore(); var scoreString = params.endGame.scoreString.replace("@score", finals).replace("@total", totals); var success = ((100 * finals / totals) >= params.passPercentage); var eventData = { @@ -437,7 +707,7 @@ H5P.QuestionSet = function (options, contentId, contentData) { }; var displayResults = function () { - self.triggerXAPICompleted(self.getScore(), self.totalScore(), success); + self.triggerXAPICompleted(self.getScore(), self.getMaxScore(), success); var eparams = { message: params.endGame.showResultPage ? params.endGame.message : params.endGame.noResultMessage, @@ -528,7 +798,7 @@ H5P.QuestionSet = function (options, contentId, contentData) { }); video.play(); - if (params.endGame.skipButtonText) { + if (params.endGame.skippable) { $('').click(function () { video.pause(); $videoContainer.hide(); @@ -544,7 +814,76 @@ H5P.QuestionSet = function (options, contentId, contentData) { self.trigger('resize'); }; - // Function for attaching the multichoice to a DOM element. + var registerImageLoadedListener = function (question) { + H5P.on(question, 'imageLoaded', function () { + self.trigger('resize'); + }); + }; + + /** + * Initialize a question and attach it to the DOM + * + */ + function initializeQuestion() { + // Attach questions + for (var i = 0; i < questionInstances.length; i++) { + var question = questionInstances[i]; + + // Make sure styles are not being added twice + $('.question-container:eq(' + i + ')', $myDom).attr('class', 'question-container'); + + question.attach($('.question-container:eq(' + i + ')', $myDom)); + + // Listen for image resize + registerImageLoadedListener(question); + + // Add finish button + question.addButton('finish', params.texts.finishButton, + moveQuestion.bind(this, 1), false); + + // Add next button + question.addButton('next', '', moveQuestion.bind(this, 1), + !params.disableBackwardsNavigation || !!question.getAnswerGiven(), { + href: '#', // Use href since this is a navigation button + 'aria-label': params.texts.nextButton + }); + + // Add previous button + question.addButton('prev', '', moveQuestion.bind(this, -1), + !(questionInstances[0] === question || params.disableBackwardsNavigation), { + href: '#', // Use href since this is a navigation button + 'aria-label': params.texts.prevButton + }); + + // Hide next button if it is the last question + if(questionInstances[questionInstances.length -1] === question) { + question.hideButton('next'); + } + + question.on('xAPI', function (event) { + var shortVerb = event.getVerb(); + if (shortVerb === 'interacted' || + shortVerb === 'answered' || + shortVerb === 'attempted') { + toggleAnsweredDot(currentQuestion, + questionInstances[currentQuestion].getAnswerGiven()); + _updateButtons(); + } + if (shortVerb === 'completed') { + // An activity within this activity is not allowed to send completed events + event.setVerb('answered'); + } + if (event.data.statement.context.extensions === undefined) { + event.data.statement.context.extensions = {}; + } + event.data.statement.context.extensions['http://id.tincanapi.com/extension/ending-point'] = currentQuestion + 1; + }); + + // Mark question if answered + toggleAnsweredDot(i, question.getAnswerGiven()); + } + } + this.attach = function (target) { if (this.isRoot()) { this.setActivityStarted(); @@ -579,67 +918,8 @@ H5P.QuestionSet = function (options, contentId, contentData) { }); } } - var registerImageLoadedListener = function (question) { - H5P.on(question, 'imageLoaded', function () { - self.trigger('resize'); - }); - }; - // Attach questions - for (var i = 0; i < questionInstances.length; i++) { - var question = questionInstances[i]; - - question.attach($('.question-container:eq(' + i + ')', $myDom)); - - // Listen for image resize - registerImageLoadedListener(question); - - // Add next/finish button - if (questionInstances[questionInstances.length -1] === question) { - - // Add finish question set button - question.addButton('finish', params.texts.finishButton, - moveQuestion.bind(this, 1), false); - - } else { - - // Add next question button - question.addButton('next', '', moveQuestion.bind(this, 1), true, { - href: '#', // Use href since this is a navigation button - 'aria-label': params.texts.nextButton - }); - } - - // Add previous question button - if (questionInstances[0] !== question && !params.disableBackwardsNavigation) { - question.addButton('prev', '', moveQuestion.bind(this, -1), true, { - href: '#', // Use href since this is a navigation button - 'aria-label': params.texts.prevButton - }); - } - - question.on('xAPI', function (event) { - var shortVerb = event.getVerb(); - if (shortVerb === 'interacted' || - shortVerb === 'answered' || - shortVerb === 'attempted') { - toggleAnsweredDot(currentQuestion, - questionInstances[currentQuestion].getAnswerGiven()); - _updateButtons(); - } - if (shortVerb === 'completed') { - // An activity within this activity is not allowed to send completed events - event.setVerb('answered'); - } - if (event.data.statement.context.extensions === undefined) { - event.data.statement.context.extensions = {}; - } - event.data.statement.context.extensions['http://id.tincanapi.com/extension/ending-point'] = currentQuestion + 1; - }); - - // Mark question if answered - toggleAnsweredDot(i, question.getAnswerGiven()); - } + initializeQuestion(); // Allow other libraries to add transitions after the questions have been inited $('.questionset', $myDom).addClass('started'); @@ -657,9 +937,13 @@ H5P.QuestionSet = function (options, contentId, contentData) { * @param {Object} [event] */ var handleProgressDotClick = function (event) { + // Disable dots when backward nav disabled + event.preventDefault(); + if (params.disableBackwardsNavigation && !showingSolutions) { + return; + } _stopQuestion(currentQuestion); _showQuestion($(this).parent().index()); - event.preventDefault(); }; // Set event listeners. @@ -719,7 +1003,7 @@ H5P.QuestionSet = function (options, contentId, contentData) { }; // Get total score possible for questionset. - this.totalScore = function () { + this.getMaxScore = function () { var score = 0; for (var i = questionInstances.length - 1; i >= 0; i--) { score += questionInstances[i].getMaxScore(); @@ -727,6 +1011,14 @@ H5P.QuestionSet = function (options, contentId, contentData) { return score; }; + /** + * @deprecated since version 1.9.2 + * @returns {number} + */ + this.totalScore = function () { + return this.getMaxScore(); + }; + /** * Gather copyright information for the current content. * @@ -821,17 +1113,74 @@ H5P.QuestionSet = function (options, contentId, contentData) { /** * Returns the complete state of question set and sub-content * - * @returns {Object} + * @returns {Object} current state */ this.getCurrentState = function () { - var state = { - progress: currentQuestion, + return { + progress: showingSolutions ? questionInstances.length - 1 : currentQuestion, answers: questionInstances.map(function (qi) { return qi.getCurrentState(); - }) + }), + order: questionOrder, + poolOrder: poolOrder + }; + }; + + /** + * Generate xAPI object definition used in xAPI statements. + * @return {Object} + */ + var getxAPIDefinition = function () { + var definition = {}; + + definition.interactionType = 'compound'; + definition.type = 'http://adlnet.gov/expapi/activities/cmi.interaction'; + definition.description = { + 'en-US': '' }; - return state; + return definition; + }; + + /** + * Add the question itself to the definition part of an xAPIEvent + */ + var addQuestionToXAPI = function(xAPIEvent) { + var definition = xAPIEvent.getVerifiedStatementValue(['object', 'definition']); + $.extend(definition, getxAPIDefinition()); + }; + + /** + * Get xAPI data from sub content types + * + * @param {Object} metaContentType + * @returns {array} + */ + var getXAPIDataFromChildren = function(metaContentType) { + return metaContentType.getQuestions().map(function(question) { + return question.getXAPIData(); + }); + }; + + /** + * Get xAPI data. + * Contract used by report rendering engine. + * + * @see contract at {@link https://h5p.org/documentation/developers/contracts#guides-header-6} + */ + this.getXAPIData = function(){ + var xAPIEvent = this.createXAPIEventTemplate('answered'); + addQuestionToXAPI(xAPIEvent); + xAPIEvent.setScoredResult(this.getScore(), + this.getMaxScore(), + this, + true, + this.getScore() === this.getMaxScore() + ); + return { + statement: xAPIEvent.data.statement, + children: getXAPIDataFromChildren(this) + } }; }; diff --git a/language/ar.json b/language/ar.json index dd9f71c..07fc570 100644 --- a/language/ar.json +++ b/language/ar.json @@ -107,6 +107,14 @@ "label": "Disable backwards navigation", "description": "This option will only allow you to move forward in Question Set" }, + { + "label": "Randomize questions", + "description": "Enable to randomize the order of questions on display." + }, + { + "label": "Number of questions to be shown:", + "description": "Create a randomized batch of questions from the total." + }, { "label": "انتهاء المسابقة", "fields": [ diff --git a/language/de.json b/language/de.json index 51bc558..1713d8b 100644 --- a/language/de.json +++ b/language/de.json @@ -1,7 +1,7 @@ { "semantics": [ { - "label": "Quiz Einführung", + "label": "Quiz-Einführung", "fields": [ { "label": "Zeige Einführung" @@ -15,7 +15,7 @@ "description": "Dieser Text wird angezeigt, bevor das Quiz anfängt." }, { - "label": "Start Button Text", + "label": "Beschriftung des \"Start\"-Buttons", "default": "Starte Quiz" }, { @@ -26,11 +26,11 @@ }, { "label": "Hintergrundbild", - "description": "Ein optionales Hintergrundbild für das Frage Set." + "description": "Ein optionales Hintergrundbild für das Fragenset." }, { "label": "Fortschrittsanzeige", - "description": "Anzeigestil für die Fortschrittsanzeige des Frage Sets.", + "description": "Anzeigestil für die Fortschrittsanzeige des Fragensets.", "options": [ { "label": "Text" @@ -42,7 +42,7 @@ }, { "label": "Prozent bearbeitet", - "description": "Prozent der Gesamtpunkte sind notwendig um das Quiz zu bestehen." + "description": "Prozentsatz der Gesamtpunkte, die notwendig sind, um das Quiz zu bestehen." }, { "label": "Fragen", @@ -64,53 +64,61 @@ "label": "Anzeigetexte im Quiz", "fields": [ { - "label": "Rückwärts Button", - "default": "vorherige" + "label": "Beschriftung des \"Zurück\"-Buttons", + "default": "Zurück" }, { - "label": "Weiter Button", - "default": "weiter" + "label": "Beschriftung des \"Weiter\"-Buttons", + "default": "Weiter" }, { - "label": "Ende Button", - "default": "beenden" + "label": "Beschriftung des \"Beenden\"-Buttons", + "default": "Beenden" }, { "label": "Fortschrittstext", - "description": "Verwendeter Text wenn Text Fortschrittsanzeige gewählt wurde.", + "description": "Verwendeter Text, wenn Fortschrittsanzeige in Textform gewählt wurde.", "default": "Aktuelle Frage: @current von @total Fragen" }, { - "label": "Label for jumping to a certain question", - "description": "You must use the placeholder '%d' instead of the question number, and %total instead of total amount of questions.", - "default": "Question %d of %total" + "label": "Beschriftung für das Springen zu einem bestimmten Punkt", + "description": "Nutze den Platzhalter '%d' für die Fragennummer und %total für die Gesamtzahl der Fragen.", + "default": "Frage %d von %total" }, { - "label": "Copyright dialog question label", - "default": "Question" + "label": "Beschriftung für eine Frage zum Urheberrechtsdialog", + "default": "Frage" }, { - "label": "Readspeaker progress", - "description": "May use @current and @total question variables", - "default": "Question @current of @total" + "label": "Fortschritt \"Readspeaker\"", + "description": "Variablen: @current und @total", + "default": "Frage @current von @total" }, { - "label": "Unanswered question text", - "default": "Unanswered" + "label": "Text für unbeantwortete Fragen", + "default": "Unbeantwortet" }, { - "label": "Answered question text", - "default": "Answered" + "label": "Text für beantwortete Fragen", + "default": "Beantwortet" }, { - "label": "Current question text", - "default": "Current question" + "label": "Text fpr aktuelle Frage", + "default": "Aktuelle Frage" } ] }, { - "label": "Disable backwards navigation", - "description": "This option will only allow you to move forward in Question Set" + "label": "Möglichkeit zum Rückwärts-Springen deaktivieren", + "description": "Wenn aktiviert, kann der Nutzer nur vorwärts durch das Fragenset navigieren." + }, + { + "label": "Fragen zufällig anordnen", + "description": "Aktivieren, um die Reihenfolge der Fragen zufällig zu bestimmen." + }, + { + "label": "Anzahl der anzuzeigenden Fragen:", + "description": "Kann benutzt werden, um eine zufällige Auswahl auf mehreren Fragen zu treffen." }, { "label": "Quiz beendet", @@ -119,101 +127,99 @@ "label": "Ergebnisanzeige" }, { - "label": "Display solution button" + "label": "Hinweis bei keinen Ergebnissen", + "description": "Text, der am Ende angezeigt wird, falls \"Ergebnisse anzeigen\" deaktiviert ist", + "default": "Beendet" }, { - "label": "No results message", - "description": "Text displayed on end page when \"Display results\" is disabled", - "default": "Finished" - }, - { - "label": "Kopfzeile Feedback", + "label": "Kopfzeile für die Rückmeldungen", "default": "Dein Ergebnis:", - "description": "diese Kopfzeile wird am Ende des Quiz angezeigt, wenn der Benutzer alle Fragen beantwortet hat." + "description": "diese Kopfzeile wird am Ende des Quizzes angezeigt, wenn der Nutzer alle Fragen beantwortet hat." }, { - "label": "Punkt Anzeige Text", - "description": "Dieser Text wird verwendet um die Gesamtpunkte des Benutzers anzuzeigen. \"@score\" wird durch die errechneten Punkte ersetzt, \"@total\" wird durch die maximal erreichbaren Punkte ersetzt.", + "label": "Text für die Punkteanzeige", + "description": "Dieser Text wird verwendet, um die Gesamtpunkte des Nutzers anzuzeigen. \"@score\" wird durch die errechnete Punktzahl ersetzt, \"@total\" wird durch die maximal erreichbare Punktzahl ersetzt.", "default": "Du hast @score Punkte von @total möglichen." }, { - "label": "Quiz bestanden Mitteilung", - "default": "Gratulation!", - "description": "Dieser Text wird oberhalb der Punkte angezeigt, wenn der Benutzer das Quiz erfolgreich abgeschlossen hat." + "label": "Kopfzeile für \"Quiz bestanden\"", + "placeholder": "Glückwunsch!", + "default": "Glückwunsch!", + "description": "Dieser Text wird oberhalb der Punkte angezeigt, wenn der Nutzer das Quiz erfolgreich abgeschlossen hat." }, { - "label": "Bestanden Kommentar", + "label": "Kommentar für bestandenes Quiz", "default": "Gut gemacht!", - "description": "dieser Kommentar wird nach den Punkten angezeigt, wenn der Benutzer das Quiz erfolgreich bestanden hat." + "description": "Dieser Kommentar wird nach den Punkten angezeigt, wenn der Nutzer das Quiz erfolgreich bestanden hat." }, { - "label": "Quiz nicht bestanden Titel", + "label": "Kopfzeile für nicht bestandenes Quiz", "default": "Diese Mal hast du nicht bestanden.", - "description": "dieser Text wird oberhalb der Punkte angezeigt, wenn der Benutzer das Quiz nicht bestanden hat." + "description": "Dieser Text wird oberhalb der Punkte angezeigt, wenn der Nutzer das Quiz nicht bestanden hat." }, { - "label": "Nicht bestanden Kommentar", + "label": "Kommentar für nicht bestandenes Quiz", "default": "Versuche es noch einmal!", - "description": "Dieser Kommentar wird nach den Punkten angezeigt, wenn der Benutzer das Quiz nicht bestanden hat." + "description": "Dieser Kommentar wird nach den Punkten angezeigt, wenn der Nutzer das Quiz nicht bestanden hat." }, { - "label": "Lösungs Button Beschriftung", - "default": "Zeige die Lösung", - "description": "Text für den Lösungs Button." + "label": "Beschriftung des \"Lösung zeigen\"-Buttons", + "default": "Lösung zeigen", + "description": "Beschriftung des \"Lösung zeigen\"-Buttons" }, { - "label": "Wiederholen Button Beschriftung", - "default": "Versuch es nochmal", - "description": "Text für den Wiederholen Button." + "label": "Beschriftung des \"Wiederholen\"-Buttons", + "default": "Wiederholen", + "description": "Beschriftung des \"Wiederholen\"-Buttons" }, { - "label": "Beende Button Text", + "label": "Beschriftung des \"Beenden\"-Buttons", "default": "Beenden" }, { "label": "Zeige Video vor den Quizergebnissen" }, { - "label": "Aktiviere Skip Video Button" + "label": "Aktiviere \"Video überspringen\"-Button" }, { - "label": "Skip Video Button Beschriftung", - "default": "Überspringe Video" + "label": "Beschriftung des \"Video überspringen\"-Buttons", + "default": "Video überspringen" }, { - "label": "Bestanden Video", - "description": "Dieses Video wird angezeigt, wenn der Benutzer das Quiz erfolgreich bestanden hat." + "label": "Bestanden-Video", + "description": "Dieses Video wird angezeigt, wenn der Nutzer das Quiz erfolgreich bestanden hat." }, { - "label": "Nicht bestanden Video", - "description": "Dieses Video wird angezeigt, wenn der Benutzer das Quiz nicht bestanden hat." + "label": "Nicht-bestanden-Video", + "description": "Dieses Video wird angezeigt, wenn der Nutzer das Quiz nicht bestanden hat." } ] }, { - "label": "Einstellungen für \"Zeige die Lösung\" Button und \"Nochmal\".", + "label": "Einstellungen für \"Lösung zeigen\"-Button und \"Nochmal\".", "fields": [ { - "label": "Override \"Show Solution\" button", - "description": "This option determines if the \"Show Solution\" button will be shown for all questions, disabled for all or configured for each question individually.", + "label": "Überschreiben des \"Lösungen anzeigen\"-Buttons", + "description": "Diese Option gibt an, ob der \"Lösung anzeigen\"-Button für alle Fragen angezeigt, ausgeblendet oder für jede Frage individuell konfiguriert wird.", "options": [ { - "label": "Enabled" + "label": "Anzeigen" }, { - "label": "Disabled" + "label": "Ausblenden" } ] }, { - "label": "Override \"Retry\" button", - "description": "This option determines if the \"Retry\" button will be shown for all questions, disabled for all or configured for each question individually.", + "label": "Überschreiben des \"Wiederholen\"-Buttons", + "description": "Diese Option gibt an, ob der \"Wiederholen\" Button für alle Fragen angezeigt, ausgeblendet oder für jede Frage individuell konfiguriert wird.", "options": [ { - "label": "Enabled" + "label": "Anzeigen" }, { - "label": "Disabled" + "label": "Ausblenden" } ] } diff --git a/language/fr.json b/language/fr.json index 2cef1f0..1656009 100644 --- a/language/fr.json +++ b/language/fr.json @@ -109,8 +109,16 @@ ] }, { - "label": "Disable backwards navigation", - "description": "This option will only allow you to move forward in Question Set" + "label": "Désactiver la possibilité de naviguer en arrière", + "description": "Cette option ne permettra plus que la navigation en avant au sein du module Question Set" + }, + { + "label": "Afficher les question dans un ordre aléatoire", + "description": "Ceci permet d'afficher les questions dans un ordre aléatoire." + }, + { + "label": "Nombre de questions à afficher:", + "description": "Génère aléatoire un jeu de question parmi toutes les questions disponibles." }, { "label": "Quiz terminé", diff --git a/language/it.json b/language/it.json index 0b96fec..0d6d818 100644 --- a/language/it.json +++ b/language/it.json @@ -107,6 +107,14 @@ "label": "Disable backwards navigation", "description": "This option will only allow you to move forward in Question Set" }, + { + "label": "Randomize questions", + "description": "Enable to randomize the order of questions on display." + }, + { + "label": "Number of questions to be shown:", + "description": "Create a randomized batch of questions from the total." + }, { "label": "Quiz terminato", "fields": [ diff --git a/language/nb.json b/language/nb.json index 2a07368..4cca386 100644 --- a/language/nb.json +++ b/language/nb.json @@ -47,14 +47,17 @@ { "label": "Spørsmål", "entity": "spørsmål", + "widgets": [ + { + "label": "Standard" + }, + { + "label": "Tekstlig" + } + ], "field": { - "label": "Spørsmål", - "fields": [ - { - "label": "Spørsmålstype", - "description": "Velg spørsmålstype for dette spørsmålet" - } - ] + "label": "Spørsmåltype", + "description": "Velg spørsmålstype for dette spørsmålet" } }, { @@ -109,6 +112,14 @@ "label": "Slå av bakoverknapp", "description": "Slå på for å nekte å gå tilbake i Question Set" }, + { + "label": "Randomize questions", + "description": "Enable to randomize the order of questions on display." + }, + { + "label": "Number of questions to be shown:", + "description": "Create a randomized batch of questions from the total." + }, { "label": "Spørsmålssett avslutning", "fields": [ diff --git a/language/nn.json b/language/nn.json index f6e5844..08ad5e9 100644 --- a/language/nn.json +++ b/language/nn.json @@ -47,14 +47,17 @@ { "label": "Spørsmål", "entity": "spørsmål", + "widgets": [ + { + "label": "Standard" + }, + { + "label": "Tekstlig" + } + ], "field": { - "label": "Spørsmål", - "fields": [ - { - "label": "Spørsmålstype", - "description": "Velg spørsmålstype for dette spørsmålet" - } - ] + "label": "Spørsmålstype", + "description": "Velg spørsmålstype for dette spørsmålet" } }, { @@ -109,6 +112,14 @@ "label": "Slå av bakoverknapp", "description": "Slå på for å nekte å gå tilbake i Question Set" }, + { + "label": "Randomize questions", + "description": "Enable to randomize the order of questions on display." + }, + { + "label": "Number of questions to be shown:", + "description": "Create a randomized batch of questions from the total." + }, { "label": "Spørsmålssett avslutning", "fields": [ diff --git a/library.json b/library.json index 232a3be..3fb82a0 100644 --- a/library.json +++ b/library.json @@ -3,15 +3,15 @@ "description": "Put together a set of different questions that has to be solved. (Quiz)", "contentType": "question", "majorVersion": 1, - "minorVersion": 9, - "patchVersion": 1, + "minorVersion": 13, + "patchVersion": 0, "embedTypes": [ "iframe" ], "runnable": 1, "fullscreen": 0, "machineName": "H5P.QuestionSet", - "author": "Joubel AS", + "author": "Joubel", "coreApi": { "majorVersion": 1, "minorVersion": 6 @@ -53,12 +53,12 @@ { "machineName": "H5PEditor.VerticalTabs", "majorVersion": 1, - "minorVersion": 2 + "minorVersion": 3 }, { "machineName": "H5PEditor.QuestionSetTextualEditor", "majorVersion": 1, - "minorVersion": 1 + "minorVersion": 2 } ] -} +} \ No newline at end of file diff --git a/semantics.json b/semantics.json index a3ebb19..7268972 100644 --- a/semantics.json +++ b/semantics.json @@ -3,16 +3,19 @@ "name": "introPage", "type": "group", "label": "Quiz introduction", + "importance": "medium", "fields": [ { "name": "showIntroPage", "type": "boolean", - "label": "Display introduction" + "label": "Display introduction", + "importance": "low" }, { "name": "title", "type": "text", "label": "Title", + "importance": "high", "optional": true, "description": "This title will be displayed above the introduction text.", "tags": [ @@ -27,6 +30,7 @@ "type": "text", "widget": "html", "label": "Introduction text", + "importance": "medium", "optional": true, "description": "This text will be displayed before the quiz starts.", "enterMode": "p", @@ -42,6 +46,7 @@ "name": "startButtonText", "type": "text", "label": "Start button text", + "importance": "low", "optional": true, "default": "Start Quiz" }, @@ -49,6 +54,7 @@ "name": "backgroundImage", "type": "image", "label": "Background image", + "importance": "low", "optional": true, "description": "An optional background image for the introduction." } @@ -58,6 +64,7 @@ "name": "backgroundImage", "type": "image", "label": "Background image", + "importance": "low", "optional": true, "description": "An optional background image for the Question set." }, @@ -65,6 +72,7 @@ "name": "progressType", "type": "select", "label": "Progress indicator", + "importance": "low", "description": "Question set progress indicator style.", "options": [ { @@ -82,6 +90,7 @@ "name": "passPercentage", "type": "number", "label": "Pass percentage", + "importance": "low", "description": "Percentage of Total score required for passing the quiz.", "min": 0, "max": 100, @@ -91,6 +100,7 @@ { "name": "questions", "label": "Questions", + "importance": "high", "type": "list", "widgets": [ { @@ -108,13 +118,15 @@ "name": "question", "type": "library", "label": "Question type", + "importance": "high", "description": "Library for this question.", "options": [ - "H5P.MultiChoice 1.8", - "H5P.DragQuestion 1.7", + "H5P.MultiChoice 1.9", + "H5P.DragQuestion 1.10", "H5P.Blanks 1.7", "H5P.MarkTheWords 1.6", - "H5P.DragText 1.5" + "H5P.DragText 1.5", + "H5P.TrueFalse 1.1" ] } }, @@ -122,30 +134,35 @@ "name": "texts", "type": "group", "label": "Interface texts in quiz", + "importance": "low", "common": true, "fields": [ { "name": "prevButton", "type": "text", "label": "Back button", + "importance": "low", "default": "Previous question" }, { "name": "nextButton", "type": "text", "label": "Next button", + "importance": "low", "default": "Next question" }, { "name": "finishButton", "type": "text", "label": "Finish button", + "importance": "low", "default": "Finish" }, { "name": "textualProgress", "type": "text", "label": "Progress text", + "importance": "low", "description": "Text used if textual progress is selected.", "default": "Question: @current of @total questions", "tags": [ @@ -157,6 +174,7 @@ "name": "jumpToQuestion", "type": "text", "label": "Label for jumping to a certain question", + "importance": "low", "description": "You must use the placeholder '%d' instead of the question number, and %total instead of total amount of questions.", "default": "Question %d of %total" }, @@ -164,12 +182,14 @@ "name": "questionLabel", "type": "text", "label": "Copyright dialog question label", + "importance": "low", "default": "Question" }, { "name": "readSpeakerProgress", "type": "text", "label": "Readspeaker progress", + "importance": "low", "description": "May use @current and @total question variables", "default": "Question @current of @total" }, @@ -177,18 +197,21 @@ "name": "unansweredText", "type": "text", "label": "Unanswered question text", + "importance": "low", "default": "Unanswered" }, { "name": "answeredText", "type": "text", "label": "Answered question text", + "importance": "low", "default": "Answered" }, { "name": "currentQuestionText", "type": "text", "label": "Current question text", + "importance": "low", "default": "Current question" } ] @@ -197,19 +220,39 @@ "name": "disableBackwardsNavigation", "type": "boolean", "label": "Disable backwards navigation", + "importance": "low", "description": "This option will only allow you to move forward in Question Set", "optional": true, "default": false }, + { + "name": "randomQuestions", + "type": "boolean", + "label": "Randomize questions", + "importance": "low", + "description": "Enable to randomize the order of questions on display.", + "default": false + }, + { + "name": "poolSize", + "type": "number", + "min": 1, + "label": "Number of questions to be shown:", + "importance": "low", + "description": "Create a randomized batch of questions from the total.", + "optional": true + }, { "name": "endGame", "type": "group", "label": "Quiz finished", + "importance": "medium", "fields": [ { "name": "showResultPage", "type": "boolean", "label": "Display results", + "importance": "low", "default": true }, { @@ -222,6 +265,7 @@ "name": "noResultMessage", "type": "text", "label": "No results message", + "importance": "low", "description": "Text displayed on end page when \"Display results\" is disabled", "default": "Finished", "optional": true @@ -230,6 +274,7 @@ "name": "message", "type": "text", "label": "Feedback heading", + "importance": "low", "default": "Your result:", "optional": true, "description": "This heading will be displayed at the end of the quiz when the user has answered all questions.", @@ -242,6 +287,7 @@ "name": "scoreString", "type": "text", "label": "Score display text", + "importance": "low", "description": "Text used to display Total user score. \"@score\" will be replaced by calculated score, \"@total\" will be replaced by maximum possible score. ", "default": "You got @score of @total points", "optional": true @@ -250,6 +296,7 @@ "name": "successGreeting", "type": "text", "label": "Quiz passed greeting", + "importance": "low", "placeholder": "Congratulations!", "default": "Congratulations!", "optional": true, @@ -264,6 +311,7 @@ "type": "text", "widget": "html", "label": "Passed comment", + "importance": "low", "default": "You did very well!", "optional": true, "description": "This comment will be displayed after the score if the user has successfully passed the quiz.", @@ -280,6 +328,7 @@ "name": "failGreeting", "type": "text", "label": "Quiz failed title", + "importance": "low", "default": "You did not pass this time.", "optional": true, "description": "This text will be displayed above the score if the user has failed the quiz.", @@ -293,6 +342,7 @@ "type": "text", "widget": "html", "label": "Failed comment", + "importance": "low", "default": "Have another try!", "optional": true, "description": "This comment will be displayed after the score if the user has failed the quiz.", @@ -309,6 +359,7 @@ "name": "solutionButtonText", "type": "text", "label": "Solution button label", + "importance": "low", "default": "Show solution", "description": "Text for the solution button." }, @@ -316,6 +367,7 @@ "name": "retryButtonText", "type": "text", "label": "Retry button label", + "importance": "low", "default": "Retry", "description": "Text for the retry button." }, @@ -323,28 +375,33 @@ "name": "finishButtonText", "type": "text", "label": "Finish button text", + "importance": "low", "default": "Finish" }, { "name": "showAnimations", "type": "boolean", - "label": "Display video before quiz results" + "label": "Display video before quiz results", + "importance": "low" }, { "name": "skippable", "type": "boolean", - "label": "Enable skip video button" + "label": "Enable skip video button", + "importance": "low" }, { "name": "skipButtonText", "type": "text", "label": "Skip video button label", + "importance": "low", "default": "Skip video" }, { "name": "successVideo", "type": "video", "label": "Passed video", + "importance": "low", "optional": true, "description": "This video will be played if the user successfully passed the quiz." }, @@ -352,6 +409,7 @@ "name": "failVideo", "type": "video", "label": "Fail video", + "importance": "low", "optional": true, "description": "This video will be played if the user failes the quiz." } @@ -361,12 +419,14 @@ "name": "override", "type": "group", "label": "Settings for \"Show solution\" and \"Retry\" buttons", + "importance": "low", "optional": true, "fields": [ { "name": "showSolutionButton", "type": "select", "label": "Override \"Show Solution\" button", + "importance": "low", "description": "This option determines if the \"Show Solution\" button will be shown for all questions, disabled for all or configured for each question individually.", "optional": true, "options": [ @@ -384,6 +444,7 @@ "name": "retryButton", "type": "select", "label": "Override \"Retry\" button", + "importance": "low", "description": "This option determines if the \"Retry\" button will be shown for all questions, disabled for all or configured for each question individually.", "optional": true, "options": [ @@ -399,4 +460,4 @@ } ] } -] +] \ No newline at end of file