HFP-29 Merge with master

pull/4/merge
Timothy Lim 2017-05-10 10:23:49 +02:00
commit 9cf00d897d
17 changed files with 797 additions and 239 deletions

3
.gitignore vendored
View File

@ -1,2 +1,3 @@
*~
/nbproject/private/
/nbproject/private/
.idea

View File

@ -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.

View File

@ -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;

Binary file not shown.

View File

@ -1,14 +0,0 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Generated by IcoMoon</metadata>
<defs>
<font id="icomoon" horiz-adv-x="1024">
<font-face units-per-em="1024" ascent="960" descent="-64" />
<missing-glyph horiz-adv-x="1024" />
<glyph unicode="&#x20;" d="" horiz-adv-x="512" />
<glyph unicode="&#xe604;" d="M512-64c282.77 0 512 229.23 512 512s-229.23 512-512 512-512-229.23-512-512 229.23-512 512-512zM512 864c229.75 0 416-186.25 416-416s-186.25-416-416-416-416 186.25-416 416 186.25 416 416 416zM256 640c0 35.346 28.654 64 64 64s64-28.654 64-64c0-35.346-28.654-64-64-64-35.346 0-64 28.654-64 64zM640 640c0 35.346 28.654 64 64 64s64-28.654 64-64c0-35.346-28.654-64-64-64-35.346 0-64 28.654-64 64zM704.098 332.74l82.328-49.396c-55.962-93.070-157.916-155.344-274.426-155.344s-218.464 62.274-274.426 155.344l82.328 49.396c39.174-65.148 110.542-108.74 192.098-108.74s152.924 43.592 192.098 108.74z" />
<glyph unicode="&#xe605;" d="M512-64c282.77 0 512 229.23 512 512s-229.23 512-512 512-512-229.23-512-512 229.23-512 512-512zM512 864c229.75 0 416-186.25 416-416s-186.25-416-416-416-416 186.25-416 416 186.25 416 416 416zM745.74 358.38l22.488-76.776-437.008-128.002-22.488 76.776zM256 640c0 35.346 28.654 64 64 64s64-28.654 64-64c0-35.346-28.654-64-64-64-35.346 0-64 28.654-64 64zM640 640c0 35.346 28.654 64 64 64s64-28.654 64-64c0-35.346-28.654-64-64-64-35.346 0-64 28.654-64 64z" />
<glyph unicode="&#xe606;" d="M512-64c282.77 0 512 229.23 512 512s-229.23 512-512 512-512-229.23-512-512 229.23-512 512-512zM512 864c229.75 0 416-186.25 416-416s-186.25-416-416-416-416 186.25-416 416 186.25 416 416 416zM192 448v-64c0-140.8 115.2-256 256-256h128c140.8 0 256 115.2 256 256v64h-640zM384 203.012c-26.538 9.458-50.924 24.822-71.544 45.446-36.406 36.402-56.456 84.54-56.456 135.542h128v-180.988zM576 192h-128v192h128v-192zM711.544 248.458c-20.624-20.624-45.010-35.988-71.544-45.446v180.988h128c0-51.002-20.048-99.14-56.456-135.542zM225.352 576c0.002 0 0 0 0 0 9.768 0 18.108 7.056 19.724 16.69 6.158 36.684 37.668 63.31 74.924 63.31s68.766-26.626 74.924-63.31c1.616-9.632 9.956-16.69 19.722-16.69 9.768 0 18.108 7.056 19.724 16.688 1.082 6.436 1.628 12.934 1.628 19.312 0 63.962-52.038 116-116 116s-116-52.038-116-116c0-6.378 0.548-12.876 1.628-19.312 1.62-9.632 9.96-16.688 19.726-16.688zM609.352 576c0.002 0 0 0 0 0 9.77 0 18.112 7.056 19.724 16.69 6.158 36.684 37.668 63.31 74.924 63.31s68.766-26.626 74.924-63.31c1.616-9.632 9.958-16.69 19.722-16.69s18.108 7.056 19.722 16.688c1.082 6.436 1.628 12.934 1.628 19.312 0 63.962-52.038 116-116 116s-116-52.038-116-116c0-6.378 0.544-12.876 1.626-19.312 1.624-9.632 9.964-16.688 19.73-16.688z" />
<glyph unicode="&#xe607;" d="M512-64c282.77 0 512 229.23 512 512s-229.23 512-512 512-512-229.23-512-512 229.23-512 512-512zM512 864c229.75 0 416-186.25 416-416s-186.25-416-416-416-416 186.25-416 416 186.25 416 416 416zM384 256c0 70.692 57.308 128 128 128s128-57.308 128-128c0-70.692-57.308-128-128-128-70.692 0-128 57.308-128 128zM640 608c0 53.019 28.654 96 64 96s64-42.981 64-96c0-53.019-28.654-96-64-96-35.346 0-64 42.981-64 96zM256 608c0 53.019 28.654 96 64 96s64-42.981 64-96c0-53.019-28.654-96-64-96-35.346 0-64 42.981-64 96z" />
</font></defs></svg>

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Binary file not shown.

105
icon.svg Normal file
View File

@ -0,0 +1,105 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 225">
<defs>
<style>
.cls-1 {
isolation: isolate;
}
.cls-2 {
fill: #56b5ef;
}
.cls-3 {
opacity: 0.15;
mix-blend-mode: multiply;
}
.cls-4 {
fill: #fff;
}
.cls-5 {
opacity: 0.2;
}
.cls-6 {
fill: #231f20;
}
.cls-7 {
fill: #ddd;
}
.cls-8 {
fill: #8db7e2;
}
.cls-9 {
fill: none;
}
</style>
</defs>
<title>question-set</title>
<g class="cls-1">
<g id="Layer_2" data-name="Layer 2">
<g id="question_set_copy" data-name="question set copy">
<g>
<circle class="cls-2" cx="204" cy="113.5" r="85"/>
<g>
<g>
<g class="cls-3">
<rect x="168" y="82.5" width="93" height="85"/>
</g>
<rect class="cls-4" x="165" y="78.5" width="92" height="86"/>
</g>
<g>
<g class="cls-3">
<rect x="162" y="75.5" width="92" height="85"/>
</g>
<rect class="cls-4" x="157" y="70.5" width="93" height="87"/>
</g>
<g>
<g class="cls-3">
<rect x="153" y="67.5" width="92" height="85"/>
</g>
<rect class="cls-4" x="151" y="63.5" width="91" height="86"/>
</g>
<g>
<g class="cls-5">
<rect class="cls-6" x="157" y="95.88" width="82.24" height="12.27" rx="5.44" ry="5.44"/>
<rect class="cls-6" x="161.84" y="99.51" width="69.11" height="4.84"/>
</g>
<g>
<rect class="cls-7" x="157" y="94.15" width="82.24" height="12.27" rx="5.44" ry="5.44"/>
<rect class="cls-8" x="161.84" y="97.78" width="69.11" height="4.84"/>
</g>
</g>
<g>
<g class="cls-5">
<rect class="cls-6" x="157" y="113.85" width="82.24" height="12.27" rx="5.44" ry="5.44"/>
<rect class="cls-6" x="161.84" y="117.65" width="57.02" height="4.84"/>
</g>
<g>
<rect class="cls-7" x="157" y="112.12" width="82.24" height="12.27" rx="5.44" ry="5.44"/>
<rect class="cls-8" x="161.84" y="115.92" width="57.02" height="4.84"/>
</g>
</g>
<g>
<g class="cls-5">
<rect class="cls-6" x="157" y="131.99" width="82.24" height="12.27" rx="5.44" ry="5.44"/>
<rect class="cls-6" x="161.84" y="135.79" width="64.27" height="4.84"/>
</g>
<g>
<rect class="cls-7" x="157" y="130.26" width="82.24" height="12.27" rx="5.44" ry="5.44"/>
<rect class="cls-8" x="161.84" y="134.06" width="64.27" height="4.84"/>
</g>
</g>
<circle class="cls-8" cx="197.91" cy="79.04" r="10.98"/>
<path class="cls-4" d="M196,82.5V81.07a2.91,2.91,0,0,1,.61-1.36A5.25,5.25,0,0,1,198,78.49a6,6,0,0,0,1.2-1,1.47,1.47,0,0,0,.31-.85.91.91,0,0,0-.37-.8,1.83,1.83,0,0,0-1.08-.28,6.24,6.24,0,0,0-2.78.8l-.88-1.78a7.75,7.75,0,0,1,3.85-1,4.09,4.09,0,0,1,2.66.8,2.63,2.63,0,0,1,1,2.15,2.85,2.85,0,0,1-.41,1.54,5.79,5.79,0,0,1-1.54,1.46,15,15,0,0,0-1.34.88c-.14.2-.56.46-.56.79V82.5Zm.3,2a1.36,1.36,0,0,1,.37-1,1.48,1.48,0,0,1,1.06-.35,1.44,1.44,0,0,1,1,.36,1.58,1.58,0,0,1,0,2,1.42,1.42,0,0,1-1,.37,1.46,1.46,0,0,1-1.06-.36A1.35,1.35,0,0,1,196.3,84.45Z"/>
</g>
</g>
<rect class="cls-9" width="400" height="225"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -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) {
' <% } %>' +
' <div class="qs-footer">' +
' <div class="qs-progress">' +
' <% if (progressType == "dots" && !disableBackwardsNavigation) { %>' +
' <% if (progressType == "dots") { %>' +
' <ul class="dots-container" role="navigation">' +
' <% for (var i=0; i<questions.length; i++) { %>' +
' <li class="progress-item"><a href="#" class="progress-dot unanswered" ' +
' aria-label="<%=' +
' texts.jumpToQuestion.replace("%d", i + 1).replace("%total", questions.length)' +
' + ", " + texts.unansweredText' +
' %>" tabindex="-1"></a></li>' +
' <li class="progress-item">' +
' <a href="#" ' +
' class="progress-dot unanswered<%' +
' if (disableBackwardsNavigation) { %> disabled <% } %>"' +
' aria-label="<%=' +
' texts.jumpToQuestion.replace("%d", i + 1).replace("%total", questions.length)' +
' + ", " + texts.unansweredText %>" tabindex="-1" ' +
' <% if (disableBackwardsNavigation) { %> aria-disabled="true" <% } %>' +
' ></a>' +
' </li>' +
' <% } %>' +
' </div>' +
' <% } else if (progressType == "textual") { %>' +
@ -81,7 +86,6 @@ H5P.QuestionSet = function (options, contentId, contentData) {
'</div>';
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.<array, array>} 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) {
$('<a class="h5p-joubelui-button h5p-button skip">' + params.endGame.skipButtonText + '</a>').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)
}
};
};

View File

@ -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": [

View File

@ -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"
}
]
}

View File

@ -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é",

View File

@ -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": [

View File

@ -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": [

View File

@ -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": [

View File

@ -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
}
]
}
}

View File

@ -3,16 +3,19 @@
"name": "introPage",