From 783a5c8ce70ec67938cd0ba0090c36dbb01c0bf2 Mon Sep 17 00:00:00 2001 From: Huong Nguyen Date: Mon, 4 Jul 2022 15:54:25 +0700 Subject: [PATCH 1/4] MDL-74996 Quiz: Convert submission confirmation to core/modal_factory --- .../amd/build/submission_confirmation.min.js | 11 +++ .../build/submission_confirmation.min.js.map | 1 + mod/quiz/amd/src/submission_confirmation.js | 72 +++++++++++++++++++ mod/quiz/renderer.php | 5 +- 4 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 mod/quiz/amd/build/submission_confirmation.min.js create mode 100644 mod/quiz/amd/build/submission_confirmation.min.js.map create mode 100644 mod/quiz/amd/src/submission_confirmation.js diff --git a/mod/quiz/amd/build/submission_confirmation.min.js b/mod/quiz/amd/build/submission_confirmation.min.js new file mode 100644 index 00000000000..31fa03faece --- /dev/null +++ b/mod/quiz/amd/build/submission_confirmation.min.js @@ -0,0 +1,11 @@ +define("mod_quiz/submission_confirmation",["exports","core/notification","core/prefetch","core/str","core/modal_factory","core/modal_events"],(function(_exports,_notification,_prefetch,_str,Modal,ModalEvents){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} +/** + * A javascript module to handle submission confirmation for quiz. + * + * @module mod_quiz/submission_confirmation + * @copyright 2022 Huong Nguyen + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since 4.1 + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_notification=_interopRequireDefault(_notification),_prefetch=_interopRequireDefault(_prefetch),Modal=_interopRequireWildcard(Modal),ModalEvents=_interopRequireWildcard(ModalEvents);const SELECTOR_attemptSubmitButton=".path-mod-quiz .btn-finishattempt button",SELECTOR_attemptSubmitForm="form#frm-finishattempt";_exports.init=()=>{_prefetch.default.prefetchStrings("core_admin",["confirmation"]),_prefetch.default.prefetchStrings("quiz",["confirmclose","submitallandfinish"]),(()=>{const submitAction=document.querySelector(SELECTOR_attemptSubmitButton);submitAction&&submitAction.addEventListener("click",(e=>{e.preventDefault(),Modal.create({type:Modal.types.SAVE_CANCEL,title:(0,_str.get_string)("confirmation","admin"),body:(0,_str.get_string)("confirmclose","quiz"),buttons:{save:(0,_str.get_string)("submitallandfinish","quiz")}}).then((modal=>(modal.show(),modal))).then((modal=>(modal.getRoot().on(ModalEvents.save,(()=>{submitAction.closest(SELECTOR_attemptSubmitForm).submit()})),modal))).catch(_notification.default.exception)}))})()}})); + +//# sourceMappingURL=submission_confirmation.min.js.map \ No newline at end of file diff --git a/mod/quiz/amd/build/submission_confirmation.min.js.map b/mod/quiz/amd/build/submission_confirmation.min.js.map new file mode 100644 index 00000000000..1d57488ed94 --- /dev/null +++ b/mod/quiz/amd/build/submission_confirmation.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"submission_confirmation.min.js","sources":["../src/submission_confirmation.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * A javascript module to handle submission confirmation for quiz.\n *\n * @module mod_quiz/submission_confirmation\n * @copyright 2022 Huong Nguyen \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 4.1\n */\n\nimport Notification from 'core/notification';\nimport Prefetch from 'core/prefetch';\nimport {get_string as getString} from 'core/str';\nimport * as Modal from 'core/modal_factory';\nimport * as ModalEvents from 'core/modal_events';\n\nconst SELECTOR = {\n attemptSubmitButton: '.path-mod-quiz .btn-finishattempt button',\n attemptSubmitForm: 'form#frm-finishattempt',\n};\n\n/**\n * Register events for attempt submit button.\n */\nconst registerEventListeners = () => {\n const submitAction = document.querySelector(SELECTOR.attemptSubmitButton);\n if (submitAction) {\n submitAction.addEventListener('click', e => {\n e.preventDefault();\n Modal.create({\n type: Modal.types.SAVE_CANCEL,\n title: getString('confirmation', 'admin'),\n body: getString('confirmclose', 'quiz'),\n buttons: {\n save: getString('submitallandfinish', 'quiz')\n },\n }).then(modal => {\n modal.show();\n return modal;\n }).then(modal => {\n modal.getRoot().on(ModalEvents.save, () => {\n const attemptForm = submitAction.closest(SELECTOR.attemptSubmitForm);\n attemptForm.submit();\n });\n return modal;\n }).catch(Notification.exception);\n });\n }\n};\n\n/**\n * Initialises.\n */\nexport const init = () => {\n Prefetch.prefetchStrings('core_admin', ['confirmation']);\n Prefetch.prefetchStrings('quiz', ['confirmclose', 'submitallandfinish']);\n registerEventListeners();\n};\n"],"names":["SELECTOR","prefetchStrings","submitAction","document","querySelector","addEventListener","e","preventDefault","Modal","create","type","types","SAVE_CANCEL","title","body","buttons","save","then","modal","show","getRoot","on","ModalEvents","closest","submit","catch","Notification","exception","registerEventListeners"],"mappings":";;;;;;;;8QA8BMA,6BACmB,2CADnBA,2BAEiB,uCAmCH,uBACPC,gBAAgB,aAAc,CAAC,mCAC/BA,gBAAgB,OAAQ,CAAC,eAAgB,uBA/BvB,YACrBC,aAAeC,SAASC,cAAcJ,8BACxCE,cACAA,aAAaG,iBAAiB,SAASC,IACnCA,EAAEC,iBACFC,MAAMC,OAAO,CACTC,KAAMF,MAAMG,MAAMC,YAClBC,OAAO,mBAAU,eAAgB,SACjCC,MAAM,mBAAU,eAAgB,QAChCC,QAAS,CACLC,MAAM,mBAAU,qBAAsB,WAE3CC,MAAKC,QACJA,MAAMC,OACCD,SACRD,MAAKC,QACJA,MAAME,UAAUC,GAAGC,YAAYN,MAAM,KACbd,aAAaqB,QAAQvB,4BAC7BwB,YAETN,SACRO,MAAMC,sBAAaC,eAW9BC"} \ No newline at end of file diff --git a/mod/quiz/amd/src/submission_confirmation.js b/mod/quiz/amd/src/submission_confirmation.js new file mode 100644 index 00000000000..e263bff6c49 --- /dev/null +++ b/mod/quiz/amd/src/submission_confirmation.js @@ -0,0 +1,72 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * A javascript module to handle submission confirmation for quiz. + * + * @module mod_quiz/submission_confirmation + * @copyright 2022 Huong Nguyen + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since 4.1 + */ + +import Notification from 'core/notification'; +import Prefetch from 'core/prefetch'; +import {get_string as getString} from 'core/str'; +import * as Modal from 'core/modal_factory'; +import * as ModalEvents from 'core/modal_events'; + +const SELECTOR = { + attemptSubmitButton: '.path-mod-quiz .btn-finishattempt button', + attemptSubmitForm: 'form#frm-finishattempt', +}; + +/** + * Register events for attempt submit button. + */ +const registerEventListeners = () => { + const submitAction = document.querySelector(SELECTOR.attemptSubmitButton); + if (submitAction) { + submitAction.addEventListener('click', e => { + e.preventDefault(); + Modal.create({ + type: Modal.types.SAVE_CANCEL, + title: getString('confirmation', 'admin'), + body: getString('confirmclose', 'quiz'), + buttons: { + save: getString('submitallandfinish', 'quiz') + }, + }).then(modal => { + modal.show(); + return modal; + }).then(modal => { + modal.getRoot().on(ModalEvents.save, () => { + const attemptForm = submitAction.closest(SELECTOR.attemptSubmitForm); + attemptForm.submit(); + }); + return modal; + }).catch(Notification.exception); + }); + } +}; + +/** + * Initialises. + */ +export const init = () => { + Prefetch.prefetchStrings('core_admin', ['confirmation']); + Prefetch.prefetchStrings('quiz', ['confirmclose', 'submitallandfinish']); + registerEventListeners(); +}; diff --git a/mod/quiz/renderer.php b/mod/quiz/renderer.php index f323ef0eddb..be6cf9428d4 100644 --- a/mod/quiz/renderer.php +++ b/mod/quiz/renderer.php @@ -786,9 +786,10 @@ class mod_quiz_renderer extends plugin_renderer_base { new moodle_url($attemptobj->processattempt_url(), $options), get_string('submitallandfinish', 'quiz')); $button->id = 'responseform'; + $button->class = 'btn-finishattempt'; + $button->formid = 'frm-finishattempt'; if ($attemptobj->get_state() == quiz_attempt::IN_PROGRESS) { - $button->add_action(new confirm_action(get_string('confirmclose', 'quiz'), null, - get_string('submitallandfinish', 'quiz'))); + $this->page->requires->js_call_amd('mod_quiz/submission_confirmation', 'init'); } $button->primary = true; From d4d666cdf49d8a2a92b65df2338f4327ace4d3ee Mon Sep 17 00:00:00 2001 From: Huong Nguyen Date: Mon, 4 Jul 2022 16:23:51 +0700 Subject: [PATCH 2/4] MDL-74996 Quiz: Change the submission confirmation dialogue content - Changed the title from "Confirm" to "Submit all your answers and finish?" - Changed the content from "Once you submit, you will no longer be able to change your answers for this attempt." to "You will no longer be able to change your answers for this attempt." - Changed some Behat scenarios to match with the new dialogue content --- badges/tests/behat/criteria_activity.feature | 6 +++--- mod/quiz/amd/build/submission_confirmation.min.js | 8 ++++---- mod/quiz/amd/build/submission_confirmation.min.js.map | 2 +- mod/quiz/amd/src/submission_confirmation.js | 10 +++++----- mod/quiz/lang/en/quiz.php | 3 ++- mod/quiz/report/overview/tests/behat/regrading.feature | 2 +- mod/quiz/tests/behat/add_quiz.feature | 2 +- mod/quiz/tests/behat/attempt_basic.feature | 4 ++-- mod/quiz/tests/behat/attempt_redo_questions.feature | 4 ++-- mod/quiz/tests/behat/attempt_require_previous.feature | 2 +- mod/quiz/tests/behat/feedback_on_override_mark.feature | 2 +- mod/quiz/tests/behat/manually_mark_question.feature | 2 +- 12 files changed, 24 insertions(+), 23 deletions(-) diff --git a/badges/tests/behat/criteria_activity.feature b/badges/tests/behat/criteria_activity.feature index 20965dba93f..df981116cb7 100644 --- a/badges/tests/behat/criteria_activity.feature +++ b/badges/tests/behat/criteria_activity.feature @@ -70,7 +70,7 @@ Feature: Award badges based on activity completion And I set the field "False" to "1" And I press "Finish attempt ..." And I press "Submit all and finish" - And I click on "Submit all and finish" "button" in the "Confirmation" "dialogue" + And I click on "Submit" "button" in the "Submit all your answers and finish?" "dialogue" And I log out And I am on the "Course 1" course page logged in as teacher1 @@ -97,7 +97,7 @@ Feature: Award badges based on activity completion And I set the field "False" to "1" And I press "Finish attempt ..." And I press "Submit all and finish" - And I click on "Submit all and finish" "button" in the "Confirmation" "dialogue" + And I click on "Submit" "button" in the "Submit all your answers and finish?" "dialogue" And I log out # Pass grade with student1 @@ -107,7 +107,7 @@ Feature: Award badges based on activity completion And I set the field "False" to "0" And I press "Finish attempt ..." And I press "Submit all and finish" - And I click on "Submit all and finish" "button" in the "Confirmation" "dialogue" + And I click on "Submit" "button" in the "Submit all your answers and finish?" "dialogue" And I log out # Enable badge access once all students have completed an activity. diff --git a/mod/quiz/amd/build/submission_confirmation.min.js b/mod/quiz/amd/build/submission_confirmation.min.js index 31fa03faece..f7e3e93aa4d 100644 --- a/mod/quiz/amd/build/submission_confirmation.min.js +++ b/mod/quiz/amd/build/submission_confirmation.min.js @@ -2,10 +2,10 @@ define("mod_quiz/submission_confirmation",["exports","core/notification","core/p /** * A javascript module to handle submission confirmation for quiz. * - * @module mod_quiz/submission_confirmation + * @module mod_quiz/submission_confirmation * @copyright 2022 Huong Nguyen - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @since 4.1 - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_notification=_interopRequireDefault(_notification),_prefetch=_interopRequireDefault(_prefetch),Modal=_interopRequireWildcard(Modal),ModalEvents=_interopRequireWildcard(ModalEvents);const SELECTOR_attemptSubmitButton=".path-mod-quiz .btn-finishattempt button",SELECTOR_attemptSubmitForm="form#frm-finishattempt";_exports.init=()=>{_prefetch.default.prefetchStrings("core_admin",["confirmation"]),_prefetch.default.prefetchStrings("quiz",["confirmclose","submitallandfinish"]),(()=>{const submitAction=document.querySelector(SELECTOR_attemptSubmitButton);submitAction&&submitAction.addEventListener("click",(e=>{e.preventDefault(),Modal.create({type:Modal.types.SAVE_CANCEL,title:(0,_str.get_string)("confirmation","admin"),body:(0,_str.get_string)("confirmclose","quiz"),buttons:{save:(0,_str.get_string)("submitallandfinish","quiz")}}).then((modal=>(modal.show(),modal))).then((modal=>(modal.getRoot().on(ModalEvents.save,(()=>{submitAction.closest(SELECTOR_attemptSubmitForm).submit()})),modal))).catch(_notification.default.exception)}))})()}})); + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since 4.1 + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_notification=_interopRequireDefault(_notification),_prefetch=_interopRequireDefault(_prefetch),Modal=_interopRequireWildcard(Modal),ModalEvents=_interopRequireWildcard(ModalEvents);const SELECTOR_attemptSubmitButton=".path-mod-quiz .btn-finishattempt button",SELECTOR_attemptSubmitForm="form#frm-finishattempt";_exports.init=()=>{_prefetch.default.prefetchStrings("core_admin",["confirmation"]),_prefetch.default.prefetchStrings("quiz",["submitallandfinish","submission_confirmation","confirmclose"]),(()=>{const submitAction=document.querySelector(SELECTOR_attemptSubmitButton);submitAction&&submitAction.addEventListener("click",(e=>{e.preventDefault(),Modal.create({type:Modal.types.SAVE_CANCEL,title:(0,_str.get_string)("submission_confirmation","quiz"),body:(0,_str.get_string)("confirmclose","quiz"),buttons:{save:(0,_str.get_string)("submitallandfinish","quiz")}}).then((modal=>(modal.show(),modal))).then((modal=>(modal.getRoot().on(ModalEvents.save,(()=>{submitAction.closest(SELECTOR_attemptSubmitForm).submit()})),modal))).catch(_notification.default.exception)}))})()}})); //# sourceMappingURL=submission_confirmation.min.js.map \ No newline at end of file diff --git a/mod/quiz/amd/build/submission_confirmation.min.js.map b/mod/quiz/amd/build/submission_confirmation.min.js.map index 1d57488ed94..11637b49264 100644 --- a/mod/quiz/amd/build/submission_confirmation.min.js.map +++ b/mod/quiz/amd/build/submission_confirmation.min.js.map @@ -1 +1 @@ -{"version":3,"file":"submission_confirmation.min.js","sources":["../src/submission_confirmation.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * A javascript module to handle submission confirmation for quiz.\n *\n * @module mod_quiz/submission_confirmation\n * @copyright 2022 Huong Nguyen \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 4.1\n */\n\nimport Notification from 'core/notification';\nimport Prefetch from 'core/prefetch';\nimport {get_string as getString} from 'core/str';\nimport * as Modal from 'core/modal_factory';\nimport * as ModalEvents from 'core/modal_events';\n\nconst SELECTOR = {\n attemptSubmitButton: '.path-mod-quiz .btn-finishattempt button',\n attemptSubmitForm: 'form#frm-finishattempt',\n};\n\n/**\n * Register events for attempt submit button.\n */\nconst registerEventListeners = () => {\n const submitAction = document.querySelector(SELECTOR.attemptSubmitButton);\n if (submitAction) {\n submitAction.addEventListener('click', e => {\n e.preventDefault();\n Modal.create({\n type: Modal.types.SAVE_CANCEL,\n title: getString('confirmation', 'admin'),\n body: getString('confirmclose', 'quiz'),\n buttons: {\n save: getString('submitallandfinish', 'quiz')\n },\n }).then(modal => {\n modal.show();\n return modal;\n }).then(modal => {\n modal.getRoot().on(ModalEvents.save, () => {\n const attemptForm = submitAction.closest(SELECTOR.attemptSubmitForm);\n attemptForm.submit();\n });\n return modal;\n }).catch(Notification.exception);\n });\n }\n};\n\n/**\n * Initialises.\n */\nexport const init = () => {\n Prefetch.prefetchStrings('core_admin', ['confirmation']);\n Prefetch.prefetchStrings('quiz', ['confirmclose', 'submitallandfinish']);\n registerEventListeners();\n};\n"],"names":["SELECTOR","prefetchStrings","submitAction","document","querySelector","addEventListener","e","preventDefault","Modal","create","type","types","SAVE_CANCEL","title","body","buttons","save","then","modal","show","getRoot","on","ModalEvents","closest","submit","catch","Notification","exception","registerEventListeners"],"mappings":";;;;;;;;8QA8BMA,6BACmB,2CADnBA,2BAEiB,uCAmCH,uBACPC,gBAAgB,aAAc,CAAC,mCAC/BA,gBAAgB,OAAQ,CAAC,eAAgB,uBA/BvB,YACrBC,aAAeC,SAASC,cAAcJ,8BACxCE,cACAA,aAAaG,iBAAiB,SAASC,IACnCA,EAAEC,iBACFC,MAAMC,OAAO,CACTC,KAAMF,MAAMG,MAAMC,YAClBC,OAAO,mBAAU,eAAgB,SACjCC,MAAM,mBAAU,eAAgB,QAChCC,QAAS,CACLC,MAAM,mBAAU,qBAAsB,WAE3CC,MAAKC,QACJA,MAAMC,OACCD,SACRD,MAAKC,QACJA,MAAME,UAAUC,GAAGC,YAAYN,MAAM,KACbd,aAAaqB,QAAQvB,4BAC7BwB,YAETN,SACRO,MAAMC,sBAAaC,eAW9BC"} \ No newline at end of file +{"version":3,"file":"submission_confirmation.min.js","sources":["../src/submission_confirmation.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * A javascript module to handle submission confirmation for quiz.\n *\n * @module mod_quiz/submission_confirmation\n * @copyright 2022 Huong Nguyen \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 4.1\n */\n\nimport Notification from 'core/notification';\nimport Prefetch from 'core/prefetch';\nimport {get_string as getString} from 'core/str';\nimport * as Modal from 'core/modal_factory';\nimport * as ModalEvents from 'core/modal_events';\n\nconst SELECTOR = {\n attemptSubmitButton: '.path-mod-quiz .btn-finishattempt button',\n attemptSubmitForm: 'form#frm-finishattempt',\n};\n\n/**\n * Register events for attempt submit button.\n */\nconst registerEventListeners = () => {\n const submitAction = document.querySelector(SELECTOR.attemptSubmitButton);\n if (submitAction) {\n submitAction.addEventListener('click', e => {\n e.preventDefault();\n Modal.create({\n type: Modal.types.SAVE_CANCEL,\n title: getString('submission_confirmation', 'quiz'),\n body: getString('confirmclose', 'quiz'),\n buttons: {\n save: getString('submitallandfinish', 'quiz')\n },\n }).then(modal => {\n modal.show();\n return modal;\n }).then(modal => {\n modal.getRoot().on(ModalEvents.save, () => {\n const attemptForm = submitAction.closest(SELECTOR.attemptSubmitForm);\n attemptForm.submit();\n });\n return modal;\n }).catch(Notification.exception);\n });\n }\n};\n\n/**\n * Initialises.\n */\nexport const init = () => {\n Prefetch.prefetchStrings('core_admin', ['confirmation']);\n Prefetch.prefetchStrings('quiz', ['submitallandfinish', 'submission_confirmation', 'confirmclose']);\n registerEventListeners();\n};\n"],"names":["SELECTOR","prefetchStrings","submitAction","document","querySelector","addEventListener","e","preventDefault","Modal","create","type","types","SAVE_CANCEL","title","body","buttons","save","then","modal","show","getRoot","on","ModalEvents","closest","submit","catch","Notification","exception","registerEventListeners"],"mappings":";;;;;;;;8QA8BMA,6BACmB,2CADnBA,2BAEiB,uCAmCH,uBACPC,gBAAgB,aAAc,CAAC,mCAC/BA,gBAAgB,OAAQ,CAAC,qBAAsB,0BAA2B,iBA/BxD,YACrBC,aAAeC,SAASC,cAAcJ,8BACxCE,cACAA,aAAaG,iBAAiB,SAASC,IACnCA,EAAEC,iBACFC,MAAMC,OAAO,CACTC,KAAMF,MAAMG,MAAMC,YAClBC,OAAO,mBAAU,0BAA2B,QAC5CC,MAAM,mBAAU,eAAgB,QAChCC,QAAS,CACLC,MAAM,mBAAU,qBAAsB,WAE3CC,MAAKC,QACJA,MAAMC,OACCD,SACRD,MAAKC,QACJA,MAAME,UAAUC,GAAGC,YAAYN,MAAM,KACbd,aAAaqB,QAAQvB,4BAC7BwB,YAETN,SACRO,MAAMC,sBAAaC,eAW9BC"} \ No newline at end of file diff --git a/mod/quiz/amd/src/submission_confirmation.js b/mod/quiz/amd/src/submission_confirmation.js index e263bff6c49..d3eee2e5466 100644 --- a/mod/quiz/amd/src/submission_confirmation.js +++ b/mod/quiz/amd/src/submission_confirmation.js @@ -16,10 +16,10 @@ /** * A javascript module to handle submission confirmation for quiz. * - * @module mod_quiz/submission_confirmation + * @module mod_quiz/submission_confirmation * @copyright 2022 Huong Nguyen - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @since 4.1 + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since 4.1 */ import Notification from 'core/notification'; @@ -43,7 +43,7 @@ const registerEventListeners = () => { e.preventDefault(); Modal.create({ type: Modal.types.SAVE_CANCEL, - title: getString('confirmation', 'admin'), + title: getString('submission_confirmation', 'quiz'), body: getString('confirmclose', 'quiz'), buttons: { save: getString('submitallandfinish', 'quiz') @@ -67,6 +67,6 @@ const registerEventListeners = () => { */ export const init = () => { Prefetch.prefetchStrings('core_admin', ['confirmation']); - Prefetch.prefetchStrings('quiz', ['confirmclose', 'submitallandfinish']); + Prefetch.prefetchStrings('quiz', ['submitallandfinish', 'submission_confirmation', 'confirmclose']); registerEventListeners(); }; diff --git a/mod/quiz/lang/en/quiz.php b/mod/quiz/lang/en/quiz.php index c43564d2c4c..2a0c30b2f86 100644 --- a/mod/quiz/lang/en/quiz.php +++ b/mod/quiz/lang/en/quiz.php @@ -218,7 +218,7 @@ $string['configshufflewithin'] = 'If you enable this option, then the parts maki $string['configtimelimit'] = 'Default time limit for quizzes in minutes. 0 mean no time limit.'; $string['configtimelimitsec'] = 'Default time limit for quizzes in seconds. 0 mean no time limit.'; $string['configurerandomquestion'] = 'Configure question'; -$string['confirmclose'] = 'Once you submit, you will no longer be able to change your answers for this attempt.'; +$string['confirmclose'] = 'Once you submit your answers, you won’t be able to change them.'; $string['confirmremovequestion'] = 'Are you sure you want to remove this {$a} question?'; $string['confirmremovesectionheading'] = 'Are you sure you want to remove the \'{$a}\' section heading?'; $string['confirmserverdelete'] = 'Are you sure you want to remove the server {$a} from the list?'; @@ -987,6 +987,7 @@ $string['stateoverdue'] = 'Overdue'; $string['stateoverduedetails'] = 'Must be submitted by {$a}'; $string['status'] = 'Status'; $string['stoponerror'] = 'Stop on error'; +$string['submission_confirmation'] = 'Submit all your answers and finish?'; $string['submitallandfinish'] = 'Submit all and finish'; $string['subneterror'] = 'Sorry, this quiz has been locked so that it is only accessible from certain locations. Currently your computer is not one of those allowed to use this quiz.'; $string['subnetnotice'] = 'This quiz has been locked so that it is only accessible from certain locations. Your computer is not on an allowed subnet. As teacher you are allowed to preview anyway.'; diff --git a/mod/quiz/report/overview/tests/behat/regrading.feature b/mod/quiz/report/overview/tests/behat/regrading.feature index 7b78eb58321..a31927b81e9 100644 --- a/mod/quiz/report/overview/tests/behat/regrading.feature +++ b/mod/quiz/report/overview/tests/behat/regrading.feature @@ -205,7 +205,7 @@ Feature: Regrading quiz attempts using the Grades report And I set the field "True" to "1" And I click on "Finish attempt ..." "button" And I press "Submit all and finish" - And I click on "Submit all and finish" "button" in the "Confirmation" "dialogue" + And I click on "Submit" "button" in the "Submit all your answers and finish?" "dialogue" And I am on the "Quiz for testing regrading" "mod_quiz > question bank" page logged in as teacher And I choose "Edit question" action for "TF" in the question bank And I set the field "Correct answer" to "False" diff --git a/mod/quiz/tests/behat/add_quiz.feature b/mod/quiz/tests/behat/add_quiz.feature index f480bce7c5f..af5c0cdda89 100644 --- a/mod/quiz/tests/behat/add_quiz.feature +++ b/mod/quiz/tests/behat/add_quiz.feature @@ -46,7 +46,7 @@ Feature: Add a quiz @javascript @skip_chrome_zerosize Scenario: Add and configure small quiz and perform an attempt as a student with Javascript enabled - Then I click on "Submit all and finish" "button" in the "Confirmation" "dialogue" + Then I click on "Submit all and finish" "button" in the "Submit all your answers and finish?" "dialogue" And I should see "So you think it is true" And I should see "Thank you, this is the general feedback" And I should see "The correct answer is 'False'." diff --git a/mod/quiz/tests/behat/attempt_basic.feature b/mod/quiz/tests/behat/attempt_basic.feature index 5b7f5169564..4d57d23501a 100644 --- a/mod/quiz/tests/behat/attempt_basic.feature +++ b/mod/quiz/tests/behat/attempt_basic.feature @@ -100,7 +100,7 @@ Feature: Attempt a quiz And I should see "Section 3" in the "quizsummaryofattempt" "table" And I press "Submit all and finish" - And I click on "Submit all and finish" "button" in the "Confirmation" "dialogue" + And I click on "Submit all and finish" "button" in the "Submit all your answers and finish?" "dialogue" And I should see "1.00 out of 6.00 (16.67%)" in the "Grade" "table_row" And I should see question "1" in section "Section 1" in the quiz navigation And I should see question "2" in section "Section 1" in the quiz navigation @@ -146,7 +146,7 @@ Feature: Attempt a quiz And I should not see "Text of the second question" And I follow "Finish attempt ..." And I press "Submit all and finish" - And I click on "Submit all and finish" "button" in the "Confirmation" "dialogue" + And I click on "Submit all and finish" "button" in the "Submit all your answers and finish?" "dialogue" And I should see "Text of the first question" And I should see "Text of the second question" And I follow "Show one page at a time" diff --git a/mod/quiz/tests/behat/attempt_redo_questions.feature b/mod/quiz/tests/behat/attempt_redo_questions.feature index 74b01c6c360..05cedcb7c22 100644 --- a/mod/quiz/tests/behat/attempt_redo_questions.feature +++ b/mod/quiz/tests/behat/attempt_redo_questions.feature @@ -96,7 +96,7 @@ Feature: Allow students to redo questions in a practice quiz, without starting a And I click on "Check" "button" in the "First question" "question" And I press "Finish attempt ..." And I press "Submit all and finish" - And I click on "Submit all and finish" "button" in the "Confirmation" "dialogue" + And I click on "Submit all and finish" "button" in the "Submit all your answers and finish?" "dialogue" Then "Try another question like this one" "button" should not exist @javascript @_switch_window @@ -108,7 +108,7 @@ Feature: Allow students to redo questions in a practice quiz, without starting a And I press "Try another question like this one" And I press "Finish attempt ..." And I press "Submit all and finish" - And I click on "Submit all and finish" "button" in the "Confirmation" "dialogue" + And I click on "Submit all and finish" "button" in the "Submit all your answers and finish?" "dialogue" And I log out And I am on the "Quiz 1" "mod_quiz > View" page logged in as "teacher" And I follow "Attempts: 1" diff --git a/mod/quiz/tests/behat/attempt_require_previous.feature b/mod/quiz/tests/behat/attempt_require_previous.feature index 3102ee6f639..4703e754b93 100644 --- a/mod/quiz/tests/behat/attempt_require_previous.feature +++ b/mod/quiz/tests/behat/attempt_require_previous.feature @@ -91,7 +91,7 @@ Feature: Attempt a quiz where some questions require that the previous question And I press "Attempt quiz" And I press "Finish attempt ..." And I press "Submit all and finish" - And I click on "Submit all and finish" "button" in the "Confirmation" "dialogue" + And I click on "Submit all and finish" "button" in the "Submit all your answers and finish?" "dialogue" Then the state of "First question" question is shown as "Not answered" And the state of "Second question" question is shown as "Not answered" diff --git a/mod/quiz/tests/behat/feedback_on_override_mark.feature b/mod/quiz/tests/behat/feedback_on_override_mark.feature index 926d5f1c4e8..c2620cd659a 100644 --- a/mod/quiz/tests/behat/feedback_on_override_mark.feature +++ b/mod/quiz/tests/behat/feedback_on_override_mark.feature @@ -33,7 +33,7 @@ Feature: Teachers see correct answers when overriding marks And I set the field "Answer" to "lion" And I follow "Finish attempt ..." And I press "Submit all and finish" - And I click on "Submit all and finish" "button" in the "Confirmation" "dialogue" + And I click on "Submit all and finish" "button" in the "Submit all your answers and finish?" "dialogue" And I log out @javascript @_switch_window @_bug_phantomjs diff --git a/mod/quiz/tests/behat/manually_mark_question.feature b/mod/quiz/tests/behat/manually_mark_question.feature index 2a7ed44c575..4c92475eac6 100644 --- a/mod/quiz/tests/behat/manually_mark_question.feature +++ b/mod/quiz/tests/behat/manually_mark_question.feature @@ -35,7 +35,7 @@ Feature: Teachers can override the grade for any question And I press "Attempt quiz" And I follow "Finish attempt ..." And I press "Submit all and finish" - And I click on "Submit all and finish" "button" in the "Confirmation" "dialogue" + And I click on "Submit all and finish" "button" in the "Submit all your answers and finish?" "dialogue" And I log out @javascript @_switch_window @_bug_phantomjs From c55d8aca6a059852c0097a96a1496236b829aaf9 Mon Sep 17 00:00:00 2001 From: Huong Nguyen Date: Mon, 4 Jul 2022 18:11:08 +0700 Subject: [PATCH 3/4] MDL-74996 Quiz: Unanswered question warning for submission confirmations Including in this commit: - New method called get_number_of_unanswered_questions() for getting the unanswered questions - PHPUnit and Behat tests for the new dialogue --- .../amd/build/submission_confirmation.min.js | 4 +- .../build/submission_confirmation.min.js.map | 2 +- mod/quiz/amd/src/submission_confirmation.js | 22 ++- mod/quiz/attemptlib.php | 16 ++ mod/quiz/lang/en/quiz.php | 1 + mod/quiz/renderer.php | 7 +- .../submission_confirmation.mustache | 37 +++++ mod/quiz/tests/attempt_walkthrough_test.php | 46 +++++- mod/quiz/tests/behat/attempt_basic.feature | 149 ++++++++++-------- mod/quiz/upgrade.txt | 2 + 10 files changed, 212 insertions(+), 74 deletions(-) create mode 100644 mod/quiz/templates/submission_confirmation.mustache diff --git a/mod/quiz/amd/build/submission_confirmation.min.js b/mod/quiz/amd/build/submission_confirmation.min.js index f7e3e93aa4d..e82016ef29d 100644 --- a/mod/quiz/amd/build/submission_confirmation.min.js +++ b/mod/quiz/amd/build/submission_confirmation.min.js @@ -1,4 +1,4 @@ -define("mod_quiz/submission_confirmation",["exports","core/notification","core/prefetch","core/str","core/modal_factory","core/modal_events"],(function(_exports,_notification,_prefetch,_str,Modal,ModalEvents){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} +define("mod_quiz/submission_confirmation",["exports","core/notification","core/prefetch","core/templates","core/str","core/modal_factory","core/modal_events"],(function(_exports,_notification,_prefetch,_templates,_str,Modal,ModalEvents){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} /** * A javascript module to handle submission confirmation for quiz. * @@ -6,6 +6,6 @@ define("mod_quiz/submission_confirmation",["exports","core/notification","core/p * @copyright 2022 Huong Nguyen * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since 4.1 - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_notification=_interopRequireDefault(_notification),_prefetch=_interopRequireDefault(_prefetch),Modal=_interopRequireWildcard(Modal),ModalEvents=_interopRequireWildcard(ModalEvents);const SELECTOR_attemptSubmitButton=".path-mod-quiz .btn-finishattempt button",SELECTOR_attemptSubmitForm="form#frm-finishattempt";_exports.init=()=>{_prefetch.default.prefetchStrings("core_admin",["confirmation"]),_prefetch.default.prefetchStrings("quiz",["submitallandfinish","submission_confirmation","confirmclose"]),(()=>{const submitAction=document.querySelector(SELECTOR_attemptSubmitButton);submitAction&&submitAction.addEventListener("click",(e=>{e.preventDefault(),Modal.create({type:Modal.types.SAVE_CANCEL,title:(0,_str.get_string)("submission_confirmation","quiz"),body:(0,_str.get_string)("confirmclose","quiz"),buttons:{save:(0,_str.get_string)("submitallandfinish","quiz")}}).then((modal=>(modal.show(),modal))).then((modal=>(modal.getRoot().on(ModalEvents.save,(()=>{submitAction.closest(SELECTOR_attemptSubmitForm).submit()})),modal))).catch(_notification.default.exception)}))})()}})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_notification=_interopRequireDefault(_notification),_prefetch=_interopRequireDefault(_prefetch),_templates=_interopRequireDefault(_templates),Modal=_interopRequireWildcard(Modal),ModalEvents=_interopRequireWildcard(ModalEvents);const SELECTOR_attemptSubmitButton=".path-mod-quiz .btn-finishattempt button",SELECTOR_attemptSubmitForm="form#frm-finishattempt",TEMPLATES_submissionConfirmation="mod_quiz/submission_confirmation";_exports.init=unAnsweredQuestions=>{_prefetch.default.prefetchStrings("core",["submit"]),_prefetch.default.prefetchStrings("core_admin",["confirmation"]),_prefetch.default.prefetchStrings("quiz",["submitallandfinish","submission_confirmation"]),_prefetch.default.prefetchTemplate(TEMPLATES_submissionConfirmation),(unAnsweredQuestions=>{const submitAction=document.querySelector(SELECTOR_attemptSubmitButton);submitAction&&submitAction.addEventListener("click",(e=>{e.preventDefault(),Modal.create({type:Modal.types.SAVE_CANCEL,title:(0,_str.get_string)("submission_confirmation","quiz"),body:_templates.default.render(TEMPLATES_submissionConfirmation,{hasunanswered:unAnsweredQuestions>0,totalunanswered:unAnsweredQuestions}),buttons:{save:(0,_str.get_string)("submitallandfinish","quiz")}}).then((modal=>(modal.show(),modal))).then((modal=>(modal.getRoot().on(ModalEvents.save,(()=>{submitAction.closest(SELECTOR_attemptSubmitForm).submit()})),modal))).catch(_notification.default.exception)}))})(unAnsweredQuestions)}})); //# sourceMappingURL=submission_confirmation.min.js.map \ No newline at end of file diff --git a/mod/quiz/amd/build/submission_confirmation.min.js.map b/mod/quiz/amd/build/submission_confirmation.min.js.map index 11637b49264..91cf12ed2b8 100644 --- a/mod/quiz/amd/build/submission_confirmation.min.js.map +++ b/mod/quiz/amd/build/submission_confirmation.min.js.map @@ -1 +1 @@ -{"version":3,"file":"submission_confirmation.min.js","sources":["../src/submission_confirmation.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * A javascript module to handle submission confirmation for quiz.\n *\n * @module mod_quiz/submission_confirmation\n * @copyright 2022 Huong Nguyen \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 4.1\n */\n\nimport Notification from 'core/notification';\nimport Prefetch from 'core/prefetch';\nimport {get_string as getString} from 'core/str';\nimport * as Modal from 'core/modal_factory';\nimport * as ModalEvents from 'core/modal_events';\n\nconst SELECTOR = {\n attemptSubmitButton: '.path-mod-quiz .btn-finishattempt button',\n attemptSubmitForm: 'form#frm-finishattempt',\n};\n\n/**\n * Register events for attempt submit button.\n */\nconst registerEventListeners = () => {\n const submitAction = document.querySelector(SELECTOR.attemptSubmitButton);\n if (submitAction) {\n submitAction.addEventListener('click', e => {\n e.preventDefault();\n Modal.create({\n type: Modal.types.SAVE_CANCEL,\n title: getString('submission_confirmation', 'quiz'),\n body: getString('confirmclose', 'quiz'),\n buttons: {\n save: getString('submitallandfinish', 'quiz')\n },\n }).then(modal => {\n modal.show();\n return modal;\n }).then(modal => {\n modal.getRoot().on(ModalEvents.save, () => {\n const attemptForm = submitAction.closest(SELECTOR.attemptSubmitForm);\n attemptForm.submit();\n });\n return modal;\n }).catch(Notification.exception);\n });\n }\n};\n\n/**\n * Initialises.\n */\nexport const init = () => {\n Prefetch.prefetchStrings('core_admin', ['confirmation']);\n Prefetch.prefetchStrings('quiz', ['submitallandfinish', 'submission_confirmation', 'confirmclose']);\n registerEventListeners();\n};\n"],"names":["SELECTOR","prefetchStrings","submitAction","document","querySelector","addEventListener","e","preventDefault","Modal","create","type","types","SAVE_CANCEL","title","body","buttons","save","then","modal","show","getRoot","on","ModalEvents","closest","submit","catch","Notification","exception","registerEventListeners"],"mappings":";;;;;;;;8QA8BMA,6BACmB,2CADnBA,2BAEiB,uCAmCH,uBACPC,gBAAgB,aAAc,CAAC,mCAC/BA,gBAAgB,OAAQ,CAAC,qBAAsB,0BAA2B,iBA/BxD,YACrBC,aAAeC,SAASC,cAAcJ,8BACxCE,cACAA,aAAaG,iBAAiB,SAASC,IACnCA,EAAEC,iBACFC,MAAMC,OAAO,CACTC,KAAMF,MAAMG,MAAMC,YAClBC,OAAO,mBAAU,0BAA2B,QAC5CC,MAAM,mBAAU,eAAgB,QAChCC,QAAS,CACLC,MAAM,mBAAU,qBAAsB,WAE3CC,MAAKC,QACJA,MAAMC,OACCD,SACRD,MAAKC,QACJA,MAAME,UAAUC,GAAGC,YAAYN,MAAM,KACbd,aAAaqB,QAAQvB,4BAC7BwB,YAETN,SACRO,MAAMC,sBAAaC,eAW9BC"} \ No newline at end of file +{"version":3,"file":"submission_confirmation.min.js","sources":["../src/submission_confirmation.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * A javascript module to handle submission confirmation for quiz.\n *\n * @module mod_quiz/submission_confirmation\n * @copyright 2022 Huong Nguyen \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 4.1\n */\n\nimport Notification from 'core/notification';\nimport Prefetch from 'core/prefetch';\nimport Templates from 'core/templates';\nimport {get_string as getString} from 'core/str';\nimport * as Modal from 'core/modal_factory';\nimport * as ModalEvents from 'core/modal_events';\n\nconst SELECTOR = {\n attemptSubmitButton: '.path-mod-quiz .btn-finishattempt button',\n attemptSubmitForm: 'form#frm-finishattempt',\n};\n\nconst TEMPLATES = {\n submissionConfirmation: 'mod_quiz/submission_confirmation',\n};\n\n/**\n * Register events for attempt submit button.\n * @param {int} unAnsweredQuestions Total number of un-answered questions\n */\nconst registerEventListeners = (unAnsweredQuestions) => {\n const submitAction = document.querySelector(SELECTOR.attemptSubmitButton);\n if (submitAction) {\n submitAction.addEventListener('click', e => {\n e.preventDefault();\n Modal.create({\n type: Modal.types.SAVE_CANCEL,\n title: getString('submission_confirmation', 'quiz'),\n body: Templates.render(TEMPLATES.submissionConfirmation, {\n hasunanswered: unAnsweredQuestions > 0,\n totalunanswered: unAnsweredQuestions\n }),\n buttons: {\n save: getString('submitallandfinish', 'quiz')\n },\n }).then(modal => {\n modal.show();\n return modal;\n }).then(modal => {\n modal.getRoot().on(ModalEvents.save, () => {\n const attemptForm = submitAction.closest(SELECTOR.attemptSubmitForm);\n attemptForm.submit();\n });\n return modal;\n }).catch(Notification.exception);\n });\n }\n};\n\n/**\n * Initialises.\n * @param {int} unAnsweredQuestions Total number of unanswered questions\n */\nexport const init = (unAnsweredQuestions) => {\n Prefetch.prefetchStrings('core', ['submit']);\n Prefetch.prefetchStrings('core_admin', ['confirmation']);\n Prefetch.prefetchStrings('quiz', ['submitallandfinish', 'submission_confirmation']);\n Prefetch.prefetchTemplate(TEMPLATES.submissionConfirmation);\n registerEventListeners(unAnsweredQuestions);\n};\n"],"names":["SELECTOR","TEMPLATES","unAnsweredQuestions","prefetchStrings","prefetchTemplate","submitAction","document","querySelector","addEventListener","e","preventDefault","Modal","create","type","types","SAVE_CANCEL","title","body","Templates","render","hasunanswered","totalunanswered","buttons","save","then","modal","show","getRoot","on","ModalEvents","closest","submit","catch","Notification","exception","registerEventListeners"],"mappings":";;;;;;;;4TA+BMA,6BACmB,2CADnBA,2BAEiB,yBAGjBC,iCACsB,iDAwCPC,wCACRC,gBAAgB,OAAQ,CAAC,6BACzBA,gBAAgB,aAAc,CAAC,mCAC/BA,gBAAgB,OAAQ,CAAC,qBAAsB,8CAC/CC,iBAAiBH,kCArCEC,CAAAA,4BACtBG,aAAeC,SAASC,cAAcP,8BACxCK,cACAA,aAAaG,iBAAiB,SAASC,IACnCA,EAAEC,iBACFC,MAAMC,OAAO,CACTC,KAAMF,MAAMG,MAAMC,YAClBC,OAAO,mBAAU,0BAA2B,QAC5CC,KAAMC,mBAAUC,OAAOlB,iCAAkC,CACrDmB,cAAelB,oBAAsB,EACrCmB,gBAAiBnB,sBAErBoB,QAAS,CACLC,MAAM,mBAAU,qBAAsB,WAE3CC,MAAKC,QACJA,MAAMC,OACCD,SACRD,MAAKC,QACJA,MAAME,UAAUC,GAAGC,YAAYN,MAAM,KACblB,aAAayB,QAAQ9B,4BAC7B+B,YAETN,SACRO,MAAMC,sBAAaC,eAc9BC,CAAuBjC"} \ No newline at end of file diff --git a/mod/quiz/amd/src/submission_confirmation.js b/mod/quiz/amd/src/submission_confirmation.js index d3eee2e5466..71d6574161c 100644 --- a/mod/quiz/amd/src/submission_confirmation.js +++ b/mod/quiz/amd/src/submission_confirmation.js @@ -24,6 +24,7 @@ import Notification from 'core/notification'; import Prefetch from 'core/prefetch'; +import Templates from 'core/templates'; import {get_string as getString} from 'core/str'; import * as Modal from 'core/modal_factory'; import * as ModalEvents from 'core/modal_events'; @@ -33,10 +34,15 @@ const SELECTOR = { attemptSubmitForm: 'form#frm-finishattempt', }; +const TEMPLATES = { + submissionConfirmation: 'mod_quiz/submission_confirmation', +}; + /** * Register events for attempt submit button. + * @param {int} unAnsweredQuestions Total number of un-answered questions */ -const registerEventListeners = () => { +const registerEventListeners = (unAnsweredQuestions) => { const submitAction = document.querySelector(SELECTOR.attemptSubmitButton); if (submitAction) { submitAction.addEventListener('click', e => { @@ -44,7 +50,10 @@ const registerEventListeners = () => { Modal.create({ type: Modal.types.SAVE_CANCEL, title: getString('submission_confirmation', 'quiz'), - body: getString('confirmclose', 'quiz'), + body: Templates.render(TEMPLATES.submissionConfirmation, { + hasunanswered: unAnsweredQuestions > 0, + totalunanswered: unAnsweredQuestions + }), buttons: { save: getString('submitallandfinish', 'quiz') }, @@ -64,9 +73,12 @@ const registerEventListeners = () => { /** * Initialises. + * @param {int} unAnsweredQuestions Total number of unanswered questions */ -export const init = () => { +export const init = (unAnsweredQuestions) => { + Prefetch.prefetchStrings('core', ['submit']); Prefetch.prefetchStrings('core_admin', ['confirmation']); - Prefetch.prefetchStrings('quiz', ['submitallandfinish', 'submission_confirmation', 'confirmclose']); - registerEventListeners(); + Prefetch.prefetchStrings('quiz', ['submitallandfinish', 'submission_confirmation']); + Prefetch.prefetchTemplate(TEMPLATES.submissionConfirmation); + registerEventListeners(unAnsweredQuestions); }; diff --git a/mod/quiz/attemptlib.php b/mod/quiz/attemptlib.php index 3639cc8cd7f..c2b1aab7472 100644 --- a/mod/quiz/attemptlib.php +++ b/mod/quiz/attemptlib.php @@ -2712,6 +2712,22 @@ class quiz_attempt { } return false; } + + /** + * Get the total number of unanswered questions in the attempt. + * + * @return int + */ + public function get_number_of_unanswered_questions(): int { + $totalunanswered = 0; + foreach ($this->get_slots() as $slot) { + $questionstate = $this->get_question_state($slot); + if ($questionstate == question_state::$todo || $questionstate == question_state::$invalid) { + $totalunanswered++; + } + } + return $totalunanswered; + } } diff --git a/mod/quiz/lang/en/quiz.php b/mod/quiz/lang/en/quiz.php index 2a0c30b2f86..3fd0e324af5 100644 --- a/mod/quiz/lang/en/quiz.php +++ b/mod/quiz/lang/en/quiz.php @@ -988,6 +988,7 @@ $string['stateoverduedetails'] = 'Must be submitted by {$a}'; $string['status'] = 'Status'; $string['stoponerror'] = 'Stop on error'; $string['submission_confirmation'] = 'Submit all your answers and finish?'; +$string['submission_confirmation_unanswered'] = 'Questions without a response: {$a}'; $string['submitallandfinish'] = 'Submit all and finish'; $string['subneterror'] = 'Sorry, this quiz has been locked so that it is only accessible from certain locations. Currently your computer is not one of those allowed to use this quiz.'; $string['subnetnotice'] = 'This quiz has been locked so that it is only accessible from certain locations. Your computer is not on an allowed subnet. As teacher you are allowed to preview anyway.'; diff --git a/mod/quiz/renderer.php b/mod/quiz/renderer.php index be6cf9428d4..395d3f07e82 100644 --- a/mod/quiz/renderer.php +++ b/mod/quiz/renderer.php @@ -789,7 +789,12 @@ class mod_quiz_renderer extends plugin_renderer_base { $button->class = 'btn-finishattempt'; $button->formid = 'frm-finishattempt'; if ($attemptobj->get_state() == quiz_attempt::IN_PROGRESS) { - $this->page->requires->js_call_amd('mod_quiz/submission_confirmation', 'init'); + $totalunanswered = 0; + if ($attemptobj->get_quiz()->navmethod == 'free') { + // Only count the unanswered question if the navigation method is set to free. + $totalunanswered = $attemptobj->get_number_of_unanswered_questions(); + } + $this->page->requires->js_call_amd('mod_quiz/submission_confirmation', 'init', [$totalunanswered]); } $button->primary = true; diff --git a/mod/quiz/templates/submission_confirmation.mustache b/mod/quiz/templates/submission_confirmation.mustache new file mode 100644 index 00000000000..0961be517a2 --- /dev/null +++ b/mod/quiz/templates/submission_confirmation.mustache @@ -0,0 +1,37 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +{{! + @template mod_quiz/submission_confirmation + + Submission confirmation dialogue template + + Example context (json): + { + "hasunanswered": false, + "totalunanswered": 0 + } +}} +
+
+ {{#str}} confirmclose, mod_quiz {{/str}} +
+ {{#hasunanswered}} +
+ {{#str}} submission_confirmation_unanswered, mod_quiz, {{totalunanswered}} {{/str}} +
+ {{/hasunanswered}} +
diff --git a/mod/quiz/tests/attempt_walkthrough_test.php b/mod/quiz/tests/attempt_walkthrough_test.php index 542d01b3fe4..cbeb26db77b 100644 --- a/mod/quiz/tests/attempt_walkthrough_test.php +++ b/mod/quiz/tests/attempt_walkthrough_test.php @@ -50,7 +50,7 @@ class attempt_walkthrough_test extends \advanced_testcase { $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz'); $quiz = $quizgenerator->create_instance(array('course'=>$SITE->id, 'questionsperpage' => 0, 'grade' => 100.0, - 'sumgrades' => 2)); + 'sumgrades' => 3)); // Create a couple of questions. $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); @@ -58,10 +58,12 @@ class attempt_walkthrough_test extends \advanced_testcase { $cat = $questiongenerator->create_question_category(); $saq = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id)); $numq = $questiongenerator->create_question('numerical', null, array('category' => $cat->id)); + $matchq = $questiongenerator->create_question('match', null, ['category' => $cat->id]); // Add them to the quiz. quiz_add_quiz_question($saq->id, $quiz); quiz_add_quiz_question($numq->id, $quiz); + quiz_add_quiz_question($matchq->id, $quiz); // Make a user to do the quiz. $user1 = $this->getDataGenerator()->create_user(); @@ -76,18 +78,46 @@ class attempt_walkthrough_test extends \advanced_testcase { $attempt = quiz_create_attempt($quizobj, 1, false, $timenow, false, $user1->id); quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $timenow); - $this->assertEquals('1,2,0', $attempt->layout); + $this->assertEquals('1,2,3,0', $attempt->layout); quiz_attempt_save_started($quizobj, $quba, $attempt); // Process some responses from the student. $attemptobj = quiz_attempt::create($attempt->id); $this->assertFalse($attemptobj->has_response_to_at_least_one_graded_question()); + // The student has not answered any questions. + $this->assertEquals(3, $attemptobj->get_number_of_unanswered_questions()); $tosubmit = array(1 => array('answer' => 'frog'), 2 => array('answer' => '3.14')); $attemptobj->process_submitted_actions($timenow, false, $tosubmit); + // The student has answered two questions, and only one remaining. + $this->assertEquals(1, $attemptobj->get_number_of_unanswered_questions()); + + $tosubmit = [ + 3 => [ + 'frog' => 'amphibian', + 'cat' => 'mammal', + 'newt' => '' + ] + ]; + + $attemptobj->process_submitted_actions($timenow, false, $tosubmit); + // The student has answered three questions but one is invalid, so there is still one remaining. + $this->assertEquals(1, $attemptobj->get_number_of_unanswered_questions()); + + $tosubmit = [ + 3 => [ + 'frog' => 'amphibian', + 'cat' => 'mammal', + 'newt' => 'amphibian' + ] + ]; + + $attemptobj->process_submitted_actions($timenow, false, $tosubmit); + // The student has answered three questions, so there are no remaining. + $this->assertEquals(0, $attemptobj->get_number_of_unanswered_questions()); // Finish the attempt. $attemptobj = quiz_attempt::create($attempt->id); @@ -99,11 +129,12 @@ class attempt_walkthrough_test extends \advanced_testcase { // Check that results are stored as expected. $this->assertEquals(1, $attemptobj->get_attempt_number()); - $this->assertEquals(2, $attemptobj->get_sum_marks()); + $this->assertEquals(3, $attemptobj->get_sum_marks()); $this->assertEquals(true, $attemptobj->is_finished()); $this->assertEquals($timenow, $attemptobj->get_submitted_date()); $this->assertEquals($user1->id, $attemptobj->get_userid()); $this->assertTrue($attemptobj->has_response_to_at_least_one_graded_question()); + $this->assertEquals(0, $attemptobj->get_number_of_unanswered_questions()); // Check quiz grades. $grades = quiz_get_user_grades($quiz, $user1->id); @@ -165,6 +196,7 @@ class attempt_walkthrough_test extends \advanced_testcase { // Process some responses from the student. $attemptobj = quiz_attempt::create($attempt->id); + $this->assertEquals(1, $attemptobj->get_number_of_unanswered_questions()); $attemptobj->process_submitted_actions($quiz->timeclose - 30 * MINSECS, false, [1 => ['answer' => 'frog']]); // Attempt goes overdue (e.g. if cron ran). @@ -178,6 +210,7 @@ class attempt_walkthrough_test extends \advanced_testcase { $this->assertEquals(0, $attemptobj->get_submitted_date()); $this->assertEquals($user->id, $attemptobj->get_userid()); $this->assertTrue($attemptobj->has_response_to_at_least_one_graded_question()); + $this->assertEquals(0, $attemptobj->get_number_of_unanswered_questions()); // Student submits the attempt during the grace period. $attemptobj = quiz_attempt::create($attempt->id); @@ -190,6 +223,7 @@ class attempt_walkthrough_test extends \advanced_testcase { $this->assertEquals($quiz->timeclose + 30 * MINSECS, $attemptobj->get_submitted_date()); $this->assertEquals($user->id, $attemptobj->get_userid()); $this->assertTrue($attemptobj->has_response_to_at_least_one_graded_question()); + $this->assertEquals(0, $attemptobj->get_number_of_unanswered_questions()); } public function test_quiz_attempt_walkthrough_close_time_extended_at_last_minute() { @@ -291,6 +325,7 @@ class attempt_walkthrough_test extends \advanced_testcase { // Process some responses from the student. $attemptobj = quiz_attempt::create($attempt->id); $this->assertFalse($attemptobj->has_response_to_at_least_one_graded_question()); + $this->assertEquals(4, $attemptobj->get_number_of_unanswered_questions()); $tosubmit = array(); $selectedquestionid = $quba->get_question_attempt(1)->get_question_id(); @@ -307,6 +342,7 @@ class attempt_walkthrough_test extends \advanced_testcase { // Finish the attempt. $attemptobj = quiz_attempt::create($attempt->id); $this->assertTrue($attemptobj->has_response_to_at_least_one_graded_question()); + $this->assertEquals(0, $attemptobj->get_number_of_unanswered_questions()); $attemptobj->process_finish($timenow, false); // Re-load quiz attempt data. @@ -319,6 +355,7 @@ class attempt_walkthrough_test extends \advanced_testcase { $this->assertEquals($timenow, $attemptobj->get_submitted_date()); $this->assertEquals($user1->id, $attemptobj->get_userid()); $this->assertTrue($attemptobj->has_response_to_at_least_one_graded_question()); + $this->assertEquals(0, $attemptobj->get_number_of_unanswered_questions()); // Check quiz grades. $grades = quiz_get_user_grades($quiz, $user1->id); @@ -389,6 +426,7 @@ class attempt_walkthrough_test extends \advanced_testcase { // Process some responses from the student. $attemptobj = quiz_attempt::create($attempt->id); $this->assertFalse($attemptobj->has_response_to_at_least_one_graded_question()); + $this->assertEquals(1, $attemptobj->get_number_of_unanswered_questions()); $tosubmit = array(1 => array('answer' => $correctresponse)); $attemptobj->process_submitted_actions($timenow, false, $tosubmit); @@ -396,6 +434,7 @@ class attempt_walkthrough_test extends \advanced_testcase { // Finish the attempt. $attemptobj = quiz_attempt::create($attempt->id); $this->assertTrue($attemptobj->has_response_to_at_least_one_graded_question()); + $this->assertEquals(0, $attemptobj->get_number_of_unanswered_questions()); $attemptobj->process_finish($timenow, false); @@ -409,6 +448,7 @@ class attempt_walkthrough_test extends \advanced_testcase { $this->assertEquals($timenow, $attemptobj->get_submitted_date()); $this->assertEquals($user1->id, $attemptobj->get_userid()); $this->assertTrue($attemptobj->has_response_to_at_least_one_graded_question()); + $this->assertEquals(0, $attemptobj->get_number_of_unanswered_questions()); // Check quiz grades. $grades = quiz_get_user_grades($this->quizwithvariants, $user1->id); diff --git a/mod/quiz/tests/behat/attempt_basic.feature b/mod/quiz/tests/behat/attempt_basic.feature index 4d57d23501a..eaf53ec30d4 100644 --- a/mod/quiz/tests/behat/attempt_basic.feature +++ b/mod/quiz/tests/behat/attempt_basic.feature @@ -17,21 +17,50 @@ Feature: Attempt a quiz And the following "question categories" exist: | contextlevel | reference | name | | Course | C1 | Test questions | - And the following "activities" exist: - | activity | name | intro | course | idnumber | - | quiz | Quiz 1 | Quiz 1 description | C1 | quiz1 | - - @javascript - Scenario: Attempt a quiz with a single unnamed section, review and re-attempt - Given the following "questions" exist: + And the following "questions" exist: | questioncategory | qtype | name | questiontext | | Test questions | truefalse | TF1 | First question | | Test questions | truefalse | TF2 | Second question | + | Test questions | truefalse | TF3 | Third question | + | Test questions | truefalse | TF4 | Fourth question | + | Test questions | truefalse | TF5 | Fifth question | + | Test questions | truefalse | TF6 | Sixth question | + And the following "activities" exist: + | activity | name | intro | course | idnumber | grade | navmethod | + | quiz | Quiz 1 | Quiz 1 description | C1 | quiz1 | 100 | free | + | quiz | Quiz 2 | Quiz 2 description | C1 | quiz2 | 6 | free | + | quiz | Quiz 3 | Quiz 3 description | C1 | quiz3 | 100 | free | + | quiz | Quiz 4 | Quiz 4 description | C1 | quiz4 | 100 | sequential | And quiz "Quiz 1" contains the following questions: | question | page | maxmark | | TF1 | 1 | | | TF2 | 1 | 3.0 | - And user "student" has attempted "Quiz 1" with responses: + And quiz "Quiz 2" contains the following questions: + | question | page | + | TF1 | 1 | + | TF2 | 1 | + | TF3 | 2 | + | TF4 | 3 | + | TF5 | 4 | + | TF6 | 4 | + And quiz "Quiz 2" contains the following sections: + | heading | firstslot | shuffle | + | Section 1 | 1 | 0 | + | Section 2 | 3 | 0 | + | | 4 | 1 | + | Section 3 | 5 | 1 | + And quiz "Quiz 3" contains the following questions: + | question | page | + | TF1 | 1 | + | TF2 | 2 | + And quiz "Quiz 4" contains the following questions: + | question | page | + | TF1 | 1 | + | TF2 | 2 | + + @javascript + Scenario: Attempt a quiz with a single unnamed section, review and re-attempt + Given user "student" has attempted "Quiz 1" with responses: | slot | response | | 1 | True | | 2 | False | @@ -49,34 +78,8 @@ Feature: Attempt a quiz @javascript Scenario: Attempt a quiz with multiple sections - Given the following "activities" exist: - | activity | name | course | idnumber | grade | - | quiz | Quiz 2 | C1 | quiz2 | 6 | - And the following "questions" exist: - | questioncategory | qtype | name | questiontext | - | Test questions | truefalse | TF1 | First question | - | Test questions | truefalse | TF2 | Second question | - | Test questions | truefalse | TF3 | Third question | - | Test questions | truefalse | TF4 | Fourth question | - | Test questions | truefalse | TF5 | Fifth question | - | Test questions | truefalse | TF6 | Sixth question | - And quiz "Quiz 2" contains the following questions: - | question | page | - | TF1 | 1 | - | TF2 | 1 | - | TF3 | 2 | - | TF4 | 3 | - | TF5 | 4 | - | TF6 | 4 | - And quiz "Quiz 2" contains the following sections: - | heading | firstslot | shuffle | - | Section 1 | 1 | 0 | - | Section 2 | 3 | 0 | - | | 4 | 1 | - | Section 3 | 5 | 1 | - - When I am on the "Quiz 2" "mod_quiz > View" page logged in as "student" - And I press "Attempt quiz" + Given I am on the "Quiz 2" "mod_quiz > View" page logged in as "student" + When I press "Attempt quiz" Then I should see "Section 1" in the "Quiz navigation" "block" And I should see question "1" in section "Section 1" in the quiz navigation @@ -121,40 +124,62 @@ Feature: Attempt a quiz @javascript Scenario: Next and previous navigation - Given the following "questions" exist: - | questioncategory | qtype | name | questiontext | - | Test questions | truefalse | TF1 | Text of the first question | - | Test questions | truefalse | TF2 | Text of the second question | - And quiz "Quiz 1" contains the following questions: - | question | page | - | TF1 | 1 | - | TF2 | 2 | - When I am on the "Quiz 1" "mod_quiz > View" page logged in as "student" - And I press "Attempt quiz" - Then I should see "Text of the first question" - And I should not see "Text of the second question" + Given I am on the "Quiz 3" "mod_quiz > View" page logged in as "student" + When I press "Attempt quiz" + Then I should see "First question" + And I should not see "Second question" And I press "Next page" - And I should see "Text of the second question" - And I should not see "Text of the first question" + And I should see "Second question" + And I should not see "First question" And I click on "Finish attempt ..." "button" in the "region-main" "region" And I should see "Summary of attempt" And I press "Return to attempt" - And I should see "Text of the second question" - And I should not see "Text of the first question" + And I should see "Second question" + And I should not see "First question" And I press "Previous page" - And I should see "Text of the first question" - And I should not see "Text of the second question" + And I should see "First question" + And I should not see "Second question" And I follow "Finish attempt ..." And I press "Submit all and finish" + And I should see "Once you submit your answers, you won’t be able to change them." in the "Submit all your answers and finish?" "dialogue" + And I should see "Questions without a response: 2" in the "Submit all your answers and finish?" "dialogue" And I click on "Submit all and finish" "button" in the "Submit all your answers and finish?" "dialogue" - And I should see "Text of the first question" - And I should see "Text of the second question" + And I should see "First question" + And I should see "Second question" And I follow "Show one page at a time" - And I should see "Text of the first question" - And I should not see "Text of the second question" + And I should see "First question" + And I should not see "Second question" And I follow "Next page" - And I should see "Text of the second question" - And I should not see "Text of the first question" + And I should see "Second question" + And I should not see "First question" And I follow "Previous page" - And I should see "Text of the first question" - And I should not see "Text of the second question" + And I should see "First question" + And I should not see "Second question" + + @javascript + Scenario: Next and previous with sequential navigation method + Given I am on the "Quiz 4" "mod_quiz > View" page logged in as "student" + When I press "Attempt quiz" + Then I should see "First question" + And I should not see "Second question" + And I press "Next page" + And I should see "Second question" + And I should not see "First question" + And "Previous page" "button" should not exist + And I click on "Finish attempt ..." "button" in the "region-main" "region" + And I should see "Summary of attempt" + And I press "Submit all and finish" + And I should see "Once you submit your answers, you won’t be able to change them." in the "Submit all your answers and finish?" "dialogue" + And I should not see "Questions without a response: 2" in the "Submit all your answers and finish?" "dialogue" + And I click on "Submit" "button" in the "Submit all your answers and finish?" "dialogue" + And I should see "First question" + And I should see "Second question" + And I follow "Show one page at a time" + And I should see "First question" + And I should not see "Second question" + And I follow "Next page" + And I should see "Second question" + And I should not see "First question" + And I follow "Previous page" + And I should see "First question" + And I should not see "Second question" diff --git a/mod/quiz/upgrade.txt b/mod/quiz/upgrade.txt index ac3a5e6cdac..c412c99f6fa 100644 --- a/mod/quiz/upgrade.txt +++ b/mod/quiz/upgrade.txt @@ -16,6 +16,8 @@ related to out of sequence question except while reviewing the quiz (MDL-75210). internal method of the quiz_overview plugin. But if you are incorrectly using it, be aware that the API changed slightly. It now returns an array listing any questions which could not be regraded. +* New method called get_number_of_unanswered_questions() has been added to mod/quiz/attemptlib.php + to get the total number of unanswered questions in the current attempt. === 4.0 === From 2a74b873d7c7eb31aa34700c17095c543dcfbb82 Mon Sep 17 00:00:00 2001 From: Huong Nguyen Date: Tue, 25 Oct 2022 11:22:57 +0700 Subject: [PATCH 4/4] MDL-74996 Quiz: Convert submission confirmation to saveCancelPromise --- .../amd/build/submission_confirmation.min.js | 4 +- .../build/submission_confirmation.min.js.map | 2 +- mod/quiz/amd/src/submission_confirmation.js | 42 ++++++++----------- 3 files changed, 21 insertions(+), 27 deletions(-) diff --git a/mod/quiz/amd/build/submission_confirmation.min.js b/mod/quiz/amd/build/submission_confirmation.min.js index e82016ef29d..6ca80085c7d 100644 --- a/mod/quiz/amd/build/submission_confirmation.min.js +++ b/mod/quiz/amd/build/submission_confirmation.min.js @@ -1,4 +1,4 @@ -define("mod_quiz/submission_confirmation",["exports","core/notification","core/prefetch","core/templates","core/str","core/modal_factory","core/modal_events"],(function(_exports,_notification,_prefetch,_templates,_str,Modal,ModalEvents){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} +define("mod_quiz/submission_confirmation",["exports","core/notification","core/prefetch","core/templates","core/str"],(function(_exports,_notification,_prefetch,_templates,_str){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} /** * A javascript module to handle submission confirmation for quiz. * @@ -6,6 +6,6 @@ define("mod_quiz/submission_confirmation",["exports","core/notification","core/p * @copyright 2022 Huong Nguyen * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since 4.1 - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_notification=_interopRequireDefault(_notification),_prefetch=_interopRequireDefault(_prefetch),_templates=_interopRequireDefault(_templates),Modal=_interopRequireWildcard(Modal),ModalEvents=_interopRequireWildcard(ModalEvents);const SELECTOR_attemptSubmitButton=".path-mod-quiz .btn-finishattempt button",SELECTOR_attemptSubmitForm="form#frm-finishattempt",TEMPLATES_submissionConfirmation="mod_quiz/submission_confirmation";_exports.init=unAnsweredQuestions=>{_prefetch.default.prefetchStrings("core",["submit"]),_prefetch.default.prefetchStrings("core_admin",["confirmation"]),_prefetch.default.prefetchStrings("quiz",["submitallandfinish","submission_confirmation"]),_prefetch.default.prefetchTemplate(TEMPLATES_submissionConfirmation),(unAnsweredQuestions=>{const submitAction=document.querySelector(SELECTOR_attemptSubmitButton);submitAction&&submitAction.addEventListener("click",(e=>{e.preventDefault(),Modal.create({type:Modal.types.SAVE_CANCEL,title:(0,_str.get_string)("submission_confirmation","quiz"),body:_templates.default.render(TEMPLATES_submissionConfirmation,{hasunanswered:unAnsweredQuestions>0,totalunanswered:unAnsweredQuestions}),buttons:{save:(0,_str.get_string)("submitallandfinish","quiz")}}).then((modal=>(modal.show(),modal))).then((modal=>(modal.getRoot().on(ModalEvents.save,(()=>{submitAction.closest(SELECTOR_attemptSubmitForm).submit()})),modal))).catch(_notification.default.exception)}))})(unAnsweredQuestions)}})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_prefetch=_interopRequireDefault(_prefetch),_templates=_interopRequireDefault(_templates);const SELECTOR_attemptSubmitButton=".path-mod-quiz .btn-finishattempt button",SELECTOR_attemptSubmitForm="form#frm-finishattempt",TEMPLATES_submissionConfirmation="mod_quiz/submission_confirmation";_exports.init=unAnsweredQuestions=>{_prefetch.default.prefetchStrings("core",["submit"]),_prefetch.default.prefetchStrings("core_admin",["confirmation"]),_prefetch.default.prefetchStrings("quiz",["submitallandfinish","submission_confirmation"]),_prefetch.default.prefetchTemplate(TEMPLATES_submissionConfirmation),(unAnsweredQuestions=>{const submitAction=document.querySelector(SELECTOR_attemptSubmitButton);submitAction&&submitAction.addEventListener("click",(async e=>{e.preventDefault();try{await(0,_notification.saveCancelPromise)((0,_str.get_string)("submission_confirmation","quiz"),_templates.default.render(TEMPLATES_submissionConfirmation,{hasunanswered:unAnsweredQuestions>0,totalunanswered:unAnsweredQuestions}),(0,_str.get_string)("submitallandfinish","quiz")),submitAction.closest(SELECTOR_attemptSubmitForm).submit()}catch{return}}))})(unAnsweredQuestions)}})); //# sourceMappingURL=submission_confirmation.min.js.map \ No newline at end of file diff --git a/mod/quiz/amd/build/submission_confirmation.min.js.map b/mod/quiz/amd/build/submission_confirmation.min.js.map index 91cf12ed2b8..b6573302051 100644 --- a/mod/quiz/amd/build/submission_confirmation.min.js.map +++ b/mod/quiz/amd/build/submission_confirmation.min.js.map @@ -1 +1 @@ -{"version":3,"file":"submission_confirmation.min.js","sources":["../src/submission_confirmation.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * A javascript module to handle submission confirmation for quiz.\n *\n * @module mod_quiz/submission_confirmation\n * @copyright 2022 Huong Nguyen \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 4.1\n */\n\nimport Notification from 'core/notification';\nimport Prefetch from 'core/prefetch';\nimport Templates from 'core/templates';\nimport {get_string as getString} from 'core/str';\nimport * as Modal from 'core/modal_factory';\nimport * as ModalEvents from 'core/modal_events';\n\nconst SELECTOR = {\n attemptSubmitButton: '.path-mod-quiz .btn-finishattempt button',\n attemptSubmitForm: 'form#frm-finishattempt',\n};\n\nconst TEMPLATES = {\n submissionConfirmation: 'mod_quiz/submission_confirmation',\n};\n\n/**\n * Register events for attempt submit button.\n * @param {int} unAnsweredQuestions Total number of un-answered questions\n */\nconst registerEventListeners = (unAnsweredQuestions) => {\n const submitAction = document.querySelector(SELECTOR.attemptSubmitButton);\n if (submitAction) {\n submitAction.addEventListener('click', e => {\n e.preventDefault();\n Modal.create({\n type: Modal.types.SAVE_CANCEL,\n title: getString('submission_confirmation', 'quiz'),\n body: Templates.render(TEMPLATES.submissionConfirmation, {\n hasunanswered: unAnsweredQuestions > 0,\n totalunanswered: unAnsweredQuestions\n }),\n buttons: {\n save: getString('submitallandfinish', 'quiz')\n },\n }).then(modal => {\n modal.show();\n return modal;\n }).then(modal => {\n modal.getRoot().on(ModalEvents.save, () => {\n const attemptForm = submitAction.closest(SELECTOR.attemptSubmitForm);\n attemptForm.submit();\n });\n return modal;\n }).catch(Notification.exception);\n });\n }\n};\n\n/**\n * Initialises.\n * @param {int} unAnsweredQuestions Total number of unanswered questions\n */\nexport const init = (unAnsweredQuestions) => {\n Prefetch.prefetchStrings('core', ['submit']);\n Prefetch.prefetchStrings('core_admin', ['confirmation']);\n Prefetch.prefetchStrings('quiz', ['submitallandfinish', 'submission_confirmation']);\n Prefetch.prefetchTemplate(TEMPLATES.submissionConfirmation);\n registerEventListeners(unAnsweredQuestions);\n};\n"],"names":["SELECTOR","TEMPLATES","unAnsweredQuestions","prefetchStrings","prefetchTemplate","submitAction","document","querySelector","addEventListener","e","preventDefault","Modal","create","type","types","SAVE_CANCEL","title","body","Templates","render","hasunanswered","totalunanswered","buttons","save","then","modal","show","getRoot","on","ModalEvents","closest","submit","catch","Notification","exception","registerEventListeners"],"mappings":";;;;;;;;4TA+BMA,6BACmB,2CADnBA,2BAEiB,yBAGjBC,iCACsB,iDAwCPC,wCACRC,gBAAgB,OAAQ,CAAC,6BACzBA,gBAAgB,aAAc,CAAC,mCAC/BA,gBAAgB,OAAQ,CAAC,qBAAsB,8CAC/CC,iBAAiBH,kCArCEC,CAAAA,4BACtBG,aAAeC,SAASC,cAAcP,8BACxCK,cACAA,aAAaG,iBAAiB,SAASC,IACnCA,EAAEC,iBACFC,MAAMC,OAAO,CACTC,KAAMF,MAAMG,MAAMC,YAClBC,OAAO,mBAAU,0BAA2B,QAC5CC,KAAMC,mBAAUC,OAAOlB,iCAAkC,CACrDmB,cAAelB,oBAAsB,EACrCmB,gBAAiBnB,sBAErBoB,QAAS,CACLC,MAAM,mBAAU,qBAAsB,WAE3CC,MAAKC,QACJA,MAAMC,OACCD,SACRD,MAAKC,QACJA,MAAME,UAAUC,GAAGC,YAAYN,MAAM,KACblB,aAAayB,QAAQ9B,4BAC7B+B,YAETN,SACRO,MAAMC,sBAAaC,eAc9BC,CAAuBjC"} \ No newline at end of file +{"version":3,"file":"submission_confirmation.min.js","sources":["../src/submission_confirmation.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * A javascript module to handle submission confirmation for quiz.\n *\n * @module mod_quiz/submission_confirmation\n * @copyright 2022 Huong Nguyen \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 4.1\n */\n\nimport {saveCancelPromise} from 'core/notification';\nimport Prefetch from 'core/prefetch';\nimport Templates from 'core/templates';\nimport {get_string as getString} from 'core/str';\n\nconst SELECTOR = {\n attemptSubmitButton: '.path-mod-quiz .btn-finishattempt button',\n attemptSubmitForm: 'form#frm-finishattempt',\n};\n\nconst TEMPLATES = {\n submissionConfirmation: 'mod_quiz/submission_confirmation',\n};\n\n/**\n * Register events for attempt submit button.\n * @param {int} unAnsweredQuestions Total number of un-answered questions\n */\nconst registerEventListeners = (unAnsweredQuestions) => {\n const submitAction = document.querySelector(SELECTOR.attemptSubmitButton);\n if (submitAction) {\n submitAction.addEventListener('click', async(e) => {\n e.preventDefault();\n try {\n await saveCancelPromise(\n getString('submission_confirmation', 'quiz'),\n Templates.render(TEMPLATES.submissionConfirmation, {\n hasunanswered: unAnsweredQuestions > 0,\n totalunanswered: unAnsweredQuestions\n }),\n getString('submitallandfinish', 'quiz')\n );\n\n // Save pressed.\n submitAction.closest(SELECTOR.attemptSubmitForm).submit();\n } catch {\n // Cancel pressed.\n return;\n }\n });\n }\n};\n\n/**\n * Initialises.\n * @param {int} unAnsweredQuestions Total number of unanswered questions\n */\nexport const init = (unAnsweredQuestions) => {\n Prefetch.prefetchStrings('core', ['submit']);\n Prefetch.prefetchStrings('core_admin', ['confirmation']);\n Prefetch.prefetchStrings('quiz', ['submitallandfinish', 'submission_confirmation']);\n Prefetch.prefetchTemplate(TEMPLATES.submissionConfirmation);\n registerEventListeners(unAnsweredQuestions);\n};\n"],"names":["SELECTOR","TEMPLATES","unAnsweredQuestions","prefetchStrings","prefetchTemplate","submitAction","document","querySelector","addEventListener","async","e","preventDefault","Templates","render","hasunanswered","totalunanswered","closest","submit","registerEventListeners"],"mappings":";;;;;;;;kLA6BMA,6BACmB,2CADnBA,2BAEiB,yBAGjBC,iCACsB,iDAoCPC,wCACRC,gBAAgB,OAAQ,CAAC,6BACzBA,gBAAgB,aAAc,CAAC,mCAC/BA,gBAAgB,OAAQ,CAAC,qBAAsB,8CAC/CC,iBAAiBH,kCAjCEC,CAAAA,4BACtBG,aAAeC,SAASC,cAAcP,8BACxCK,cACAA,aAAaG,iBAAiB,SAASC,MAAAA,IACnCC,EAAEC,2BAEQ,oCACF,mBAAU,0BAA2B,QACrCC,mBAAUC,OAAOZ,iCAAkC,CAC/Ca,cAAeZ,oBAAsB,EACrCa,gBAAiBb,uBAErB,mBAAU,qBAAsB,SAIpCG,aAAaW,QAAQhB,4BAA4BiB,SACnD,kBAiBVC,CAAuBhB"} \ No newline at end of file diff --git a/mod/quiz/amd/src/submission_confirmation.js b/mod/quiz/amd/src/submission_confirmation.js index 71d6574161c..967cf2ef62d 100644 --- a/mod/quiz/amd/src/submission_confirmation.js +++ b/mod/quiz/amd/src/submission_confirmation.js @@ -22,12 +22,10 @@ * @since 4.1 */ -import Notification from 'core/notification'; +import {saveCancelPromise} from 'core/notification'; import Prefetch from 'core/prefetch'; import Templates from 'core/templates'; import {get_string as getString} from 'core/str'; -import * as Modal from 'core/modal_factory'; -import * as ModalEvents from 'core/modal_events'; const SELECTOR = { attemptSubmitButton: '.path-mod-quiz .btn-finishattempt button', @@ -45,28 +43,24 @@ const TEMPLATES = { const registerEventListeners = (unAnsweredQuestions) => { const submitAction = document.querySelector(SELECTOR.attemptSubmitButton); if (submitAction) { - submitAction.addEventListener('click', e => { + submitAction.addEventListener('click', async(e) => { e.preventDefault(); - Modal.create({ - type: Modal.types.SAVE_CANCEL, - title: getString('submission_confirmation', 'quiz'), - body: Templates.render(TEMPLATES.submissionConfirmation, { - hasunanswered: unAnsweredQuestions > 0, - totalunanswered: unAnsweredQuestions - }), - buttons: { - save: getString('submitallandfinish', 'quiz') - }, - }).then(modal => { - modal.show(); - return modal; - }).then(modal => { - modal.getRoot().on(ModalEvents.save, () => { - const attemptForm = submitAction.closest(SELECTOR.attemptSubmitForm); - attemptForm.submit(); - }); - return modal; - }).catch(Notification.exception); + try { + await saveCancelPromise( + getString('submission_confirmation', 'quiz'), + Templates.render(TEMPLATES.submissionConfirmation, { + hasunanswered: unAnsweredQuestions > 0, + totalunanswered: unAnsweredQuestions + }), + getString('submitallandfinish', 'quiz') + ); + + // Save pressed. + submitAction.closest(SELECTOR.attemptSubmitForm).submit(); + } catch { + // Cancel pressed. + return; + } }); } };