From 0f34307537bbd5a9dbee8583a55590d6ea9e44d2 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 13 May 2016 12:50:35 +0200 Subject: [PATCH 1/4] MOBILE-1577 quiz: Handle all kind of essay questions --- www/addons/qtype/essay/directive.js | 19 +++++++------------ www/addons/qtype/essay/template.html | 9 ++++++--- www/core/components/question/lang/en.json | 1 + www/core/components/question/scss/styles.scss | 5 +++++ .../components/question/services/helper.js | 5 ++++- www/core/scss/styles.scss | 4 ++++ 6 files changed, 27 insertions(+), 16 deletions(-) diff --git a/www/addons/qtype/essay/directive.js b/www/addons/qtype/essay/directive.js index d8a9464ae96..03bb7db159f 100644 --- a/www/addons/qtype/essay/directive.js +++ b/www/addons/qtype/essay/directive.js @@ -30,26 +30,21 @@ angular.module('mm.addons.qtype_essay') templateUrl: 'addons/qtype/essay/template.html', link: function(scope) { var questionEl = $mmQuestionHelper.directiveInit(scope, $log), - question = scope.question, textarea; if (questionEl) { questionEl = questionEl[0] || questionEl; // Convert from jqLite to plain JS if needed. // First search the textarea. - textarea = questionEl.querySelector('textarea[id*=answer_id]'); + textarea = questionEl.querySelector('textarea[name*=_answer]'); + scope.allowsAttachments = !!questionEl.querySelector('div[id*=filemanager]'); + scope.isMonospaced = !!questionEl.querySelector('.qtype_essay_monospaced'); if (!textarea) { - // Textarea not found, we're probably in review. Search the answer and the attachments. - if (questionEl.querySelector('.qtype_essay_response')) { - scope.answer = $mmUtil.getContentsOfElement(angular.element(questionEl), '.qtype_essay_response'); - scope.attachments = $mmQuestionHelper.getQuestionAttachmentsFromHtml( - $mmUtil.getContentsOfElement(angular.element(questionEl), '.attachments')); - } else { - // Answer not found. Abort. - $log.warn('Aborting because couldn\'t find textarea or answer.', question.name); - return $mmQuestionHelper.showDirectiveError(scope); - } + // Textarea not found, we might be in review. Search the answer and the attachments. + scope.answer = $mmUtil.getContentsOfElement(angular.element(questionEl), '.qtype_essay_response'); + scope.attachments = $mmQuestionHelper.getQuestionAttachmentsFromHtml( + $mmUtil.getContentsOfElement(angular.element(questionEl), '.attachments')); } else { // Textarea found. var input = questionEl.querySelector('input[type="hidden"][name*=answerformat]'), diff --git a/www/addons/qtype/essay/template.html b/www/addons/qtype/essay/template.html index 831d65efb1f..127d34a9a68 100644 --- a/www/addons/qtype/essay/template.html +++ b/www/addons/qtype/essay/template.html @@ -5,10 +5,13 @@ -
-

{{answer}}

+
+

{{ 'mm.question.errorattachmentsnotsupported' | translate }}

+
+
+

{{answer}}

diff --git a/www/core/components/question/lang/en.json b/www/core/components/question/lang/en.json index 33c3418e7f2..472a32c78b5 100644 --- a/www/core/components/question/lang/en.json +++ b/www/core/components/question/lang/en.json @@ -4,6 +4,7 @@ "certainty": "Certainty", "complete": "Complete", "correct": "Correct", + "errorattachmentsnotsupported": "The application doesn't support attaching files to answers yet.", "errorquestionnotsupported": "This type of question isn't supported by the app in your site: {{$a}}.", "feedback": "Feedback", "howtodraganddrop": "Tap to select then tap to drop.", diff --git a/www/core/components/question/scss/styles.scss b/www/core/components/question/scss/styles.scss index 81efa0a2f2d..6828cf8b249 100644 --- a/www/core/components/question/scss/styles.scss +++ b/www/core/components/question/scss/styles.scss @@ -4,6 +4,7 @@ $mm-question-incorrect-color: #b94a48 !default; $mm-question-state-correct-color: #cfc !default; $mm-question-state-partial-color: #ffa !default; $mm-question-state-incorrect-color: #fcc !default; +$mm-question-warning-color: #c00 !default; .mm-question-textarea { width: 100%; @@ -97,3 +98,7 @@ li.mm-question-answer-incorrect, background-color: $mm-question-state-incorrect-color; } } + +.mm-question-warning, p.mm-question-warning, .item .mm-question-warning { + color: $mm-question-warning-color; +} diff --git a/www/core/components/question/services/helper.js b/www/core/components/question/services/helper.js index 4f12fec1bf1..51733fc8750 100644 --- a/www/core/components/question/services/helper.js +++ b/www/core/components/question/services/helper.js @@ -411,7 +411,7 @@ angular.module('mm.core.question') * @ngdoc method * @name $mmQuestionHelper#getQuestionAttachmentsFromHtml * @param {String} html HTML code to search in. - * @return {[type]} [description] + * @return {Object[]} Attachments. */ self.getQuestionAttachmentsFromHtml = function(html) { var el = angular.element('
'), @@ -422,6 +422,9 @@ angular.module('mm.core.question') el.html(html); el = el[0]; + // Remove the filemanager (area to attach files to a question). + $mmUtil.removeElement(el, 'div[id*=filemanager]'); + // Search the anchors. anchors = el.querySelectorAll('a'); angular.forEach(anchors, function(anchor) { diff --git a/www/core/scss/styles.scss b/www/core/scss/styles.scss index fbc2bf00e96..8567282310e 100644 --- a/www/core/scss/styles.scss +++ b/www/core/scss/styles.scss @@ -389,6 +389,10 @@ mm-timer { } } +.mm-monospaced { + font-family: Andale Mono,Monaco,Courier New,DejaVu Sans Mono,monospace; +} + /** * This CSS uses adjacent selectors instead of general sibling (~) selectors * for ion radio that are broken in iOS 9 UIWebView. From 52b0d5d657e9f7708a8da841b52f05c6b5c75d1d Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 13 May 2016 16:21:24 +0200 Subject: [PATCH 2/4] MOBILE-1577 quiz: Fix scroll to question from summary --- www/addons/mod_quiz/templates/player.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/addons/mod_quiz/templates/player.html b/www/addons/mod_quiz/templates/player.html index 155e9ac37e8..10535a40e6b 100644 --- a/www/addons/mod_quiz/templates/player.html +++ b/www/addons/mod_quiz/templates/player.html @@ -54,7 +54,7 @@

{{ 'mma.mod_quiz.summaryofattempt' | translate }}

{{ question.number }}

{{ question.status }}

- +

From be865e615a388099d7104eb90f58153565c2ca52 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 13 May 2016 17:06:46 +0200 Subject: [PATCH 3/4] MOBILE-1577 quiz: Improve the offline question status detection --- www/addons/qtype/calculated/handlers.js | 23 +++++++------ www/addons/qtype/calculatedmulti/handlers.js | 17 ++++++---- www/addons/qtype/calculatedsimple/handlers.js | 23 +++++++------ www/addons/qtype/ddimageortext/handlers.js | 29 +++++++++------- www/addons/qtype/ddmarker/handlers.js | 21 +++++++----- www/addons/qtype/ddwtos/handlers.js | 29 +++++++++------- www/addons/qtype/essay/handlers.js | 28 +++++++++------ www/addons/qtype/gapselect/handlers.js | 17 ++++++---- www/addons/qtype/match/handlers.js | 17 ++++++---- www/addons/qtype/multianswer/handlers.js | 34 +++++++++++-------- www/addons/qtype/multichoice/handlers.js | 27 ++++++++------- www/addons/qtype/numerical/handlers.js | 19 ++++++----- www/addons/qtype/randomsamatch/handlers.js | 23 +++++++------ www/addons/qtype/shortanswer/handlers.js | 19 ++++++----- www/addons/qtype/truefalse/handlers.js | 19 ++++++----- .../components/question/services/delegate.js | 27 +++++++++------ .../components/question/services/helper.js | 31 +++++++++++++++++ .../components/question/services/question.js | 5 ++- 18 files changed, 250 insertions(+), 158 deletions(-) diff --git a/www/addons/qtype/calculated/handlers.js b/www/addons/qtype/calculated/handlers.js index 611a394d9ec..39dff7bbbdd 100644 --- a/www/addons/qtype/calculated/handlers.js +++ b/www/addons/qtype/calculated/handlers.js @@ -28,12 +28,13 @@ angular.module('mm.addons.qtype_calculated') /** * Check if a response is complete. * - * @param {Object} answers Question answers (without prefix). - * @return {Mixed} True if complete, false if not complete, -1 if cannot determine. + * @param {Object} question Question. + * @param {Object} answers Question answers (without prefix). + * @return {Mixed} True if complete, false if not complete, -1 if cannot determine. */ - self.isCompleteResponse = function(answers) { + self.isCompleteResponse = function(question, answers) { // This question type depends on numerical. - return $mmaQtypeNumericalHandler.isCompleteResponse(answers); + return $mmaQtypeNumericalHandler.isCompleteResponse(question, answers); }; /** @@ -49,24 +50,26 @@ angular.module('mm.addons.qtype_calculated') * Check if a student has provided enough of an answer for the question to be graded automatically, * or whether it must be considered aborted. * - * @param {Object} answers Question answers (without prefix). - * @return {Mixed} True if gradable, false if not gradable, -1 if cannot determine. + * @param {Object} question Question. + * @param {Object} answers Question answers (without prefix). + * @return {Mixed} True if gradable, false if not gradable, -1 if cannot determine. */ - self.isGradableResponse = function(answers) { + self.isGradableResponse = function(question, answers) { // This question type depends on numerical. - return $mmaQtypeNumericalHandler.isGradableResponse(answers); + return $mmaQtypeNumericalHandler.isGradableResponse(question, answers); }; /** * Check if two responses are the same. * + * @param {Object} question Question. * @param {Object} prevAnswers Previous answers. * @param {Object} newAnswers New answers. * @return {Boolean} True if same, false otherwise. */ - self.isSameResponse = function(prevAnswers, newAnswers) { + self.isSameResponse = function(question, prevAnswers, newAnswers) { // This question type depends on numerical. - return $mmaQtypeNumericalHandler.isSameResponse(prevAnswers, newAnswers); + return $mmaQtypeNumericalHandler.isSameResponse(question, prevAnswers, newAnswers); }; /** diff --git a/www/addons/qtype/calculatedmulti/handlers.js b/www/addons/qtype/calculatedmulti/handlers.js index 86186cf9c9d..207a2048a4c 100644 --- a/www/addons/qtype/calculatedmulti/handlers.js +++ b/www/addons/qtype/calculatedmulti/handlers.js @@ -28,10 +28,11 @@ angular.module('mm.addons.qtype_calculatedmulti') /** * Check if a response is complete. * - * @param {Object} answers Question answers (without prefix). - * @return {Mixed} True if complete, false if not complete, -1 if cannot determine. + * @param {Object} question Question. + * @param {Object} answers Question answers (without prefix). + * @return {Mixed} True if complete, false if not complete, -1 if cannot determine. */ - self.isCompleteResponse = function(answers) { + self.isCompleteResponse = function(question, answers) { // This question type depends on multichoice. return $mmaQtypeMultichoiceHandler.isCompleteResponseSingle(answers); }; @@ -49,10 +50,11 @@ angular.module('mm.addons.qtype_calculatedmulti') * Check if a student has provided enough of an answer for the question to be graded automatically, * or whether it must be considered aborted. * - * @param {Object} answers Question answers (without prefix). - * @return {Mixed} True if gradable, false if not gradable, -1 if cannot determine. + * @param {Object} question Question. + * @param {Object} answers Question answers (without prefix). + * @return {Mixed} True if gradable, false if not gradable, -1 if cannot determine. */ - self.isGradableResponse = function(answers) { + self.isGradableResponse = function(question, answers) { // This question type depends on multichoice. return $mmaQtypeMultichoiceHandler.isGradableResponseSingle(answers); }; @@ -60,11 +62,12 @@ angular.module('mm.addons.qtype_calculatedmulti') /** * Check if two responses are the same. * + * @param {Object} question Question. * @param {Object} prevAnswers Previous answers. * @param {Object} newAnswers New answers. * @return {Boolean} True if same, false otherwise. */ - self.isSameResponse = function(prevAnswers, newAnswers) { + self.isSameResponse = function(question, prevAnswers, newAnswers) { // This question type depends on multichoice. return $mmaQtypeMultichoiceHandler.isSameResponseSingle(prevAnswers, newAnswers); }; diff --git a/www/addons/qtype/calculatedsimple/handlers.js b/www/addons/qtype/calculatedsimple/handlers.js index 3dcbe88cd45..e8481e3821c 100644 --- a/www/addons/qtype/calculatedsimple/handlers.js +++ b/www/addons/qtype/calculatedsimple/handlers.js @@ -28,12 +28,13 @@ angular.module('mm.addons.qtype_calculatedsimple') /** * Check if a response is complete. * - * @param {Object} answers Question answers (without prefix). - * @return {Mixed} True if complete, false if not complete, -1 if cannot determine. + * @param {Object} question Question. + * @param {Object} answers Question answers (without prefix). + * @return {Mixed} True if complete, false if not complete, -1 if cannot determine. */ - self.isCompleteResponse = function(answers) { + self.isCompleteResponse = function(question, answers) { // This question type depends on calculated. - return $mmaQtypeCalculatedHandler.isCompleteResponse(answers); + return $mmaQtypeCalculatedHandler.isCompleteResponse(question, answers); }; /** @@ -49,24 +50,26 @@ angular.module('mm.addons.qtype_calculatedsimple') * Check if a student has provided enough of an answer for the question to be graded automatically, * or whether it must be considered aborted. * - * @param {Object} answers Question answers (without prefix). - * @return {Mixed} True if gradable, false if not gradable, -1 if cannot determine. + * @param {Object} question Question. + * @param {Object} answers Question answers (without prefix). + * @return {Mixed} True if gradable, false if not gradable, -1 if cannot determine. */ - self.isGradableResponse = function(answers) { + self.isGradableResponse = function(question, answers) { // This question type depends on calculated. - return $mmaQtypeCalculatedHandler.isGradableResponse(answers); + return $mmaQtypeCalculatedHandler.isGradableResponse(question, answers); }; /** * Check if two responses are the same. * + * @param {Object} question Question. * @param {Object} prevAnswers Previous answers. * @param {Object} newAnswers New answers. * @return {Boolean} True if same, false otherwise. */ - self.isSameResponse = function(prevAnswers, newAnswers) { + self.isSameResponse = function(question, prevAnswers, newAnswers) { // This question type depends on calculated. - return $mmaQtypeCalculatedHandler.isSameResponse(prevAnswers, newAnswers); + return $mmaQtypeCalculatedHandler.isSameResponse(question, prevAnswers, newAnswers); }; /** diff --git a/www/addons/qtype/ddimageortext/handlers.js b/www/addons/qtype/ddimageortext/handlers.js index 665f0f016da..641f70bccd8 100644 --- a/www/addons/qtype/ddimageortext/handlers.js +++ b/www/addons/qtype/ddimageortext/handlers.js @@ -28,19 +28,20 @@ angular.module('mm.addons.qtype_ddimageortext') /** * Check if a response is complete. * - * @param {Object} answers Question answers (without prefix). - * @return {Mixed} True if complete, false if not complete, -1 if cannot determine. + * @param {Object} question Question. + * @param {Object} answers Question answers (without prefix). + * @return {Mixed} True if complete, false if not complete, -1 if cannot determine. */ - self.isCompleteResponse = function(answers) { - // An answer is complete if all drop zones have an answer. Since we don't have the list of drop zones - // we cannot determine if the answer is complete, only if it's unanswered. - var hasReponse = false; + self.isCompleteResponse = function(question, answers) { + // An answer is complete if all drop zones have an answer. + // We should always receive all the drop zones with their value ('' if not answered). + var isComplete = true; angular.forEach(answers, function(value) { - if (value && value !== '0') { - hasReponse = true; + if (!value || value === '0') { + isComplete = false; } }); - return hasReponse ? -1 : false; + return isComplete; }; /** @@ -56,10 +57,11 @@ angular.module('mm.addons.qtype_ddimageortext') * Check if a student has provided enough of an answer for the question to be graded automatically, * or whether it must be considered aborted. * - * @param {Object} answers Question answers (without prefix). - * @return {Mixed} True if gradable, false if not gradable, -1 if cannot determine. + * @param {Object} question Question. + * @param {Object} answers Question answers (without prefix). + * @return {Mixed} True if gradable, false if not gradable, -1 if cannot determine. */ - self.isGradableResponse = function(answers) { + self.isGradableResponse = function(question, answers) { var hasReponse = false; angular.forEach(answers, function(value) { if (value && value !== '0') { @@ -72,11 +74,12 @@ angular.module('mm.addons.qtype_ddimageortext') /** * Check if two responses are the same. * + * @param {Object} question Question. * @param {Object} prevAnswers Previous answers. * @param {Object} newAnswers New answers. * @return {Boolean} True if same, false otherwise. */ - self.isSameResponse = function(prevAnswers, newAnswers) { + self.isSameResponse = function(question, prevAnswers, newAnswers) { return $mmQuestion.compareAllAnswers(prevAnswers, newAnswers); }; diff --git a/www/addons/qtype/ddmarker/handlers.js b/www/addons/qtype/ddmarker/handlers.js index 6ace8679f6c..d0cd84c0c45 100644 --- a/www/addons/qtype/ddmarker/handlers.js +++ b/www/addons/qtype/ddmarker/handlers.js @@ -28,11 +28,12 @@ angular.module('mm.addons.qtype_ddmarker') /** * Check if a response is complete. * - * @param {Object} answers Question answers (without prefix). - * @return {Mixed} True if complete, false if not complete, -1 if cannot determine. + * @param {Object} question Question. + * @param {Object} answers Question answers (without prefix). + * @return {Mixed} True if complete, false if not complete, -1 if cannot determine. */ - self.isCompleteResponse = function(answers) { - // We should always get a value for each dragitem so we can assume we receive all the possible answers. + self.isCompleteResponse = function(question, answers) { + // If 1 dragitem is set we assume the answer is complete (like Moodle does). var hasReponse = false; angular.forEach(answers, function(value) { if (value) { @@ -55,21 +56,23 @@ angular.module('mm.addons.qtype_ddmarker') * Check if a student has provided enough of an answer for the question to be graded automatically, * or whether it must be considered aborted. * - * @param {Object} answers Question answers (without prefix). - * @return {Mixed} True if gradable, false if not gradable, -1 if cannot determine. + * @param {Object} question Question. + * @param {Object} answers Question answers (without prefix). + * @return {Mixed} True if gradable, false if not gradable, -1 if cannot determine. */ - self.isGradableResponse = function(answers) { - return self.isCompleteResponse(answers); + self.isGradableResponse = function(question, answers) { + return self.isCompleteResponse(question, answers); }; /** * Check if two responses are the same. * + * @param {Object} question Question. * @param {Object} prevAnswers Previous answers. * @param {Object} newAnswers New answers. * @return {Boolean} True if same, false otherwise. */ - self.isSameResponse = function(prevAnswers, newAnswers) { + self.isSameResponse = function(question, prevAnswers, newAnswers) { return $mmQuestion.compareAllAnswers(prevAnswers, newAnswers); }; diff --git a/www/addons/qtype/ddwtos/handlers.js b/www/addons/qtype/ddwtos/handlers.js index 8808560d556..6d81a4ba0ff 100644 --- a/www/addons/qtype/ddwtos/handlers.js +++ b/www/addons/qtype/ddwtos/handlers.js @@ -28,19 +28,20 @@ angular.module('mm.addons.qtype_ddwtos') /** * Check if a response is complete. * - * @param {Object} answers Question answers (without prefix). - * @return {Mixed} True if complete, false if not complete, -1 if cannot determine. + * @param {Object} question Question. + * @param {Object} answers Question answers (without prefix). + * @return {Mixed} True if complete, false if not complete, -1 if cannot determine. */ - self.isCompleteResponse = function(answers) { - // An answer is complete if all drop zones have an answer. Since we don't have the list of drop zones - // we cannot determine if the answer is complete, only if it's unanswered. - var hasReponse = false; + self.isCompleteResponse = function(question, answers) { + // An answer is complete if all drop zones have an answer. + // We should always receive all the drop zones with their value ('0' if not answered). + var isComplete = true; angular.forEach(answers, function(value) { - if (value && value !== '0') { - hasReponse = true; + if (!value || value === '0') { + isComplete = false; } }); - return hasReponse ? -1 : false; + return isComplete; }; /** @@ -56,10 +57,11 @@ angular.module('mm.addons.qtype_ddwtos') * Check if a student has provided enough of an answer for the question to be graded automatically, * or whether it must be considered aborted. * - * @param {Object} answers Question answers (without prefix). - * @return {Mixed} True if gradable, false if not gradable, -1 if cannot determine. + * @param {Object} question Question. + * @param {Object} answers Question answers (without prefix). + * @return {Mixed} True if gradable, false if not gradable, -1 if cannot determine. */ - self.isGradableResponse = function(answers) { + self.isGradableResponse = function(question, answers) { var hasReponse = false; angular.forEach(answers, function(value) { if (value && value !== '0') { @@ -72,11 +74,12 @@ angular.module('mm.addons.qtype_ddwtos') /** * Check if two responses are the same. * + * @param {Object} question Question. * @param {Object} prevAnswers Previous answers. * @param {Object} newAnswers New answers. * @return {Boolean} True if same, false otherwise. */ - self.isSameResponse = function(prevAnswers, newAnswers) { + self.isSameResponse = function(question, prevAnswers, newAnswers) { return $mmQuestion.compareAllAnswers(prevAnswers, newAnswers); }; diff --git a/www/addons/qtype/essay/handlers.js b/www/addons/qtype/essay/handlers.js index d450f88ca9c..051833a1ebd 100644 --- a/www/addons/qtype/essay/handlers.js +++ b/www/addons/qtype/essay/handlers.js @@ -39,14 +39,20 @@ angular.module('mm.addons.qtype_essay') /** * Check if a response is complete. * - * @param {Object} answers Question answers (without prefix). - * @return {Mixed} True if complete, false if not complete, -1 if cannot determine. + * @param {Object} question Question. + * @param {Object} answers Question answers (without prefix). + * @return {Mixed} True if complete, false if not complete, -1 if cannot determine. */ - self.isCompleteResponse = function(answers) { - if (answers['answer'] && answers['answer'] !== '') { - return true; + self.isCompleteResponse = function(question, answers) { + var hasInlineText = answers['answer'] && answers['answer'] !== '', + questionEl = angular.element(question.html)[0], + allowsAttachments = !!questionEl.querySelector('div[id*=filemanager]'); + + if (!allowsAttachments) { + return hasInlineText; } - // Since we can't know if the text is required or not we return -1. + + // We can't know if the attachments are required or if the user added any in web. return -1; }; @@ -63,21 +69,23 @@ angular.module('mm.addons.qtype_essay') * Check if a student has provided enough of an answer for the question to be graded automatically, * or whether it must be considered aborted. * - * @param {Object} answers Question answers (without prefix). - * @return {Mixed} True if gradable, false if not gradable, -1 if cannot determine. + * @param {Object} question Question. + * @param {Object} answers Question answers (without prefix). + * @return {Mixed} True if gradable, false if not gradable, -1 if cannot determine. */ - self.isGradableResponse = function(answers) { + self.isGradableResponse = function(question, answers) { return false; }; /** * Check if two responses are the same. * + * @param {Object} question Question. * @param {Object} prevAnswers Previous answers. * @param {Object} newAnswers New answers. * @return {Boolean} True if same, false otherwise. */ - self.isSameResponse = function(prevAnswers, newAnswers) { + self.isSameResponse = function(question, prevAnswers, newAnswers) { // For now we don't support attachments so we only compare the answer. return $mmUtil.sameAtKeyMissingIsBlank(prevAnswers, newAnswers, 'answer'); }; diff --git a/www/addons/qtype/gapselect/handlers.js b/www/addons/qtype/gapselect/handlers.js index bd45bbfb68d..2c6c550252d 100644 --- a/www/addons/qtype/gapselect/handlers.js +++ b/www/addons/qtype/gapselect/handlers.js @@ -28,10 +28,11 @@ angular.module('mm.addons.qtype_gapselect') /** * Check if a response is complete. * - * @param {Object} answers Question answers (without prefix). - * @return {Mixed} True if complete, false if not complete, -1 if cannot determine. + * @param {Object} question Question. + * @param {Object} answers Question answers (without prefix). + * @return {Mixed} True if complete, false if not complete, -1 if cannot determine. */ - self.isCompleteResponse = function(answers) { + self.isCompleteResponse = function(question, answers) { // We should always get a value for each select so we can assume we receive all the possible answers. var isComplete = true; angular.forEach(answers, function(value) { @@ -55,10 +56,11 @@ angular.module('mm.addons.qtype_gapselect') * Check if a student has provided enough of an answer for the question to be graded automatically, * or whether it must be considered aborted. * - * @param {Object} answers Question answers (without prefix). - * @return {Mixed} True if gradable, false if not gradable, -1 if cannot determine. + * @param {Object} question Question. + * @param {Object} answers Question answers (without prefix). + * @return {Mixed} True if gradable, false if not gradable, -1 if cannot determine. */ - self.isGradableResponse = function(answers) { + self.isGradableResponse = function(question, answers) { var hasReponse = false; angular.forEach(answers, function(value) { if (value) { @@ -71,11 +73,12 @@ angular.module('mm.addons.qtype_gapselect') /** * Check if two responses are the same. * + * @param {Object} question Question. * @param {Object} prevAnswers Previous answers. * @param {Object} newAnswers New answers. * @return {Boolean} True if same, false otherwise. */ - self.isSameResponse = function(prevAnswers, newAnswers) { + self.isSameResponse = function(question, prevAnswers, newAnswers) { return $mmQuestion.compareAllAnswers(prevAnswers, newAnswers); }; diff --git a/www/addons/qtype/match/handlers.js b/www/addons/qtype/match/handlers.js index abf2263752e..03b12307d76 100644 --- a/www/addons/qtype/match/handlers.js +++ b/www/addons/qtype/match/handlers.js @@ -28,10 +28,11 @@ angular.module('mm.addons.qtype_match') /** * Check if a response is complete. * - * @param {Object} answers Question answers (without prefix). - * @return {Mixed} True if complete, false if not complete, -1 if cannot determine. + * @param {Object} question Question. + * @param {Object} answers Question answers (without prefix). + * @return {Mixed} True if complete, false if not complete, -1 if cannot determine. */ - self.isCompleteResponse = function(answers) { + self.isCompleteResponse = function(question, answers) { // We should always get a value for each select so we can assume we receive all the possible answers. var isComplete = true; angular.forEach(answers, function(value) { @@ -55,10 +56,11 @@ angular.module('mm.addons.qtype_match') * Check if a student has provided enough of an answer for the question to be graded automatically, * or whether it must be considered aborted. * - * @param {Object} answers Question answers (without prefix). - * @return {Mixed} True if gradable, false if not gradable, -1 if cannot determine. + * @param {Object} question Question. + * @param {Object} answers Question answers (without prefix). + * @return {Mixed} True if gradable, false if not gradable, -1 if cannot determine. */ - self.isGradableResponse = function(answers) { + self.isGradableResponse = function(question, answers) { // We should always get a value for each select so we can assume we receive all the possible answers. var isGradable = false; angular.forEach(answers, function(value) { @@ -72,11 +74,12 @@ angular.module('mm.addons.qtype_match') /** * Check if two responses are the same. * + * @param {Object} question Question. * @param {Object} prevAnswers Previous answers. * @param {Object} newAnswers New answers. * @return {Boolean} True if same, false otherwise. */ - self.isSameResponse = function(prevAnswers, newAnswers) { + self.isSameResponse = function(question, prevAnswers, newAnswers) { return $mmQuestion.compareAllAnswers(prevAnswers, newAnswers); }; diff --git a/www/addons/qtype/multianswer/handlers.js b/www/addons/qtype/multianswer/handlers.js index 6e924d46a5a..42fbd378f53 100644 --- a/www/addons/qtype/multianswer/handlers.js +++ b/www/addons/qtype/multianswer/handlers.js @@ -21,26 +21,28 @@ angular.module('mm.addons.qtype_multianswer') * @ngdoc service * @name $mmaQtypeMultianswerHandler */ -.factory('$mmaQtypeMultianswerHandler', function($mmQuestion) { +.factory('$mmaQtypeMultianswerHandler', function($mmQuestion, $mmQuestionHelper) { var self = {}; /** * Check if a response is complete. * - * @param {Object} answers Question answers (without prefix). - * @return {Mixed} True if complete, false if not complete, -1 if cannot determine. + * @param {Object} question Question. + * @param {Object} answers Question answers (without prefix). + * @return {Mixed} True if complete, false if not complete, -1 if cannot determine. */ - self.isCompleteResponse = function(answers) { - var hasReponse = false; - angular.forEach(answers, function(value) { - if (value || value === false) { - hasReponse = true; + self.isCompleteResponse = function(question, answers) { + // Get all the inputs in the question to check if they've all been answered. + var names = $mmQuestion.getBasicAnswers($mmQuestionHelper.getAllInputNamesFromHtml(question.html)); + + for (var name in names) { + if (!answers[name] && answers[name] !== false && answers[name] !== 0) { + return false; } - }); + } - // We don't have the full list of subquestions, so we can't be sure they all have been answered. - return hasReponse ? -1 : false; + return true; }; /** @@ -56,10 +58,11 @@ angular.module('mm.addons.qtype_multianswer') * Check if a student has provided enough of an answer for the question to be graded automatically, * or whether it must be considered aborted. * - * @param {Object} answers Question answers (without prefix). - * @return {Mixed} True if gradable, false if not gradable, -1 if cannot determine. + * @param {Object} question Question. + * @param {Object} answers Question answers (without prefix). + * @return {Mixed} True if gradable, false if not gradable, -1 if cannot determine. */ - self.isGradableResponse = function(answers) { + self.isGradableResponse = function(question, answers) { var hasReponse = false; angular.forEach(answers, function(value) { if (value || value === false) { @@ -72,11 +75,12 @@ angular.module('mm.addons.qtype_multianswer') /** * Check if two responses are the same. * + * @param {Object} question Question. * @param {Object} prevAnswers Previous answers. * @param {Object} newAnswers New answers. * @return {Boolean} True if same, false otherwise. */ - self.isSameResponse = function(prevAnswers, newAnswers) { + self.isSameResponse = function(question, prevAnswers, newAnswers) { return $mmQuestion.compareAllAnswers(prevAnswers, newAnswers); }; diff --git a/www/addons/qtype/multichoice/handlers.js b/www/addons/qtype/multichoice/handlers.js index 4314d92f601..066c79751b1 100644 --- a/www/addons/qtype/multichoice/handlers.js +++ b/www/addons/qtype/multichoice/handlers.js @@ -28,10 +28,11 @@ angular.module('mm.addons.qtype_multichoice') /** * Check if a response is complete. * - * @param {Object} answers Question answers (without prefix). - * @return {Mixed} True if complete, false if not complete, -1 if cannot determine. + * @param {Object} question Question. + * @param {Object} answers Question answers (without prefix). + * @return {Mixed} True if complete, false if not complete, -1 if cannot determine. */ - self.isCompleteResponse = function(answers) { + self.isCompleteResponse = function(question, answers) { var isSingle = true, isMultiComplete = false; @@ -57,8 +58,8 @@ angular.module('mm.addons.qtype_multichoice') /** * Check if a response is complete. Only for single answer. * - * @param {Object} answers Question answers (without prefix). - * @return {Mixed} True if complete, false if not complete, -1 if cannot determine. + * @param {Object} answers Question answers (without prefix). + * @return {Mixed} True if complete, false if not complete, -1 if cannot determine. */ self.isCompleteResponseSingle = function(answers) { return answers['answer'] && answers['answer'] !== ''; @@ -77,19 +78,20 @@ angular.module('mm.addons.qtype_multichoice') * Check if a student has provided enough of an answer for the question to be graded automatically, * or whether it must be considered aborted. * - * @param {Object} answers Question answers (without prefix). - * @return {Mixed} True if gradable, false if not gradable, -1 if cannot determine. + * @param {Object} question Question. + * @param {Object} answers Question answers (without prefix). + * @return {Mixed} True if gradable, false if not gradable, -1 if cannot determine. */ - self.isGradableResponse = function(answers) { - return self.isCompleteResponse(answers); + self.isGradableResponse = function(question, answers) { + return self.isCompleteResponse(question, answers); }; /** * Check if a student has provided enough of an answer for the question to be graded automatically, * or whether it must be considered aborted. Only for single answer. * - * @param {Object} answers Question answers (without prefix). - * @return {Mixed} True if gradable, false if not gradable, -1 if cannot determine. + * @param {Object} answers Question answers (without prefix). + * @return {Mixed} True if gradable, false if not gradable, -1 if cannot determine. */ self.isGradableResponseSingle = function(answers) { return self.isCompleteResponseSingle(answers); @@ -98,11 +100,12 @@ angular.module('mm.addons.qtype_multichoice') /** * Check if two responses are the same. * + * @param {Object} question Question. * @param {Object} prevAnswers Previous answers. * @param {Object} newAnswers New answers. * @return {Boolean} True if same, false otherwise. */ - self.isSameResponse = function(prevAnswers, newAnswers) { + self.isSameResponse = function(question, prevAnswers, newAnswers) { var isSingle = true, isMultiSame = true; diff --git a/www/addons/qtype/numerical/handlers.js b/www/addons/qtype/numerical/handlers.js index aeda7b92673..2a5f50358bd 100644 --- a/www/addons/qtype/numerical/handlers.js +++ b/www/addons/qtype/numerical/handlers.js @@ -28,11 +28,12 @@ angular.module('mm.addons.qtype_numerical') /** * Check if a response is complete. * - * @param {Object} answers Question answers (without prefix). - * @return {Mixed} True if complete, false if not complete, -1 if cannot determine. + * @param {Object} question Question. + * @param {Object} answers Question answers (without prefix). + * @return {Mixed} True if complete, false if not complete, -1 if cannot determine. */ - self.isCompleteResponse = function(answers) { - if (!self.isGradableResponse(answers) || !self.validateUnits(answers['answer'])) { + self.isCompleteResponse = function(question, answers) { + if (!self.isGradableResponse(question, answers) || !self.validateUnits(answers['answer'])) { return false; } @@ -52,21 +53,23 @@ angular.module('mm.addons.qtype_numerical') * Check if a student has provided enough of an answer for the question to be graded automatically, * or whether it must be considered aborted. * - * @param {Object} answers Question answers (without prefix). - * @return {Mixed} True if gradable, false if not gradable, -1 if cannot determine. + * @param {Object} question Question. + * @param {Object} answers Question answers (without prefix). + * @return {Mixed} True if gradable, false if not gradable, -1 if cannot determine. */ - self.isGradableResponse = function(answers) { + self.isGradableResponse = function(question, answers) { return answers['answer'] || answers['answer'] === '0' || answers['answer'] === 0; }; /** * Check if two responses are the same. * + * @param {Object} question Question. * @param {Object} prevAnswers Previous answers. * @param {Object} newAnswers New answers. * @return {Boolean} True if same, false otherwise. */ - self.isSameResponse = function(prevAnswers, newAnswers) { + self.isSameResponse = function(question, prevAnswers, newAnswers) { return $mmUtil.sameAtKeyMissingIsBlank(prevAnswers, newAnswers, 'answer'); }; diff --git a/www/addons/qtype/randomsamatch/handlers.js b/www/addons/qtype/randomsamatch/handlers.js index 73f1fcb5898..47dada28862 100644 --- a/www/addons/qtype/randomsamatch/handlers.js +++ b/www/addons/qtype/randomsamatch/handlers.js @@ -28,12 +28,13 @@ angular.module('mm.addons.qtype_randomsamatch') /** * Check if a response is complete. * - * @param {Object} answers Question answers (without prefix). - * @return {Mixed} True if complete, false if not complete, -1 if cannot determine. + * @param {Object} question Question. + * @param {Object} answers Question answers (without prefix). + * @return {Mixed} True if complete, false if not complete, -1 if cannot determine. */ - self.isCompleteResponse = function(answers) { + self.isCompleteResponse = function(question, answers) { // This question type depends on match. - return $mmaQtypeMatchHandler.isCompleteResponse(answers); + return $mmaQtypeMatchHandler.isCompleteResponse(question, answers); }; /** @@ -49,24 +50,26 @@ angular.module('mm.addons.qtype_randomsamatch') * Check if a student has provided enough of an answer for the question to be graded automatically, * or whether it must be considered aborted. * - * @param {Object} answers Question answers (without prefix). - * @return {Mixed} True if gradable, false if not gradable, -1 if cannot determine. + * @param {Object} question Question. + * @param {Object} answers Question answers (without prefix). + * @return {Mixed} True if gradable, false if not gradable, -1 if cannot determine. */ - self.isGradableResponse = function(answers) { + self.isGradableResponse = function(question, answers) { // This question type depends on match. - return $mmaQtypeMatchHandler.isGradableResponse(answers); + return $mmaQtypeMatchHandler.isGradableResponse(question, answers); }; /** * Check if two responses are the same. * + * @param {Object} question Question. * @param {Object} prevAnswers Previous answers. * @param {Object} newAnswers New answers. * @return {Boolean} True if same, false otherwise. */ - self.isSameResponse = function(prevAnswers, newAnswers) { + self.isSameResponse = function(question, prevAnswers, newAnswers) { // This question type depends on match. - return $mmaQtypeMatchHandler.isSameResponse(prevAnswers, newAnswers); + return $mmaQtypeMatchHandler.isSameResponse(question, prevAnswers, newAnswers); }; /** diff --git a/www/addons/qtype/shortanswer/handlers.js b/www/addons/qtype/shortanswer/handlers.js index 22d48dec107..af5f966dd16 100644 --- a/www/addons/qtype/shortanswer/handlers.js +++ b/www/addons/qtype/shortanswer/handlers.js @@ -28,10 +28,11 @@ angular.module('mm.addons.qtype_shortanswer') /** * Check if a response is complete. * - * @param {Object} answers Question answers (without prefix). - * @return {Mixed} True if complete, false if not complete, -1 if cannot determine. + * @param {Object} question Question. + * @param {Object} answers Question answers (without prefix). + * @return {Mixed} True if complete, false if not complete, -1 if cannot determine. */ - self.isCompleteResponse = function(answers) { + self.isCompleteResponse = function(question, answers) { return answers['answer'] || answers['answer'] === 0; }; @@ -48,21 +49,23 @@ angular.module('mm.addons.qtype_shortanswer') * Check if a student has provided enough of an answer for the question to be graded automatically, * or whether it must be considered aborted. * - * @param {Object} answers Question answers (without prefix). - * @return {Mixed} True if gradable, false if not gradable, -1 if cannot determine. + * @param {Object} question Question. + * @param {Object} answers Question answers (without prefix). + * @return {Mixed} True if gradable, false if not gradable, -1 if cannot determine. */ - self.isGradableResponse = function(answers) { - return self.isCompleteResponse(answers); + self.isGradableResponse = function(question, answers) { + return self.isCompleteResponse(question, answers); }; /** * Check if two responses are the same. * + * @param {Object} question Question. * @param {Object} prevAnswers Previous answers. * @param {Object} newAnswers New answers. * @return {Boolean} True if same, false otherwise. */ - self.isSameResponse = function(prevAnswers, newAnswers) { + self.isSameResponse = function(question, prevAnswers, newAnswers) { return $mmUtil.sameAtKeyMissingIsBlank(prevAnswers, newAnswers, 'answer'); }; diff --git a/www/addons/qtype/truefalse/handlers.js b/www/addons/qtype/truefalse/handlers.js index 82ae0f3eb42..d58bc4dc73d 100644 --- a/www/addons/qtype/truefalse/handlers.js +++ b/www/addons/qtype/truefalse/handlers.js @@ -28,10 +28,11 @@ angular.module('mm.addons.qtype_truefalse') /** * Check if a response is complete. * - * @param {Object} answers Question answers (without prefix). - * @return {Mixed} True if complete, false if not complete, -1 if cannot determine. + * @param {Object} question Question. + * @param {Object} answers Question answers (without prefix). + * @return {Mixed} True if complete, false if not complete, -1 if cannot determine. */ - self.isCompleteResponse = function(answers) { + self.isCompleteResponse = function(question, answers) { return !!answers['answer']; }; @@ -48,21 +49,23 @@ angular.module('mm.addons.qtype_truefalse') * Check if a student has provided enough of an answer for the question to be graded automatically, * or whether it must be considered aborted. * - * @param {Object} answers Question answers (without prefix). - * @return {Mixed} True if gradable, false if not gradable, -1 if cannot determine. + * @param {Object} question Question. + * @param {Object} answers Question answers (without prefix). + * @return {Mixed} True if gradable, false if not gradable, -1 if cannot determine. */ - self.isGradableResponse = function(answers) { - return self.isCompleteResponse(answers); + self.isGradableResponse = function(question, answers) { + return self.isCompleteResponse(question, answers); }; /** * Check if two responses are the same. * + * @param {Object} question Question. * @param {Object} prevAnswers Previous answers. * @param {Object} newAnswers New answers. * @return {Boolean} True if same, false otherwise. */ - self.isSameResponse = function(prevAnswers, newAnswers) { + self.isSameResponse = function(question, prevAnswers, newAnswers) { return $mmUtil.sameAtKeyMissingIsBlank(prevAnswers, newAnswers, 'answer'); }; diff --git a/www/core/components/question/services/delegate.js b/www/core/components/question/services/delegate.js index c854d4b3878..491e14bd6a7 100644 --- a/www/core/components/question/services/delegate.js +++ b/www/core/components/question/services/delegate.js @@ -53,16 +53,23 @@ angular.module('mm.core.question') * @param {String|Object|Function} handler Must be resolved to an object defining the following properties. Or to a function * returning an object defining these properties. See {@link $mmUtil#resolveObject}. * - isEnabled (Boolean|Promise) Whether or not the handler is enabled on a site level. - * When using a promise, it should return a boolean. + * When using a promise, it should return a boolean. * - getDirectiveName(question) (String) Returns the name of the directive to render the question. - * There's no need to check the question type in this function. + * There's no need to check the question type in this function. * - getBehaviour(question, behaviour) (String) Optional. Returns the name of the behaviour to use - * for the question. If the question should use the default behaviour - * you shouldn't implement this question or it should just return - * the behaviour param. + * for the question. If the question should use the default behaviour you + * shouldn't implement this function. * - validateSequenceCheck(question, offlineSeqCheck) (Boolean) Optional. Validate if an offline - * sequencecheck is valid compared with the online one. This function - * only needs to be implemented if a specific compare is required. + * sequencecheck is valid compared with the online one. This function only + * needs to be implemented if a specific compare is required. + * - isCompleteResponse(question, answers) (Mixed) Optional. Check if a response is complete. + * Return true if complete, false if not complete, -1 if cannot determine. + * - isGradableResponse(question, answers) (Mixed) Optional. Check if a student has provided enough + * of an answer for the question to be graded automatically, or whether it must + * be considered aborted. + * Return true if gradable, false if not gradable, -1 if cannot determine. + * - isSameResponse(question, prevAnswers, newAnswers) (Boolean) Optional. Check if two responses + * are equal. Always return boolean. */ self.registerHandler = function(name, questionType, handler) { if (typeof handlers[questionType] !== 'undefined') { @@ -136,7 +143,7 @@ angular.module('mm.core.question') var type = 'qtype_' + question.type; if (typeof enabledHandlers[type] != 'undefined') { if (enabledHandlers[type].isCompleteResponse) { - return enabledHandlers[type].isCompleteResponse(answers); + return enabledHandlers[type].isCompleteResponse(question, answers); } } return -1; @@ -156,7 +163,7 @@ angular.module('mm.core.question') var type = 'qtype_' + question.type; if (typeof enabledHandlers[type] != 'undefined') { if (enabledHandlers[type].isGradableResponse) { - return enabledHandlers[type].isGradableResponse(answers); + return enabledHandlers[type].isGradableResponse(question, answers); } } return -1; @@ -177,7 +184,7 @@ angular.module('mm.core.question') var type = 'qtype_' + question.type; if (typeof enabledHandlers[type] != 'undefined') { if (enabledHandlers[type].isSameResponse) { - return enabledHandlers[type].isSameResponse(prevAnswers, newAnswers); + return enabledHandlers[type].isSameResponse(question, prevAnswers, newAnswers); } } return false; diff --git a/www/core/components/question/services/helper.js b/www/core/components/question/services/helper.js index 51733fc8750..20167498568 100644 --- a/www/core/components/question/services/helper.js +++ b/www/core/components/question/services/helper.js @@ -363,6 +363,37 @@ angular.module('mm.core.question') } }; + /** + * Get the names of all the inputs inside an HTML code. + * This function will return an object where the keys are the input names. The values will always be true. + * This is in order to make this function compatible with other functions like $mmQuestion#getBasicAnswers. + * + * @module mm.core.question + * @ngdoc method + * @name $mmQuestionHelper#getAllInputNamesFromHtml + * @param {String} html HTML code. + * @return {Object} Object where the keys are the names. + */ + self.getAllInputNamesFromHtml = function(html) { + var form = document.createElement('form'), + answers = {}; + + form.innerHTML = html; + + // Search all input elements. + angular.forEach(form.elements, function(element) { + var name = element.name || ''; + // Ignore flag and submit inputs. + if (!name || name.match(/_:flagged$/) || element.type == 'submit' || element.tagName == 'BUTTON') { + return; + } + + answers[$mmQuestion.removeQuestionPrefix(name)] = true; + }); + + return answers; + }; + /** * Retrieve the answers entered in a form. * We don't use ng-model because it doesn't detect changes done by JavaScript and some questions might do that. diff --git a/www/core/components/question/services/question.js b/www/core/components/question/services/question.js index ebf2639736e..43fabc2d781 100644 --- a/www/core/components/question/services/question.js +++ b/www/core/components/question/services/question.js @@ -447,13 +447,16 @@ angular.module('mm.core.question') /** * Check if an answer is extra data like sequencecheck or certainty. * + * @module mm.core.question + * @ngdoc method + * @name $mmQuestion#isExtraAnswer * @param {String} name Answer name. * @return {Boolean} True if extra data, false otherwise. */ self.isExtraAnswer = function(name) { // Maybe the name still has the prefix. name = self.removeQuestionPrefix(name); - return name[0] == '-' || name == ':'; + return name[0] == '-' || name[0] == ':'; }; /** From 80d1aa642a85d6d729105dd0e24f06c3f8b5a4ce Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Tue, 17 May 2016 10:22:38 +0200 Subject: [PATCH 4/4] MOBILE-1577 quiz: Prevent submit quiz if essay has attachments --- www/addons/mod_quiz/controllers/player.js | 5 ++--- www/addons/mod_quiz/lang/en.json | 1 + www/addons/mod_quiz/services/quiz.js | 22 +++++++++++++++++++ www/addons/mod_quiz/templates/player.html | 7 +++++- www/addons/qtype/essay/handlers.js | 19 ++++++++++++++++ www/core/components/question/lang/en.json | 1 + .../components/question/services/delegate.js | 18 +++++++++++++++ www/core/scss/styles.scss | 5 +++++ 8 files changed, 74 insertions(+), 4 deletions(-) diff --git a/www/addons/mod_quiz/controllers/player.js b/www/addons/mod_quiz/controllers/player.js index b5191bfaf53..5dd7c294011 100644 --- a/www/addons/mod_quiz/controllers/player.js +++ b/www/addons/mod_quiz/controllers/player.js @@ -201,15 +201,14 @@ angular.module('mm.addons.mod_quiz') function loadSummary() { $scope.showSummary = true; $scope.summaryQuestions = []; + return $mmaModQuiz.getAttemptSummary(attempt.id, $scope.preflightData, offline, true, true).then(function(questions) { $scope.summaryQuestions = questions; $scope.canReturn = attempt.state == $mmaModQuiz.ATTEMPT_IN_PROGRESS && !attempt.finishedOffline; + $scope.preventSubmitMessages = $mmaModQuiz.getPreventSubmitMessages(questions); attempt.dueDateWarning = $mmaModQuiz.getAttemptDueDateWarning(quiz, attempt); - // Remove all answers stored since the questions aren't rendered anymore. - $mmUtil.emptyObject($scope.answers); - // Log summary as viewed. $mmaModQuiz.logViewAttemptSummary(attempt.id); }).catch(function(message) { diff --git a/www/addons/mod_quiz/lang/en.json b/www/addons/mod_quiz/lang/en.json index e2e0b89d527..cbe283343d6 100644 --- a/www/addons/mod_quiz/lang/en.json +++ b/www/addons/mod_quiz/lang/en.json @@ -4,6 +4,7 @@ "attemptnumber": "Attempt", "attemptquiznow": "Attempt quiz now", "attemptstate": "State", + "cannotsubmitquizdueto": "This quiz cannot be submitted due to the following reasons:", "comment": "Comment", "completedon": "Completed on", "confirmclose": "Once you submit, you will no longer be able to change your answers for this attempt.", diff --git a/www/addons/mod_quiz/services/quiz.js b/www/addons/mod_quiz/services/quiz.js index 5e0500e47ff..625e0de3404 100644 --- a/www/addons/mod_quiz/services/quiz.js +++ b/www/addons/mod_quiz/services/quiz.js @@ -749,6 +749,28 @@ angular.module('mm.addons.mod_quiz') } }; + /** + * Given a list of questions, check if the quiz can be submitted. + * Will return an array with the messages to prevent the submit. Empty array if quiz can be submitted. + * + * @module mm.addons.mod_quiz + * @ngdoc method + * @name $mmaModQuiz#getPreventSubmitMessages + * @param {Object[]} questions Questions. + * @return {String[]} List of prevent submit messages. Empty array if quiz can be submitted. + */ + self.getPreventSubmitMessages = function(questions) { + var messages = []; + angular.forEach(questions, function(question) { + var message = $mmQuestionDelegate.getPreventSubmitMessage(question); + if (message) { + message = $translate.instant(message); + messages.push($translate.instant('mm.question.questionmessage', {$a: question.slot, $b: message})); + } + }); + return messages; + }; + /** * Get cache key for Quiz data WS calls. * diff --git a/www/addons/mod_quiz/templates/player.html b/www/addons/mod_quiz/templates/player.html index 10535a40e6b..ec6fe37cb1f 100644 --- a/www/addons/mod_quiz/templates/player.html +++ b/www/addons/mod_quiz/templates/player.html @@ -64,7 +64,12 @@

{{ 'mma.mod_quiz.summaryofattempt' | translate }}

{{ attempt.dueDateWarning }}
-
+
+

{{ 'mma.mod_quiz.cannotsubmitquizdueto' | translate }}

+

{{message}}

+ {{ 'mm.core.openinbrowser' | translate }} +
+
diff --git a/www/addons/qtype/essay/handlers.js b/www/addons/qtype/essay/handlers.js index 051833a1ebd..26daf2d75a5 100644 --- a/www/addons/qtype/essay/handlers.js +++ b/www/addons/qtype/essay/handlers.js @@ -36,6 +36,25 @@ angular.module('mm.addons.qtype_essay') return 'manualgraded'; }; + /** + * Check if a question can be submitted. + * If a question cannot be submitted it should return a message explaining why (translated or not). + * + * @module mm.core.question + * @ngdoc method + * @name $mmQuestionDelegate#getPreventSubmitMessage + * @param {Object} question Question. + * @return {String} Prevent submit message. Undefined or empty if cannot be submitted. + */ + self.getPreventSubmitMessage = function(question) { + var questionEl = angular.element(question.html)[0]; + + if (questionEl.querySelector('div[id*=filemanager]')) { + // The question allows attachments. Since the app cannot attach files yet we will prevent submitting the question. + return 'mm.question.errorattachmentsnotsupported'; + } + }; + /** * Check if a response is complete. * diff --git a/www/core/components/question/lang/en.json b/www/core/components/question/lang/en.json index 472a32c78b5..3dd8f89c385 100644 --- a/www/core/components/question/lang/en.json +++ b/www/core/components/question/lang/en.json @@ -13,6 +13,7 @@ "notanswered": "Not answered", "notyetanswered": "Not yet answered", "partiallycorrect": "Partially correct", + "questionmessage": "Question {{$a}}: {{$b}}", "requiresgrading": "Requires grading", "unknown": "Cannot determine status" } diff --git a/www/core/components/question/services/delegate.js b/www/core/components/question/services/delegate.js index 491e14bd6a7..9c55afb76cc 100644 --- a/www/core/components/question/services/delegate.js +++ b/www/core/components/question/services/delegate.js @@ -130,6 +130,24 @@ angular.module('mm.core.question') } }; + /** + * Check if a question can be submitted. + * If a question cannot be submitted it should return a message explaining why (translated or not). + * + * @module mm.core.question + * @ngdoc method + * @name $mmQuestionDelegate#getPreventSubmitMessage + * @param {Object} question Question. + * @return {String} Prevent submit message. Undefined or empty if cannot be submitted. + */ + self.getPreventSubmitMessage = function(question) { + var type = 'qtype_' + question.type, + handler = enabledHandlers[type]; + if (typeof handler != 'undefined' && handler.getPreventSubmitMessage) { + return handler.getPreventSubmitMessage(question); + } + }; + /** * Check if a response is complete. * diff --git a/www/core/scss/styles.scss b/www/core/scss/styles.scss index 8567282310e..fdb82cd9cce 100644 --- a/www/core/scss/styles.scss +++ b/www/core/scss/styles.scss @@ -74,6 +74,11 @@ em { @extend h2; } +.item.item-text-wrap .item-heading { + overflow: visible; + white-space: normal; +} + // More options for grids. .col-65 { @include flex(0, 0, 65%);