From 5f8e1a9f54accf7539510ceea81e928155b741f8 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Thu, 4 Apr 2013 15:18:08 +0200 Subject: [PATCH] Adding back the modules that used to be submodules --- css/img/mark-fail.png | Bin 0 -> 3520 bytes css/img/mark-pass.png | Bin 0 -> 3220 bytes css/questionset.css | 111 +++++++++++++ js/questionset.js | 303 ++++++++++++++++++++++++++++++++++ library.json | 24 +++ semantics.json | 221 +++++++++++++++++++++++++ views/questionset-results.ejs | 6 + views/questionset.ejs | 31 ++++ 8 files changed, 696 insertions(+) create mode 100644 css/img/mark-fail.png create mode 100644 css/img/mark-pass.png create mode 100644 css/questionset.css create mode 100644 js/questionset.js create mode 100644 library.json create mode 100644 semantics.json create mode 100644 views/questionset-results.ejs create mode 100644 views/questionset.ejs diff --git a/css/img/mark-fail.png b/css/img/mark-fail.png new file mode 100644 index 0000000000000000000000000000000000000000..cbd3936b1928c0ed73326d13e5253ed980926409 GIT binary patch literal 3520 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' + +'
' + +'
<%= introPage.title %>
' + +'
<%= introPage.introduction %>
' + +' ' + +'
' + +'<%} %>' + +'' + + ''; + var resulttemplate = '' + +'
' + +'
<%= greeting %>
' + +'
<%= score %>
' + +'
<%= resulttext %>
' + +' ' + +'
' + + ''; + + var that = this; + var defaults = { + title: "", + randomOrder: false, + initialQuestion: 0, + backgroundImage: "", + progressType: 'dots', + passPercentage: 50, + questions: [], + introPage: { + showIntroPage: true, + title: "Welcome", + introduction: "Click start to start.", + startButtonText: "Start" + }, + texts: { + prevButton: "Previous", + nextButton: "Next", + finishButton: "Finish", + textualProgress: "Question: @current of @total questions" + }, + endGame: { + showResultPage: true, + resultPage: { + successGreeting: "Congratulations!", + successComment: "You have enough correct answers to pass the test.", + failGreeting: "Sorry!", + failComment: "You don't have enough correct answers to pass this test.", + scoreString: "@score/@total", + finishButtonText: "Finish" + }, + animations: { + showAnimations: false, + successVideo: undefined, + failVideo: undefined + } + } + }; + + var template = new EJS({text: texttemplate}); + var endTemplate = new EJS({text: resulttemplate}); + var params = $.extend({}, defaults, options); + + var currentQuestion = 0; + var questionInstances = new Array(); + var allQuestionsAnswered = false; + var $myDom; + + if (params.randomOrder) { + // TODO: Randomize order of questions + console.log("TODO: Randomize order of questions"); + } + + // Instantiate question instances + for (var i=0; i= 0; i--) { + answered = answered && (questionInstances[i]).getAnswerGiven(); + } + + if (currentQuestion === 0) { + $('.prev.button', $myDom).hide(); + } else { + $('.prev.button', $myDom).show(); + } + if (currentQuestion == (params.questions.length - 1)) { + $('.next.button', $myDom).hide(); + if (answered) { + $('.finish.button', $myDom).show(); + } + } else { + $('.next.button', $myDom).show(); + $('.finish.button', $myDom).hide(); + } + + }; + + var _showQuestion = function (questionNumber) { + // Sanitize input. + if (questionNumber < 0) { questionNumber = 0; } + if (questionNumber >= params.questions.length) { questionNumber = params.questions.length - 1; } + + // Hide all questions + $('.question-container', $myDom).hide(); + + // Reshow the requested question + $('#q-' + questionNumber, $myDom).show(); + + // Update progress indicator + // Test if current has been answered. + if (params.progressType == 'textual') { + $('.progress-text', $myDom).text(params.texts.textualProgress.replace("@current", questionNumber+1).replace("@total", params.questions.length)); + } else { + // Set currentNess + $('.progress-dot.current', $myDom).removeClass('current'); + $('#qdot-' + questionNumber, $myDom).addClass('current'); + } + + // Remember where we are + currentQuestion = questionNumber; + _updateButtons(); + return currentQuestion; + }; + + var _displayEndGame = function () { + // Get total score. + var finals = getScore(); + var totals = totalScore(); + var scoreString = params.endGame.resultPage.scoreString.replace("@score", finals).replace("@total", totals); + var success = ((100 * finals / totals) >= params.passPercentage); + var eventData = { + score: scoreString, + passed: success + }; + var displayResults = function () { + if (!params.endGame.showResultPage) { + $(returnObject).trigger('h5pQuestionSetFinished', eventData); + return; + } + + var eparams = { + greeting: (success ? params.endGame.resultPage.succesGreeting : params.endGame.resultPage.failGreeting), + score: scoreString, + scoreclass: (success ? 'success' : 'fail'), + resulttext: (success ? params.endGame.resultPage.successComment : params.endGame.resultPage.failComment), + finishButtonText: params.endGame.resultPage.finishButtonText + }; + + // Show result page. + $myDom.children().hide(); + $myDom.append(endTemplate.render(eparams)); + $('.qs-finishbutton').click(function (ev) { + $(returnObject).trigger('h5pQuestionSetFinished', eventData); + }); + }; + + if (params.endGame.animations.showAnimations) { + var videoData = success ? params.endGame.animations.successVideo : params.endGame.animations.failVideo; + if (videoData) { + H5P.playVideo($myDom, videoData, params.endGame.animations.skipButtonText, cp, function () { + displayResults(); + }); + return; + } + } + // Trigger finished event. + displayResults(); + }; + + // Function for attaching the multichoice to a DOM element. + var attach = function (target) { + if (typeof(target) == "string") { + $myDom = $("#" + target); + } else { + $myDom = $(target); + } + + // Render own DOM into target. + $myDom.html(template.render(params)).css({ + backgroundImage: 'url(' + cp + params.backgroundImage.path + ')' + }); + + // Attach questions + for (var i=0; i= 0; i--) { + score += questionInstances[i].getScore(); + } + return score; + }; + + // Get total score possible for questionset. + var totalScore = function () { + var score = 0; + for (var i = questionInstances.length - 1; i >= 0; i--) { + score += questionInstances[i].totalScore(); + } + return score; + }; + + // Masquerade the main object to hide inner properties and functions. + var returnObject = { + attach: attach, // Attach to DOM object + getQuestions: function () {return questionInstances;}, + getScore: getScore, + totalScore: totalScore, + defaults: defaults // Provide defaults for inspection + }; + return returnObject; +}; diff --git a/library.json b/library.json new file mode 100644 index 0000000..cd31dac --- /dev/null +++ b/library.json @@ -0,0 +1,24 @@ +{ + "title": "Question set", + "contentType": "question", + "majorVersion": 1, + "minorVersion": 0, + "patchVersion": 6, + "runnable": 1, + "machineName": "H5P.QuestionSet", + "author": "Amendor AS", + "license": "cc-by-sa", + "preloadedJs": [ + {"path": "js/questionset.js"} + ], + "preloadedCss": [ + {"path": "css/questionset.css"} + ], + "preloadedDependencies": [ + { + "machineName": "EmbeddedJS", + "majorVersion": 1, + "minorVersion": 0 + } + ] +} diff --git a/semantics.json b/semantics.json new file mode 100644 index 0000000..d1ef7e8 --- /dev/null +++ b/semantics.json @@ -0,0 +1,221 @@ +[ + { + "name": "title", + "type": "text", + "label": "Title", + "description": "Question set title (optional)." + }, + { + "name": "randomOrder", + "type": "boolean", + "label": "Randomize order", + "description": "Whether questions should be shown in random order." + }, + { + "name": "initialQuestion", + "type": "number", + "label": "Initial question", + "description": "Which question to start with. Count from 0." + }, + { + "name": "backgroundImage", + "type": "image", + "label": "Background image", + "description": "An optional background image for the Question set." + }, + { + "name": "progressType", + "type": "select", + "label": "Progress indicator", + "description": "Question set progress indicator style.", + "values": [ + { + "text": "Textual", + "value": "textual" + }, + { + "text": "Dots", + "value": "dots" + } + ], + "default": "textual" + }, + { + "name": "passPercentage", + "type": "number", + "label": "Pass percentage", + "description": "Percentage of total score required for passing the quiz.", + "min": 0, + "max": 100, + "step": 1, + "default": 50 + }, + { + "name": "questions", + "type": "list", + "entity": "question", + "field": { + "name": "question", + "type": "group", + "label": "Question", + "fields": [ + { + "name": "library", + "type": "library", + "label": "Question library", + "description": "Library for this question.", + "options": [ + "H5P.MultiChoice 1.0" + ] + } + ] + } + }, + { + "name": "introPage", + "type": "group", + "label": "Intro page", + "description": "Data for the intro page.", + "fields": [ + { + "name": "showIntroPage", + "type": "boolean", + "label": "Show intro page?" + }, + { + "name": "title", + "type": "text", + "label": "Title" + }, + { + "name": "introduction", + "type": "text", + "label": "Introduction text" + }, + { + "name": "startButtonText", + "type": "text", + "label": "Start button text" + } + ] + }, + { + "name": "texts", + "type": "group", + "label": "Interface texts in quiz", + "common": true, + "fields": [ + { + "name": "prevButton", + "type": "text", + "label": "Back button", + "default": "Previous" + }, + { + "name": "nextButton", + "type": "text", + "label": "Next button", + "default": "Next" + }, + { + "name": "finishButton", + "type": "text", + "label": "Finish button", + "default": "Finish" + }, + { + "name": "textualProgress", + "type": "text", + "label": "Progress text", + "description": "Text used if textual progress is selected.", + "default": "Question: @current of @total questions" + } + ] + }, + { + "name": "endGame", + "type": "group", + "label": "End game data", + "fields": [ + { + "name": "showResultPage", + "type": "boolean", + "label": "Show result page", + "default": true + }, + { + "name": "resultPage", + "type": "group", + "label": "Result page", + "description": "Data and texts for the result page.", + "fields": [ + { + "name": "successGreeting", + "type": "text", + "label": "Success greeting", + "description": "Title in result page on success." + }, + { + "name": "successComment", + "type": "text", + "label": "Success comment", + "description": "Comment shown after the score." + }, + { + "name": "failGreeting", + "type": "text", + "label": "Failed greeting", + "description": "Title in result page on failed quiz." + }, + { + "name": "failComment", + "type": "text", + "label": "Failed comment", + "description": "Comment shown after the score on failed quiz." + }, + { + "name": "finishButtonText", + "type": "text", + "label": "Finish button text", + "description": "Text for the finish button." + } + ] + }, + { + "name": "animations", + "type": "group", + "label": "Animations", + "fields": [ + { + "name": "showAnimations", + "type": "boolean", + "label": "Show animations" + }, + { + "name": "skippable", + "type": "boolean", + "label": "Skippable" + }, + { + "name": "skipButtonText", + "type": "text", + "label": "Skip button text", + "common": true + }, + { + "name": "successVideo", + "type": "video", + "label": "Success video", + "description": "Video displayed on successful quiz." + }, + { + "name": "failVideo", + "type": "video", + "label": "Fail video", + "description": "Video displayed on failed quiz." + } + ] + } + ] + } +] diff --git a/views/questionset-results.ejs b/views/questionset-results.ejs new file mode 100644 index 0000000..722776d --- /dev/null +++ b/views/questionset-results.ejs @@ -0,0 +1,6 @@ +
+
<%= greeting %>
+
<%= score %>
+
<%= resulttext %>
+ +
diff --git a/views/questionset.ejs b/views/questionset.ejs new file mode 100644 index 0000000..5219d42 --- /dev/null +++ b/views/questionset.ejs @@ -0,0 +1,31 @@ +<% if (introPage.showIntroPage) { %> +
+
<%= introPage.title %>
+
<%= introPage.introduction %>
+ +
+<%} %> +