diff --git a/js/questionset.js b/js/questionset.js index 9503447..97ef90b 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. @@ -126,6 +126,7 @@ 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 = []; @@ -145,14 +146,12 @@ H5P.QuestionSet = function (options, contentId, contentData) { questionOrder = contentData.previousState.order; } - // Create a pool of questions if necessary /** * Randomizes questions in an array and updates an array containing their order * @param {array} questions - * @param {array} questionOrder * @return {Object.} questionOrdering */ - var randomizeQuestionOrdering = function (questions, questionOrder) { + 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] }); @@ -171,7 +170,7 @@ H5P.QuestionSet = function (options, contentId, contentData) { for (var i = 0; i< questionOrdering.length; i++) { // Use a previous order if it exists - if(questionOrder) { + if(contentData.previousState && contentData.previousState.questionOrder) { newOrder[i] = questionOrder[questionOrdering[i][1]]; } else { @@ -179,15 +178,15 @@ H5P.QuestionSet = function (options, contentId, contentData) { } } - // Return the questions in their new order *with* their new order + // 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 && params.poolSize < params.questions.length) { + if (params.poolSize > 0) { // If a previous pool exists, recreate it if(contentData.previousState && contentData.previousState.poolOrder) { @@ -204,7 +203,7 @@ H5P.QuestionSet = function (options, contentId, contentData) { } else { // Otherwise create a new pool // Randomize and get the results - var poolResult = randomizeQuestionOrdering(params.questions, poolOrder); + var poolResult = randomizeQuestionOrdering(params.questions); var poolQuestions = poolResult.questions; poolOrder = poolResult.questionOrder; @@ -238,44 +237,59 @@ H5P.QuestionSet = function (options, contentId, contentData) { } } - // Instantiate question instances - for (var i = 0; i < params.questions.length; 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++) { - var question; - // If a previous order exists, use it - if (questionOrder !== undefined) { - question = params.questions[questionOrder[i]]; - } - else { - // Use a generic order when initialzing for the first time - question = params.questions[i]; - } + 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); - } + 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 + 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'); }); - questionInstance.on('resize', function () { - up = true; - self.trigger('resize'); - }); - questionInstances.push(questionInstance); + result.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,questionOrder); + var result = randomizeQuestionOrdering(questionInstances); questionInstances = result.questions; questionOrder = result.questionOrder; } @@ -440,7 +454,12 @@ H5P.QuestionSet = function (options, contentId, contentData) { * @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(); @@ -477,7 +496,28 @@ H5P.QuestionSet = function (options, contentId, contentData) { //Force the last page to be reRendered rendered = false; - if (params.randomQuestions) { + 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(); } @@ -494,10 +534,21 @@ H5P.QuestionSet = function (options, contentId, contentData) { */ var randomizeQuestions = function () { - var result = randomizeQuestionOrdering(questionInstances,questionOrder); + 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(); @@ -516,18 +567,18 @@ H5P.QuestionSet = function (options, contentId, contentData) { //Show buttons if necessary if(questionInstances[questionInstances.length -1] === question && question.hasButton('finish')) { - question.showButton('finish'); + question.showButton('finish'); } if(questionInstances[questionInstances.length -1] !== question && question.hasButton('next')) { - question.showButton('next'); + question.showButton('next'); } if(questionInstances[0] !== question && question.hasButton('prev') && !params.disableBackwardsNavigation) { - question.showButton('prev'); + question.showButton('prev'); } // Hide relevant buttons since the order has changed @@ -542,10 +593,8 @@ H5P.QuestionSet = function (options, contentId, contentData) { if (questionInstances[questionInstances.length-1] !== question) { question.hideButton('finish'); } - } - - } + }; var moveQuestion = function (direction) { if (params.disableBackwardsNavigation && !questionInstances[currentQuestion].getAnswerGiven()) { @@ -762,7 +811,77 @@ 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(); @@ -797,66 +916,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 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()); - } + initializeQuestion(); // Allow other libraries to add transitions after the questions have been inited $('.questionset', $myDom).addClass('started'); diff --git a/library.json b/library.json index 1191c1b..7085667 100644 --- a/library.json +++ b/library.json @@ -4,7 +4,7 @@ "contentType": "question", "majorVersion": 1, "minorVersion": 10, - "patchVersion": 0, + "patchVersion": 1, "embedTypes": [ "iframe" ],