first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
define("mod_quiz/add_question_modal",["exports","core/modal"],(function(_exports,_modal){var obj;
/**
* Contain the logic for the add random question modal.
*
* @module mod_quiz/add_question_modal
* @copyright 2023 Andrew Lyons <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_modal=(obj=_modal)&&obj.__esModule?obj:{default:obj};class AddQuestionModal extends _modal.default{configure(modalConfig){modalConfig.large=!0,modalConfig.show=!0,modalConfig.removeOnClose=!0,this.setContextId(modalConfig.contextId),this.setAddOnPageId(modalConfig.addOnPage),super.configure(modalConfig)}constructor(root){super(root),this.contextId=null,this.addOnPageId=null}setContextId(id){this.contextId=id}getContextId(){return this.contextId}setAddOnPageId(id){this.addOnPageId=id}getAddOnPageId(){return this.addOnPageId}}return _exports.default=AddQuestionModal,_exports.default}));
//# sourceMappingURL=add_question_modal.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"add_question_modal.min.js","sources":["../src/add_question_modal.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 <http://www.gnu.org/licenses/>.\n\n/**\n * Contain the logic for the add random question modal.\n *\n * @module mod_quiz/add_question_modal\n * @copyright 2023 Andrew Lyons <andrew@nicols.co.uk>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Modal from 'core/modal';\n\nexport default class AddQuestionModal extends Modal {\n configure(modalConfig) {\n // Add question modals are always large.\n modalConfig.large = true;\n\n // Always show on creation.\n modalConfig.show = true;\n modalConfig.removeOnClose = true;\n\n // Apply question modal configuration.\n this.setContextId(modalConfig.contextId);\n this.setAddOnPageId(modalConfig.addOnPage);\n\n // Apply standard configuration.\n super.configure(modalConfig);\n }\n\n constructor(root) {\n super(root);\n\n this.contextId = null;\n this.addOnPageId = null;\n }\n\n /**\n * Save the Moodle context id that the question bank is being\n * rendered in.\n *\n * @method setContextId\n * @param {Number} id\n */\n setContextId(id) {\n this.contextId = id;\n }\n\n /**\n * Retrieve the saved Moodle context id.\n *\n * @method getContextId\n * @return {Number}\n */\n getContextId() {\n return this.contextId;\n }\n\n /**\n * Set the id of the page that the question should be added to\n * when the user clicks the add to quiz link.\n *\n * @method setAddOnPageId\n * @param {Number} id\n */\n setAddOnPageId(id) {\n this.addOnPageId = id;\n }\n\n /**\n * Returns the saved page id for the question to be added to.\n *\n * @method getAddOnPageId\n * @return {Number}\n */\n getAddOnPageId() {\n return this.addOnPageId;\n }\n\n}\n"],"names":["AddQuestionModal","Modal","configure","modalConfig","large","show","removeOnClose","setContextId","contextId","setAddOnPageId","addOnPage","constructor","root","addOnPageId","id","getContextId","this","getAddOnPageId"],"mappings":";;;;;;;iJAyBqBA,yBAAyBC,eAC1CC,UAAUC,aAENA,YAAYC,OAAQ,EAGpBD,YAAYE,MAAO,EACnBF,YAAYG,eAAgB,OAGvBC,aAAaJ,YAAYK,gBACzBC,eAAeN,YAAYO,iBAG1BR,UAAUC,aAGpBQ,YAAYC,YACFA,WAEDJ,UAAY,UACZK,YAAc,KAUvBN,aAAaO,SACJN,UAAYM,GASrBC,sBACWC,KAAKR,UAUhBC,eAAeK,SACND,YAAcC,GASvBG,wBACWD,KAAKH"}
+10
View File
@@ -0,0 +1,10 @@
/**
* JavaScript for the add_random_form class.
*
* @module mod_quiz/add_random_form
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("mod_quiz/add_random_form",["jquery","mod_quiz/random_question_form_preview"],(function($,RandomQuestionFormPreview){var SELECTORS_PREVIEW_CONTAINER='[data-region="random-question-preview-container"]',SELECTORS_CATEGORY_FORM_ELEMENT='[name="category"]',SELECTORS_SUBCATEGORY_FORM_ELEMENT='[name="includesubcategories"]',SELECTORS_TAG_IDS_FORM_ELEMENT='[name="fromtags[]"]',getCategorySelectValue=function(form){return form.find(SELECTORS_CATEGORY_FORM_ELEMENT).val()},shouldIncludeSubcategories=function(form,topCategories){return!!function(form,topCategories){var selectedValue=getCategorySelectValue(form);return topCategories.indexOf(selectedValue)>-1}(form,topCategories)||form.find(SELECTORS_SUBCATEGORY_FORM_ELEMENT).is(":checked")},reloadQuestionPreview=function(form,contextId,topCategories){var previewContainer=form.find(SELECTORS_PREVIEW_CONTAINER);RandomQuestionFormPreview.reload(previewContainer,function(form){return getCategorySelectValue(form).split(",")[0]}(form),shouldIncludeSubcategories(form,topCategories),function(form){return form.find(SELECTORS_TAG_IDS_FORM_ELEMENT).val().map((function(value){return value.split(",")[0]}))}(form),contextId)},addEventListeners=function(form,contextId,topCategories){var reloadTimerId=null;const tagsFilter=form.find(SELECTORS_TAG_IDS_FORM_ELEMENT);form.add(tagsFilter).on("change",(function(e){var element;((element=$(e.target)).closest(SELECTORS_CATEGORY_FORM_ELEMENT).length>0||element.closest(SELECTORS_SUBCATEGORY_FORM_ELEMENT).length>0||element.closest(SELECTORS_TAG_IDS_FORM_ELEMENT).length>0)&&(RandomQuestionFormPreview.showLoadingIcon(form),reloadTimerId&&clearTimeout(reloadTimerId),reloadTimerId=setTimeout((function(){reloadQuestionPreview(form,contextId,topCategories)}),2e3))}))};return{init:function(formId,contextId,topCategories,isTagsEnabled){if(1==isTagsEnabled){var form=$("#"+formId);reloadQuestionPreview(form,contextId,topCategories),addEventListeners(form,contextId,topCategories)}}}}));
//# sourceMappingURL=add_random_form.min.js.map
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+3
View File
@@ -0,0 +1,3 @@
define("mod_quiz/modal_quiz_question_bank",["exports","jquery","./add_question_modal","core/fragment","core_form/changechecker","core/modal_events"],(function(_exports,_jquery,_add_question_modal,Fragment,FormChangeChecker,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}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_jquery=_interopRequireDefault(_jquery),_add_question_modal=_interopRequireDefault(_add_question_modal),Fragment=_interopRequireWildcard(Fragment),FormChangeChecker=_interopRequireWildcard(FormChangeChecker),ModalEvents=_interopRequireWildcard(ModalEvents);const SELECTORS_ADD_TO_QUIZ_CONTAINER="td.addtoquizaction",SELECTORS_ANCHOR="a[href]",SELECTORS_PREVIEW_CONTAINER="td.previewquestionaction",SELECTORS_ADD_QUESTIONS_FORM="form#questionsubmit",SELECTORS_SORTERS=".sorters";class ModalQuizQuestionBank extends _add_question_modal.default{static init(contextId){document.addEventListener("click",(e=>{const trigger=e.target.closest('.menu [data-action="questionbank"]');trigger&&(e.preventDefault(),ModalQuizQuestionBank.create({contextId:contextId,title:trigger.dataset.header,addOnPage:trigger.dataset.addonpage,templateContext:{hidden:!0},large:!0}))}))}show(){return this.reloadBodyContent(window.location.search),super.show(this)}reloadBodyContent(querystring){this.setBody(Fragment.loadFragment("mod_quiz","quiz_question_bank",this.getContextId(),{querystring:querystring}))}handleAddToQuizEvent(e,anchorElement){const href=new URL(anchorElement.attr("href"));href.searchParams.set("addonpage",this.getAddOnPageId()),anchorElement.attr("href",href)}registerEventListeners(){super.registerEventListeners(this),this.getModal().on("submit",SELECTORS_ADD_QUESTIONS_FORM,(e=>{const formElement=(0,_jquery.default)(e.currentTarget);(0,_jquery.default)("<input />").attr("type","hidden").attr("name","addonpage").attr("value",this.getAddOnPageId()).appendTo(formElement)})),this.getModal().on("click",SELECTORS_ANCHOR,(e=>{const anchorElement=(0,_jquery.default)(e.currentTarget);anchorElement.closest(SELECTORS_ADD_TO_QUIZ_CONTAINER).length?this.handleAddToQuizEvent(e,anchorElement):anchorElement.closest(SELECTORS_PREVIEW_CONTAINER).length||anchorElement.closest(SELECTORS_SORTERS).length||(e.preventDefault(),this.reloadBodyContent(anchorElement.prop("search")))})),this.getRoot().on(ModalEvents.bodyRendered,(()=>{FormChangeChecker.disableAllChecks()}))}}var obj,key,value;return _exports.default=ModalQuizQuestionBank,value="mod_quiz-quiz-question-bank",(key="TYPE")in(obj=ModalQuizQuestionBank)?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,ModalQuizQuestionBank.registerModalType(),_exports.default}));
//# sourceMappingURL=modal_quiz_question_bank.min.js.map
File diff suppressed because one or more lines are too long
+14
View File
@@ -0,0 +1,14 @@
/**
* This class manages the confirmation pop-up (also called the pre-flight check)
* that is sometimes shown when a use clicks the start attempt button.
*
* This is also responsible for opening the pop-up window, if the quiz requires to be in one.
*
* @module mod_quiz/preflightcheck
* @copyright 2016 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.1
*/
define("mod_quiz/preflightcheck",["jquery","core/yui","core_form/changechecker"],(function($,Y,FormChangeChecker){var t={confirmDialogue:null,init:function(startButton,confirmationTitle,confirmationForm,popupoptions){var finalStartButton=startButton;Y.use("moodle-core-notification",(function(){Y.one(confirmationForm)&&(t.confirmDialogue=new M.core.dialogue({headerContent:confirmationTitle,bodyContent:Y.one(confirmationForm),draggable:!0,visible:!1,center:!0,modal:!0,width:null,extraClasses:["mod_quiz_preflight_popup"]}),Y.one(startButton).on("click",t.displayDialogue),Y.one("#id_cancel").on("click",t.hideDialogue),finalStartButton=t.confirmDialogue.get("boundingBox").one('[name="submitbutton"]')),popupoptions&&Y.one(finalStartButton).on("click",t.launchQuizPopup,t,popupoptions)}))},displayDialogue:function(e){e&&e.halt(),t.confirmDialogue.show()},hideDialogue:function(e){e&&e.halt(),t.confirmDialogue.hide(e)},launchQuizPopup:function(e,popupoptions){e.halt(),Y.use("io-form",(function(){var form=e.target.ancestor("form");FormChangeChecker.resetFormDirtyState(form.getDOMNode()),window.openpopup(e,{url:form.get("action")+"?"+Y.IO.stringify(form).replace(/\bcancel=/,"x="),windowname:"quizpopup",options:popupoptions,fullscreen:!0})}))}};return t}));
//# sourceMappingURL=preflightcheck.min.js.map
File diff suppressed because one or more lines are too long
+11
View File
@@ -0,0 +1,11 @@
define("mod_quiz/question_slot",["exports","core/ajax","core/notification"],(function(_exports,_ajax,_notification){var obj;
/**
* Render the question slot template for each question in the quiz edit view.
*
* @module mod_quiz/question_slot
* @copyright 2021 Catalyst IT Australia Pty Ltd
* @author Guillermo Gomez Arias <guillermogomez@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_notification=(obj=_notification)&&obj.__esModule?obj:{default:obj};const registerEventListeners=()=>{document.addEventListener("change",(e=>{if(!e.target.matches('[data-action="mod_quiz-select_slot"][data-slot-id]'))return;((slotId,newVersion)=>(0,_ajax.call)([{methodname:"mod_quiz_set_question_version",args:{slotid:slotId,newversion:newVersion}}])[0])(e.target.dataset.slotId,parseInt(e.target.value)).then((()=>{location.reload()})).catch(_notification.default.exception)}))};_exports.init=()=>{registerEventListeners()}}));
//# sourceMappingURL=question_slot.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"question_slot.min.js","sources":["../src/question_slot.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 <http://www.gnu.org/licenses/>.\n\n/**\n * Render the question slot template for each question in the quiz edit view.\n *\n * @module mod_quiz/question_slot\n * @copyright 2021 Catalyst IT Australia Pty Ltd\n * @author Guillermo Gomez Arias <guillermogomez@catalyst-au.net>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {call as fetchMany} from 'core/ajax';\nimport Notification from 'core/notification';\n\n/**\n * Set the question version for the slot.\n *\n * @param {Number} slotId\n * @param {Number} newVersion\n * @return {Array} The modified question version\n */\nconst setQuestionVersion = (slotId, newVersion) => fetchMany([{\n methodname: 'mod_quiz_set_question_version',\n args: {\n slotid: slotId,\n newversion: newVersion,\n }\n}])[0];\n\n/**\n * Replace the container with a new version.\n */\nconst registerEventListeners = () => {\n document.addEventListener('change', e => {\n if (!e.target.matches('[data-action=\"mod_quiz-select_slot\"][data-slot-id]')) {\n return;\n }\n\n const slotId = e.target.dataset.slotId;\n const newVersion = parseInt(e.target.value);\n\n setQuestionVersion(slotId, newVersion)\n .then(() => {\n location.reload();\n return;\n })\n .catch(Notification.exception);\n });\n};\n\n/** @property {Boolean} eventsRegistered If the event has been registered or not */\nlet eventsRegistered = false;\n\n/**\n * Entrypoint of the js.\n */\nexport const init = () => {\n if (eventsRegistered) {\n return;\n }\n\n registerEventListeners();\n};\n"],"names":["registerEventListeners","document","addEventListener","e","target","matches","slotId","newVersion","methodname","args","slotid","newversion","setQuestionVersion","dataset","parseInt","value","then","location","reload","catch","Notification","exception"],"mappings":";;;;;;;;4JA6CMA,uBAAyB,KAC3BC,SAASC,iBAAiB,UAAUC,QAC3BA,EAAEC,OAAOC,QAAQ,6DAbH,EAACC,OAAQC,cAAe,cAAU,CAAC,CAC1DC,WAAY,gCACZC,KAAM,CACFC,OAAQJ,OACRK,WAAYJ,eAEhB,GAcIK,CAHeT,EAAEC,OAAOS,QAAQP,OACbQ,SAASX,EAAEC,OAAOW,QAGhCC,MAAK,KACFC,SAASC,YAGZC,MAAMC,sBAAaC,6BAUZ,KAKhBrB"}
+11
View File
@@ -0,0 +1,11 @@
/**
* JavaScript for the random_question_form_preview of the
* add_random_form class.
*
* @module mod_quiz/random_question_form_preview
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("mod_quiz/random_question_form_preview",["jquery","core/ajax","core/str","core/notification","core/templates","core/paged_content_factory"],(function($,Ajax,Str,Notification,Templates,PagedContentFactory){var TEMPLATE_NAME="mod_quiz/random_question_form_preview_question_list",SELECTORS_LOADING_ICON_CONTAINER='[data-region="overlay-icon-container"]',SELECTORS_QUESTION_COUNT_CONTAINER='[data-region="question-count-container"]',SELECTORS_QUESTION_LIST_CONTAINER='[data-region="question-list-container"]',showLoadingIcon=function(root){root.find(SELECTORS_LOADING_ICON_CONTAINER).removeClass("hidden")},hideLoadingIcon=function(root){root.find(SELECTORS_LOADING_ICON_CONTAINER).addClass("hidden")},requestQuestions=function(categoryId,includeSubcategories,tagIds,contextId,limit,offset){var request={methodname:"core_question_get_random_question_summaries",args:{categoryid:categoryId,includesubcategories:includeSubcategories,tagids:tagIds,contextid:contextId,limit:limit,offset:offset}};return Ajax.call([request])[0]};return{reload:function(root,categoryId,includeSubcategories,tagIds,contextId){return showLoadingIcon(root),requestQuestions(categoryId,includeSubcategories,tagIds,contextId,5,0).then((function(response){var totalCount=response.totalcount;return function(root,questionCount){Str.get_string("questionsmatchingfilter","mod_quiz",questionCount).then((function(string){root.find(SELECTORS_QUESTION_COUNT_CONTAINER).html(string)})).fail(Notification.exception)}(root,totalCount),response})).then((function(response){var totalQuestionCount=response.totalcount,questions=response.questions;return questions.length?function(categoryId,includeSubcategories,tagIds,contextId,totalQuestionCount,firstPageQuestions){return PagedContentFactory.createFromAjax(totalQuestionCount,5,(function(pagesData){return pagesData.map((function(pageData){var limit=pageData.limit,offset=pageData.offset;return 0==offset?Templates.render(TEMPLATE_NAME,{questions:firstPageQuestions}):requestQuestions(categoryId,includeSubcategories,tagIds,contextId,limit,offset).then((function(response){var questions=response.questions;return Templates.render(TEMPLATE_NAME,{questions:questions})})).fail(Notification.exception)}))}))}(categoryId,includeSubcategories,tagIds,contextId,totalQuestionCount,questions):$.Deferred().resolve("","")})).then((function(html,js){var container=root.find(SELECTORS_QUESTION_LIST_CONTAINER);Templates.replaceNodeContents(container,html,js)})).always((function(){hideLoadingIcon(root)})).fail(Notification.exception)},showLoadingIcon:showLoadingIcon,hideLoadingIcon:hideLoadingIcon}}));
//# sourceMappingURL=random_question_form_preview.min.js.map
File diff suppressed because one or more lines are too long
+16
View File
@@ -0,0 +1,16 @@
define("mod_quiz/reopen_attempt_ui",["exports","core/notification","core/ajax","core/str"],(function(_exports,_notification,_ajax,_str){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0;
/**
* This module has the code to make the Re-open attempt button work, if present.
*
* That is, it looks for buttons with HTML like
* &lt;button type="button" data-action="reopen-attempt" data-attempt-id="227000" data-after-action-url="/mod/quiz/report.php">
* and if that is clicked, it first shows an 'Are you sure' pop-up, and if they are sure,
* the attempt is re-opened, and then the page reloads.
*
* @module mod_quiz/reopen_attempt_ui
* @copyright 2023 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
const reopenButtonClicked=async e=>{if(!(e.target instanceof HTMLElement&&e.target.matches('button[data-action="reopen-attempt"]')))return;e.preventDefault();const attemptId=e.target.dataset.attemptId;try{const messages=(0,_ajax.call)([{methodname:"mod_quiz_get_reopen_attempt_confirmation",args:{attemptid:attemptId}}]);await(0,_notification.saveCancelPromise)((0,_str.getString)("reopenattemptareyousuretitle","mod_quiz"),messages[0],(0,_str.getString)("reopenattempt","mod_quiz"),{triggerElement:e.target}),await(0,_ajax.call)([{methodname:"mod_quiz_reopen_attempt",args:{attemptid:attemptId}}])[0],window.location=M.cfg.wwwroot+e.target.dataset.afterActionUrl}catch(error){if("modal-save-cancel:cancel"===error.type)return;await(0,_notification.exception)(error)}};_exports.init=()=>{document.addEventListener("click",reopenButtonClicked)}}));
//# sourceMappingURL=reopen_attempt_ui.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"reopen_attempt_ui.min.js","sources":["../src/reopen_attempt_ui.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 <http://www.gnu.org/licenses/>.\n\n/**\n * This module has the code to make the Re-open attempt button work, if present.\n *\n * That is, it looks for buttons with HTML like\n * &lt;button type=\"button\" data-action=\"reopen-attempt\" data-attempt-id=\"227000\" data-after-action-url=\"/mod/quiz/report.php\">\n * and if that is clicked, it first shows an 'Are you sure' pop-up, and if they are sure,\n * the attempt is re-opened, and then the page reloads.\n *\n * @module mod_quiz/reopen_attempt_ui\n * @copyright 2023 The Open University\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {exception as displayException} from 'core/notification';\nimport {call as fetchMany} from 'core/ajax';\nimport {getString} from 'core/str';\nimport {saveCancelPromise} from 'core/notification';\n\n/**\n * Handle a click if it is on one of our buttons.\n *\n * @param {MouseEvent} e the click event.\n */\nconst reopenButtonClicked = async(e) => {\n if (!(e.target instanceof HTMLElement) || !e.target.matches('button[data-action=\"reopen-attempt\"]')) {\n return;\n }\n\n e.preventDefault();\n const attemptId = e.target.dataset.attemptId;\n\n try {\n // We fetch the confirmation message from the server now, so the message is based\n // on the latest state of the attempt, rather than when the containing page loaded.\n const messages = fetchMany([{\n methodname: 'mod_quiz_get_reopen_attempt_confirmation',\n args: {\n \"attemptid\": attemptId,\n },\n }]);\n\n await saveCancelPromise(\n getString('reopenattemptareyousuretitle', 'mod_quiz'),\n messages[0],\n getString('reopenattempt', 'mod_quiz'),\n {triggerElement: e.target},\n );\n\n await (fetchMany([{\n methodname: 'mod_quiz_reopen_attempt',\n args: {\n \"attemptid\": attemptId,\n },\n }])[0]);\n window.location = M.cfg.wwwroot + e.target.dataset.afterActionUrl;\n\n } catch (error) {\n if (error.type === 'modal-save-cancel:cancel') {\n // User clicked Cancel, so do nothing.\n return;\n }\n await displayException(error);\n }\n};\n\nexport const init = () => {\n document.addEventListener('click', reopenButtonClicked);\n};\n"],"names":["reopenButtonClicked","async","e","target","HTMLElement","matches","preventDefault","attemptId","dataset","messages","methodname","args","triggerElement","window","location","M","cfg","wwwroot","afterActionUrl","error","type","document","addEventListener"],"mappings":";;;;;;;;;;;;;MAsCMA,oBAAsBC,MAAAA,SAClBC,EAAEC,kBAAkBC,aAAiBF,EAAEC,OAAOE,QAAQ,gDAI5DH,EAAEI,uBACIC,UAAYL,EAAEC,OAAOK,QAAQD,oBAKzBE,UAAW,cAAU,CAAC,CACxBC,WAAY,2CACZC,KAAM,WACWJ,oBAIf,oCACF,kBAAU,+BAAgC,YAC1CE,SAAS,IACT,kBAAU,gBAAiB,YAC3B,CAACG,eAAgBV,EAAEC,eAGhB,cAAU,CAAC,CACdO,WAAY,0BACZC,KAAM,WACWJ,cAEjB,GACJM,OAAOC,SAAWC,EAAEC,IAAIC,QAAUf,EAAEC,OAAOK,QAAQU,eAErD,MAAOC,UACc,6BAAfA,MAAMC,kBAIJ,2BAAiBD,uBAIX,KAChBE,SAASC,iBAAiB,QAAStB"}
+10
View File
@@ -0,0 +1,10 @@
define("mod_quiz/repaginate",["exports","core/modal"],(function(_exports,_modal){var obj;
/**
* Initialise the repaginate dialogue on quiz editing page.
*
* @module mod_quiz/repaginate
* @copyright 2019 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_modal=(obj=_modal)&&obj.__esModule?obj:{default:obj};_exports.init=()=>{document.addEventListener("click",(event=>{const repaginateCommand=event.target.closest("#repaginatecommand");repaginateCommand&&(event.preventDefault(),_modal.default.create({title:repaginateCommand.dataset.header,body:repaginateCommand.dataset.form,large:!1,show:!0}))}))}}));
//# sourceMappingURL=repaginate.min.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"repaginate.min.js","sources":["../src/repaginate.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 <http://www.gnu.org/licenses/>.\n\n/**\n * Initialise the repaginate dialogue on quiz editing page.\n *\n * @module mod_quiz/repaginate\n * @copyright 2019 The Open University\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport Modal from 'core/modal';\n\nexport const init = () => {\n document.addEventListener('click', (event) => {\n const repaginateCommand = event.target.closest('#repaginatecommand');\n if (!repaginateCommand) {\n return;\n }\n\n event.preventDefault();\n Modal.create({\n title: repaginateCommand.dataset.header,\n body: repaginateCommand.dataset.form,\n large: false,\n show: true,\n });\n });\n};\n"],"names":["document","addEventListener","event","repaginateCommand","target","closest","preventDefault","create","title","dataset","header","body","form","large","show"],"mappings":";;;;;;;sJAwBoB,KAChBA,SAASC,iBAAiB,SAAUC,cAC1BC,kBAAoBD,MAAME,OAAOC,QAAQ,sBAC1CF,oBAILD,MAAMI,gCACAC,OAAO,CACTC,MAAOL,kBAAkBM,QAAQC,OACjCC,KAAMR,kBAAkBM,QAAQG,KAChCC,OAAO,EACPC,MAAM"}
+11
View File
@@ -0,0 +1,11 @@
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.
*
* @module mod_quiz/submission_confirmation
* @copyright 2022 Huong Nguyen <huongnv13@gmail.com>
* @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,_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.getString)("submission_confirmation","quiz"),_templates.default.render(TEMPLATES_submissionConfirmation,{hasunanswered:unAnsweredQuestions>0,totalunanswered:unAnsweredQuestions}),(0,_str.getString)("submitallandfinish","quiz")),submitAction.closest(SELECTOR_attemptSubmitForm).submit()}catch{return}}))})(unAnsweredQuestions)}}));
//# sourceMappingURL=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 <http://www.gnu.org/licenses/>.\n\n/**\n * A javascript module to handle submission confirmation for quiz.\n *\n * @module mod_quiz/submission_confirmation\n * @copyright 2022 Huong Nguyen <huongnv13@gmail.com>\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 {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,kBAAU,0BAA2B,QACrCC,mBAAUC,OAAOZ,iCAAkC,CAC/Ca,cAAeZ,oBAAsB,EACrCa,gBAAiBb,uBAErB,kBAAU,qBAAsB,SAIpCG,aAAaW,QAAQhB,4BAA4BiB,SACnD,kBAiBVC,CAAuBhB"}
@@ -0,0 +1,12 @@
define("mod_quiz/update_random_question_filter_condition",["exports","core/ajax","core/notification"],(function(_exports,_ajax,_notification){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}
/**
* Event handling for the edit random question form.
*
* Dynamically saves the new filter condition before navigating back to the quiz question list.
*
* @module mod_quiz/update_random_question_filter_condition
* @author 2022 <nathannguyen@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_ajax=_interopRequireDefault(_ajax),_notification=_interopRequireDefault(_notification);_exports.init=()=>{const SELECTORS_QUESTION_BANK_CONTAINER="#questionbank_container",SELECTORS_FORM_ELEMENT="#update_filter_condition_form",SELECTORS_UPDATE_BUTTON='[name="update"]',SELECTORS_MESSAGE_INPUT='[name="message"]',SELECTORS_FILTER_CONDITION_ELEMENT="[data-filtercondition]",questionBank=document.querySelector(SELECTORS_QUESTION_BANK_CONTAINER),form=document.querySelector(SELECTORS_FORM_ELEMENT);form.querySelector(SELECTORS_UPDATE_BUTTON).addEventListener("click",(async e=>{var _form$dataset,_form$dataset2,_questionBank$querySe;e.preventDefault();const request={methodname:"mod_quiz_update_filter_condition",args:{cmid:null===(_form$dataset=form.dataset)||void 0===_form$dataset?void 0:_form$dataset.cmid,slotid:null===(_form$dataset2=form.dataset)||void 0===_form$dataset2?void 0:_form$dataset2.slotid,filtercondition:null===(_questionBank$querySe=questionBank.querySelector(SELECTORS_FILTER_CONDITION_ELEMENT).dataset)||void 0===_questionBank$querySe?void 0:_questionBank$querySe.filtercondition}};try{const response=await _ajax.default.call([request])[0];form.querySelector(SELECTORS_MESSAGE_INPUT).value=response.message,form.submit()}catch(e){_notification.default.exception(e)}}))}}));
//# sourceMappingURL=update_random_question_filter_condition.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"update_random_question_filter_condition.min.js","sources":["../src/update_random_question_filter_condition.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 <http://www.gnu.org/licenses/>.\n\n/**\n * Event handling for the edit random question form.\n *\n * Dynamically saves the new filter condition before navigating back to the quiz question list.\n *\n * @module mod_quiz/update_random_question_filter_condition\n * @author 2022 <nathannguyen@catalyst-au.net>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport ajax from 'core/ajax';\nimport Notification from 'core/notification';\n\nexport const init = () => {\n const SELECTORS = {\n QUESTION_BANK_CONTAINER: '#questionbank_container',\n FORM_ELEMENT: '#update_filter_condition_form',\n UPDATE_BUTTON: '[name=\"update\"]',\n CANCEL_BUTTON: '[name=\"cancel\"]',\n MESSAGE_INPUT: '[name=\"message\"]',\n FILTER_CONDITION_ELEMENT: '[data-filtercondition]',\n };\n\n const questionBank = document.querySelector(SELECTORS.QUESTION_BANK_CONTAINER);\n const form = document.querySelector(SELECTORS.FORM_ELEMENT);\n const updateButton = form.querySelector(SELECTORS.UPDATE_BUTTON);\n\n updateButton.addEventListener(\"click\", async(e) => {\n e.preventDefault();\n const request = {\n methodname: 'mod_quiz_update_filter_condition',\n args: {\n cmid: form.dataset?.cmid,\n slotid: form.dataset?.slotid,\n filtercondition: questionBank.querySelector(SELECTORS.FILTER_CONDITION_ELEMENT).dataset?.filtercondition,\n }\n };\n try {\n const response = await ajax.call([request])[0];\n const messageInput = form.querySelector(SELECTORS.MESSAGE_INPUT);\n messageInput.value = response.message;\n form.submit();\n } catch (e) {\n Notification.exception(e);\n }\n });\n\n};\n"],"names":["SELECTORS","questionBank","document","querySelector","form","addEventListener","async","e","preventDefault","request","methodname","args","cmid","dataset","_form$dataset","slotid","_form$dataset2","filtercondition","_questionBank$querySe","response","ajax","call","value","message","submit","exception"],"mappings":";;;;;;;;;wLA4BoB,WACVA,kCACuB,0BADvBA,uBAEY,gCAFZA,wBAGa,kBAHbA,wBAKa,mBALbA,mCAMwB,yBAGxBC,aAAeC,SAASC,cAAcH,mCACtCI,KAAOF,SAASC,cAAcH,wBACfI,KAAKD,cAAcH,yBAE3BK,iBAAiB,SAASC,MAAAA,2DACnCC,EAAEC,uBACIC,QAAU,CACZC,WAAY,mCACZC,KAAM,CACFC,2BAAMR,KAAKS,wCAALC,cAAcF,KACpBG,8BAAQX,KAAKS,yCAALG,eAAcD,OACtBE,8CAAiBhB,aAAaE,cAAcH,oCAAoCa,gDAA/DK,sBAAwED,4BAIvFE,eAAiBC,cAAKC,KAAK,CAACZ,UAAU,GACvBL,KAAKD,cAAcH,yBAC3BsB,MAAQH,SAASI,QAC9BnB,KAAKoB,SACP,MAAOjB,yBACQkB,UAAUlB"}
+92
View File
@@ -0,0 +1,92 @@
// 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 <http://www.gnu.org/licenses/>.
/**
* Contain the logic for the add random question modal.
*
* @module mod_quiz/add_question_modal
* @copyright 2023 Andrew Lyons <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import Modal from 'core/modal';
export default class AddQuestionModal extends Modal {
configure(modalConfig) {
// Add question modals are always large.
modalConfig.large = true;
// Always show on creation.
modalConfig.show = true;
modalConfig.removeOnClose = true;
// Apply question modal configuration.
this.setContextId(modalConfig.contextId);
this.setAddOnPageId(modalConfig.addOnPage);
// Apply standard configuration.
super.configure(modalConfig);
}
constructor(root) {
super(root);
this.contextId = null;
this.addOnPageId = null;
}
/**
* Save the Moodle context id that the question bank is being
* rendered in.
*
* @method setContextId
* @param {Number} id
*/
setContextId(id) {
this.contextId = id;
}
/**
* Retrieve the saved Moodle context id.
*
* @method getContextId
* @return {Number}
*/
getContextId() {
return this.contextId;
}
/**
* Set the id of the page that the question should be added to
* when the user clicks the add to quiz link.
*
* @method setAddOnPageId
* @param {Number} id
*/
setAddOnPageId(id) {
this.addOnPageId = id;
}
/**
* Returns the saved page id for the question to be added to.
*
* @method getAddOnPageId
* @return {Number}
*/
getAddOnPageId() {
return this.addOnPageId;
}
}
+217
View File
@@ -0,0 +1,217 @@
// 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 <http://www.gnu.org/licenses/>.
/**
* JavaScript for the add_random_form class.
*
* @module mod_quiz/add_random_form
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(
[
'jquery',
'mod_quiz/random_question_form_preview'
],
function(
$,
RandomQuestionFormPreview
) {
// Wait 2 seconds before reloading the question set just in case
// the user is still changing the criteria.
var RELOAD_DELAY = 2000;
var SELECTORS = {
PREVIEW_CONTAINER: '[data-region="random-question-preview-container"]',
CATEGORY_FORM_ELEMENT: '[name="category"]',
SUBCATEGORY_FORM_ELEMENT: '[name="includesubcategories"]',
TAG_IDS_FORM_ELEMENT: '[name="fromtags[]"]'
};
/**
* Get the selected category value from the form.
*
* @param {jquery} form The form element.
* @return {string} The category value.
*/
var getCategorySelectValue = function(form) {
return form.find(SELECTORS.CATEGORY_FORM_ELEMENT).val();
};
/**
* Get the category id from the form.
*
* @param {jquery} form The form element.
* @return {string} The category id.
*/
var getCategoryId = function(form) {
// The value string is the category id and category context id joined
// by a comma.
var valueString = getCategorySelectValue(form);
// Split the two ids.
var values = valueString.split(',');
// Return just the category id.
return values[0];
};
/**
* Check if a top level category is selected in the form.
*
* @param {jquery} form The form element.
* @param {string[]} topCategories List of top category values (matching the select box values)
* @return {bool}
*/
var isTopLevelCategorySelected = function(form, topCategories) {
var selectedValue = getCategorySelectValue(form);
return (topCategories.indexOf(selectedValue) > -1);
};
/**
* Check if the form indicates we should include include subcategories in
* the filter.
*
* @param {jquery} form The form element.
* @param {string[]} topCategories List of top category values (matching the select box values)
* @return {bool}
*/
var shouldIncludeSubcategories = function(form, topCategories) {
if (isTopLevelCategorySelected(form, topCategories)) {
return true;
} else {
return form.find(SELECTORS.SUBCATEGORY_FORM_ELEMENT).is(':checked');
}
};
/**
* Get the tag ids for the selected tags in the form.
*
* @param {jquery} form The form element.
* @return {string[]} The tag ids.
*/
var getTagIds = function(form) {
var values = form.find(SELECTORS.TAG_IDS_FORM_ELEMENT).val();
return values.map(function(value) {
// The tag element value is the tag id and tag name joined
// by a comma. So we need to split them to get the tag id.
var parts = value.split(',');
return parts[0];
});
};
/**
* Reload the preview section with a new set of filters.
*
* @param {jquery} form The form element.
* @param {int} contextId The current context id.
* @param {string[]} topCategories List of top category values (matching the select box values)
*/
var reloadQuestionPreview = function(form, contextId, topCategories) {
var previewContainer = form.find(SELECTORS.PREVIEW_CONTAINER);
RandomQuestionFormPreview.reload(
previewContainer,
getCategoryId(form),
shouldIncludeSubcategories(form, topCategories),
getTagIds(form),
contextId
);
};
/**
* Is this an element we're interested in listening to changes on.
*
* @param {jquery} element The element to check.
* @return {bool}
*/
var isInterestingElement = function(element) {
if (element.closest(SELECTORS.CATEGORY_FORM_ELEMENT).length > 0) {
return true;
}
if (element.closest(SELECTORS.SUBCATEGORY_FORM_ELEMENT).length > 0) {
return true;
}
if (element.closest(SELECTORS.TAG_IDS_FORM_ELEMENT).length > 0) {
return true;
}
return false;
};
/**
* Listen for changes to any of the interesting elements and reload the form
* preview with the new filter values if they are changed.
*
* The reload is delayed for a small amount of time (see RELOAD_DELAY) in case
* the user is actively editing the form. This allows us to avoid having to
* send multiple requests to the server on each change.
*
* Instead we can just send a single request when the user appears to have
* finished editing the form.
*
* @param {jquery} form The form element.
* @param {int} contextId The current context id.
* @param {string[]} topCategories List of top category values (matching the select box values)
*/
var addEventListeners = function(form, contextId, topCategories) {
var reloadTimerId = null;
const tagsFilter = form.find(SELECTORS.TAG_IDS_FORM_ELEMENT);
form.add(tagsFilter).on('change', function(e) {
// Only reload the preview when elements that will change the result
// are modified.
if (!isInterestingElement($(e.target))) {
return;
}
// Show the loading icon to let the user know that the preview
// will be updated after their actions.
RandomQuestionFormPreview.showLoadingIcon(form);
if (reloadTimerId) {
// Reset the timer each time the form is modified.
clearTimeout(reloadTimerId);
}
// Don't immediately reload the question preview section just
// in case the user is still modifying the form. We don't want to
// spam reload requests.
reloadTimerId = setTimeout(function() {
reloadQuestionPreview(form, contextId, topCategories);
}, RELOAD_DELAY);
});
};
/**
* Trigger the first load of the preview section and then listen for modifications
* to the form to reload the preview with new filter values.
*
* @param {jquery} formId The form element id.
* @param {int} contextId The current context id.
* @param {string[]} topCategories List of top category values (matching the select box values)
* @param {bool} isTagsEnabled Whether tags feature is enabled or not.
*/
var init = function(formId, contextId, topCategories, isTagsEnabled) {
if (isTagsEnabled == true) {
var form = $('#' + formId);
reloadQuestionPreview(form, contextId, topCategories, isTagsEnabled);
addEventListeners(form, contextId, topCategories, isTagsEnabled);
}
};
return {
init: init
};
});
+529
View File
@@ -0,0 +1,529 @@
// 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 <http://www.gnu.org/licenses/>.
/**
* JavaScript for managing multiple grade items for a quiz.
*
* @module mod_quiz/edit_multiple_grades
* @copyright 2023 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import {call as fetchMany} from 'core/ajax';
import MoodleConfig from 'core/config';
import {addIconToContainer} from 'core/loadingicon';
import Notification from 'core/notification';
import Pending from 'core/pending';
import {get_string as getString} from 'core/str';
import {render as renderTemplate} from 'core/templates';
import {replaceNode} from 'core/templates';
/**
* @type {Object} selectors used in this code.
*/
const SELECTORS = {
'addGradeItemButton': '#mod_quiz-add_grade_item',
'autoSetupButton': '#mod_quiz-grades_auto_setup',
'editingPageContents': '#edit_grading_page-contents',
'gradeItemList': 'table#mod_quiz-grade-item-list',
'gradeItemSelect': 'select[data-slot-id]',
'gradeItemSelectId': (id) => 'select#grade-item-choice-' + id,
'gradeItemTr': 'table#mod_quiz-grade-item-list tr[data-quiz-grade-item-id]',
'inplaceEditable': 'span.inplaceeditable',
'inplaceEditableOn': 'span.inplaceeditable.inplaceeditingon',
'resetAllButton': '#mod_quiz-grades_reset_all',
'slotList': 'table#mod_quiz-slot-list',
'updateGradeItemLink': (id) => 'tr[data-quiz-grade-item-id="' + id + '"] .quickeditlink',
};
/**
* Call the Ajax service to create a quiz grade item.
*
* @param {Number} quizId id of the quiz to update.
* @returns {Promise<Object>} a promise that resolves to the template context required to re-render the page.
*/
const createGradeItem = (
quizId,
) => callServiceAndReturnRenderingData({
methodname: 'mod_quiz_create_grade_items',
args: {
quizid: quizId,
quizgradeitems: [{name: ''}],
}
});
/**
* Call the Ajax service to update a quiz grade item.
*
* @param {Number} quizId id of the quiz to update.
* @param {Number} gradeItemId id of the grade item to update.
* @param {String} newName the new name to set.
* @return {Promise} Promise that resolves to the context required to re-render the page.
*/
const updateGradeItem = (
quizId,
gradeItemId,
newName
) => callServiceAndReturnRenderingData({
methodname: 'mod_quiz_update_grade_items',
args: {
quizid: quizId,
quizgradeitems: [{id: gradeItemId, name: newName}],
}
});
/**
* Call the Ajax service to delete a quiz grade item.
*
* @param {Number} quizId id of the quiz to update.
* @param {Number} gradeItemId id of the grade item to delete.
* @return {Promise} Promise that resolves to the context required to re-render the page.
*/
const deleteGradeItem = (
quizId,
gradeItemId
) => callServiceAndReturnRenderingData({
methodname: 'mod_quiz_delete_grade_items',
args: {
quizid: quizId,
quizgradeitems: [{id: gradeItemId}],
}
});
/**
* Call the Ajax service to update the quiz grade item used by a slot.
*
* @param {Number} quizId id of the quiz to update.
* @param {Number} slotId id of the slot to update.
* @param {Number|null} gradeItemId new grade item ot set, or null to un-set.
* @return {Promise} Promise that resolves to the context required to re-render the page.
*/
const updateSlotGradeItem = (
quizId,
slotId,
gradeItemId
) => callServiceAndReturnRenderingData({
methodname: 'mod_quiz_update_slots',
args: {
quizid: quizId,
slots: [{id: slotId, quizgradeitemid: gradeItemId}],
}
});
/**
* Call the Ajax service to setup one grade item for each quiz section.
*
* @param {Number} quizId id of the quiz to update.
* @return {Promise} Promise that resolves to the context required to re-render the page.
*/
const autoSetupGradeItems = (
quizId
) => callServiceAndReturnRenderingData({
methodname: 'mod_quiz_create_grade_item_per_section',
args: {
quizid: quizId
}
});
/**
* Make a web service call, and also call mod_quiz_get_edit_grading_page_data to get the date to re-render the page.
*
* @param {Object} methodCall a web service call to pass to fetchMany. Must include methodCall.args.quizid.
* @returns {Promise<Object>} a promise that resolves to the template context required to re-render the page.
*/
const callServiceAndReturnRenderingData = (methodCall) => callServicesAndReturnRenderingData([methodCall]);
/**
* Make a web service call, and also call mod_quiz_get_edit_grading_page_data to get the date to re-render the page.
*
* @param {Object[]} methodCalls web service calls to pass to fetchMany. Must include methodCalls[0].args.quizid.
* @returns {Promise<Object>} a promise that resolves to the template context required to re-render the page.
*/
const callServicesAndReturnRenderingData = (methodCalls) => {
methodCalls.push({
methodname: 'mod_quiz_get_edit_grading_page_data',
args: {
quizid: methodCalls[0].args.quizid,
}
});
return Promise.all(fetchMany(methodCalls))
.then(results => JSON.parse(results.at(-1)));
};
/**
* Handle click events on the delete icon.
*
* @param {Event} e click event.
*/
const handleGradeItemDelete = (e) => {
e.preventDefault();
const pending = new Pending('delete-quiz-grade-item');
const tableCell = e.target.closest('td');
addIconToContainer(tableCell, pending);
const tableRow = tableCell.closest('tr');
const quizId = tableRow.closest('table').dataset.quizId;
const gradeItemId = tableRow.dataset.quizGradeItemId;
let nextItemToFocus;
if (tableRow.nextElementSibling) {
nextItemToFocus = SELECTORS.updateGradeItemLink(tableRow.nextElementSibling.dataset.quizGradeItemId);
} else {
nextItemToFocus = SELECTORS.addGradeItemButton;
}
deleteGradeItem(quizId, gradeItemId)
.then(reRenderPage)
.then(() => {
pending.resolve();
document.querySelector(nextItemToFocus).focus();
})
.catch(Notification.exception);
};
/**
*
* @param {HTMLElement} editableSpan the editable to turn off.
*/
const stopEditingGadeItem = (editableSpan) => {
editableSpan.innerHTML = editableSpan.dataset.oldContent;
delete editableSpan.dataset.oldContent;
editableSpan.classList.remove('inplaceeditingon');
editableSpan.querySelector('[data-action-edit]').focus();
};
/**
* Handle click events on the start rename icon.
*
* @param {Event} e click event.
*/
const handleGradeItemEditStart = (e) => {
e.preventDefault();
const pending = new Pending('edit-quiz-grade-item-start');
const editableSpan = e.target.closest(SELECTORS.inplaceEditable);
document.querySelectorAll(SELECTORS.inplaceEditableOn).forEach(stopEditingGadeItem);
editableSpan.dataset.oldContent = editableSpan.innerHTML;
getString('edittitleinstructions')
.then((instructions) => {
const uniqueId = 'gi-edit-input-' + editableSpan.closest('tr').dataset.quizGradeItemId;
editableSpan.innerHTML = '<span class="editinstructions">' + instructions + '</span>' +
'<label class="sr-only" for="' + uniqueId + '">' + editableSpan.dataset.editLabel + '</label>' +
'<input type="text" id="' + uniqueId + '" value="' + editableSpan.dataset.rawName +
'" class="ignoredirty form-control w-100">';
const inputElement = editableSpan.querySelector('input');
inputElement.focus();
inputElement.select();
editableSpan.classList.add('inplaceeditingon');
pending.resolve();
return null;
})
.catch(Notification.exception);
};
/**
* Handle key down in the editable.
*
* @param {Event} e key event.
*/
const handleGradeItemKeyDown = (e) => {
if (e.keyCode !== 13) {
return;
}
const editableSpan = e.target.closest(SELECTORS.inplaceEditableOn);
// Check this click is on a relevant element.
if (!editableSpan || !editableSpan.closest(SELECTORS.gradeItemList)) {
return;
}
e.preventDefault();
const pending = new Pending('edit-quiz-grade-item-save');
const newName = editableSpan.querySelector('input').value;
const tableCell = e.target.closest('th');
addIconToContainer(tableCell);
const tableRow = tableCell.closest('tr');
const quizId = tableRow.closest('table').dataset.quizId;
const gradeItemId = tableRow.dataset.quizGradeItemId;
updateGradeItem(quizId, gradeItemId, newName)
.then(reRenderPage)
.then(() => {
pending.resolve();
document.querySelector(SELECTORS.updateGradeItemLink(gradeItemId)).focus({'focusVisible': true});
})
.catch(Notification.exception);
};
/**
* Replace the contents of the page with the page re-rendered from the provided data, once that promise resolves.
*
* @param {Object} editGradingPageData the template context data required to re-render the page.
* @returns {Promise<void>} a promise that will resolve when the page is updated.
*/
const reRenderPage = (editGradingPageData) =>
renderTemplate('mod_quiz/edit_grading_page', editGradingPageData)
.then((html, js) => replaceNode(document.querySelector(SELECTORS.editingPageContents), html, js || ''));
/**
* Handle key up in the editable.
*
* @param {Event} e key event.
*/
const handleGradeItemKeyUp = (e) => {
if (e.keyCode !== 27) {
return;
}
const editableSpan = e.target.closest(SELECTORS.inplaceEditableOn);
// Check this click is on a relevant element.
if (!editableSpan || !editableSpan.closest(SELECTORS.gradeItemList)) {
return;
}
e.preventDefault();
stopEditingGadeItem(editableSpan);
};
/**
* Handle focus out of the editable.
*
* @param {Event} e event.
*/
const handleGradeItemFocusOut = (e) => {
if (MoodleConfig.behatsiterunning) {
// Behat triggers focusout too often so ignore.
return;
}
const editableSpan = e.target.closest(SELECTORS.inplaceEditableOn);
// Check this click is on a relevant element.
if (!editableSpan || !editableSpan.closest(SELECTORS.gradeItemList)) {
return;
}
e.preventDefault();
stopEditingGadeItem(editableSpan);
};
/**
* Handle when the selected grade item for a slot is changed.
*
* @param {Event} e event.
*/
const handleSlotGradeItemChanged = (e) => {
const select = e.target.closest(SELECTORS.gradeItemSelect);
// Check this click is on a relevant element.
if (!select || !select.closest(SELECTORS.slotList)) {
return;
}
e.preventDefault();
const pending = new Pending('edit-slot-grade-item-updated');
const slotId = select.dataset.slotId;
const newGradeItemId = select.value ? select.value : null;
const tableCell = e.target.closest('td');
addIconToContainer(tableCell, pending);
const quizId = tableCell.closest('table').dataset.quizId;
updateSlotGradeItem(quizId, slotId, newGradeItemId)
.then(reRenderPage)
.then(() => {
pending.resolve();
document.querySelector(SELECTORS.gradeItemSelectId(slotId)).focus();
})
.catch(Notification.exception);
};
/**
* Handle clicks in the table the shows the grade items.
*
* @param {Event} e click event.
*/
const handleGradeItemClick = (e) => {
const link = e.target.closest('a');
// Check this click is on a relevant element.
if (!link || !link.closest(SELECTORS.gradeItemList)) {
return;
}
if (link.dataset.actionDelete) {
handleGradeItemDelete(e);
}
if (link.dataset.actionEdit) {
handleGradeItemEditStart(e);
}
};
/**
* Handle clicks on the buttons.
*
* @param {Event} e click event.
*/
const handleButtonClick = (e) => {
if (e.target.closest(SELECTORS.addGradeItemButton)) {
handleAddGradeItemClick(e);
}
if (e.target.closest(SELECTORS.autoSetupButton)) {
handleAutoSetup(e);
}
if (e.target.closest(SELECTORS.resetAllButton)) {
handleResetAllClick(e);
}
};
/**
* Handle clicks on the 'Add grade item' button.
*
* @param {Event} e click event.
*/
const handleAddGradeItemClick = (e) => {
e.preventDefault();
const pending = new Pending('create-quiz-grade-item');
addIconToContainer(e.target.parentNode, pending);
const quizId = e.target.dataset.quizId;
createGradeItem(quizId)
.then(reRenderPage)
.then(() => {
pending.resolve();
document.querySelector(SELECTORS.addGradeItemButton).focus();
})
.catch(Notification.exception);
};
/**
* Handle clicks on the reset button - show a confirmation.
*
* @param {Event} e click event.
*/
const handleAutoSetup = (e) => {
e.preventDefault();
const pending = new Pending('setup-quiz-grade-items');
const quizId = e.target.dataset.quizId;
autoSetupGradeItems(quizId)
.then(reRenderPage)
.then(() => {
pending.resolve();
document.querySelector(SELECTORS.resetAllButton).focus();
})
.catch(Notification.exception);
};
/**
* Handle clicks on the reset button - show a confirmation.
*
* @param {Event} e click event.
*/
const handleResetAllClick = (e) => {
e.preventDefault();
const button = e.target;
Notification.deleteCancelPromise(
getString('gradeitemsremoveallconfirm', 'quiz'),
getString('gradeitemsremoveallmessage', 'quiz'),
getString('reset'),
button
).then(() => reallyResetAll(button))
.catch(() => button.focus());
};
/**
* Really reset all if the confirmation is OKed.
*
* @param {HTMLElement} button the reset button.
*/
const reallyResetAll = (button) => {
const pending = new Pending('reset-quiz-grading');
addIconToContainer(button.parentNode, pending);
const quizId = button.dataset.quizId;
let methodCalls = [];
// Call to clear any assignments of grade items to slots (if required).
const slotResets = [...document.querySelectorAll(SELECTORS.gradeItemSelect)].map(
(select) => ({
id: select.dataset.slotId,
quizgradeitemid: 0,
}));
if (slotResets.length) {
methodCalls.push({
methodname: 'mod_quiz_update_slots',
args: {
quizid: quizId,
slots: slotResets
}
});
}
// Request to delete all the grade items.
methodCalls.push({
methodname: 'mod_quiz_delete_grade_items',
args: {
quizid: quizId,
quizgradeitems: [...document.querySelectorAll(SELECTORS.gradeItemTr)].map((tr) => {
return {id: tr.dataset.quizGradeItemId};
})
}
});
callServicesAndReturnRenderingData(methodCalls)
.then(reRenderPage)
.then(() => {
pending.resolve();
document.querySelector(SELECTORS.addGradeItemButton).focus();
})
.catch(Notification.exception);
};
/**
* Replace the container with a new version.
*/
const registerEventListeners = () => {
document.body.addEventListener('click', handleGradeItemClick);
document.body.addEventListener('keydown', handleGradeItemKeyDown);
document.body.addEventListener('keyup', handleGradeItemKeyUp);
document.body.addEventListener('focusout', handleGradeItemFocusOut);
document.body.addEventListener('click', handleButtonClick);
document.body.addEventListener('change', handleSlotGradeItemChanged);
};
/**
* Entry point.
*/
export const init = () => {
registerEventListeners();
};
@@ -0,0 +1,373 @@
// 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 <http://www.gnu.org/licenses/>.
/**
* Contain the logic for the add random question modal.
*
* @module mod_quiz/modal_add_random_question
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import $ from 'jquery';
import Modal from './add_question_modal';
import * as Notification from 'core/notification';
import * as Fragment from 'core/fragment';
import * as Templates from 'core/templates';
import * as FormChangeChecker from 'core_form/changechecker';
import {call as fetchMany} from 'core/ajax';
import Pending from 'core/pending';
const SELECTORS = {
EXISTING_CATEGORY_CONTAINER: '[data-region="existing-category-container"]',
EXISTING_CATEGORY_TAB: '#id_existingcategoryheader',
NEW_CATEGORY_CONTAINER: '[data-region="new-category-container"]',
NEW_CATEGORY_TAB: '#id_newcategoryheader',
TAB_CONTENT: '[data-region="tab-content"]',
ADD_ON_PAGE_FORM_ELEMENT: '[name="addonpage"]',
ADD_RANDOM_BUTTON: 'input[type="submit"][name="addrandom"]',
ADD_NEW_CATEGORY_BUTTON: 'input[type="submit"][name="newcategory"]',
SUBMIT_BUTTON_ELEMENT: 'input[type="submit"][name="addrandom"], input[type="submit"][name="newcategory"]',
FORM_HEADER: 'legend',
SELECT_NUMBER_TO_ADD: '#menurandomcount',
NEW_CATEGORY_ELEMENT: '#categoryname',
PARENT_CATEGORY_ELEMENT: '#parentcategory',
FILTER_CONDITION_ELEMENT: '[data-filtercondition]',
FORM_ELEMENT: '#add_random_question_form',
MESSAGE_INPUT: '[name="message"]',
};
export default class ModalAddRandomQuestion extends Modal {
static TYPE = 'mod_quiz-quiz-add-random-question';
static TEMPLATE = 'mod_quiz/modal_add_random_question';
/**
* Create the add random question modal.
*
* @param {Number} contextId Current context id.
* @param {string} category Category id and category context id comma separated.
* @param {string} returnUrl URL to return to after form submission.
* @param {Number} cmid Current course module id.
* @param {boolean} showNewCategory Display the New category tab when selecting random questions.
*/
static init(contextId, category, returnUrl, cmid, showNewCategory = true) {
const selector = '.menu [data-action="addarandomquestion"]';
document.addEventListener('click', (e) => {
const trigger = e.target.closest(selector);
if (!trigger) {
return;
}
e.preventDefault();
ModalAddRandomQuestion.create({
contextId,
category,
returnUrl,
cmid,
title: trigger.dataset.header,
addOnPage: trigger.dataset.addonpage,
templateContext: {
hidden: showNewCategory,
},
});
});
}
/**
* Constructor for the Modal.
*
* @param {object} root The root jQuery element for the modal
*/
constructor(root) {
super(root);
this.category = null;
this.returnUrl = null;
this.cmid = null;
this.loadedForm = false;
}
configure(modalConfig) {
modalConfig.removeOnClose = true;
this.setCategory(modalConfig.category);
this.setReturnUrl(modalConfig.returnUrl);
this.setCMID(modalConfig.cmid);
super.configure(modalConfig);
}
/**
* Set the id of the page that the question should be added to
* when the user clicks the add to quiz link.
*
* @method setAddOnPageId
* @param {int} id
*/
setAddOnPageId(id) {
super.setAddOnPageId(id);
this.getBody().find(SELECTORS.ADD_ON_PAGE_FORM_ELEMENT).val(id);
}
/**
* Set the category for this form. The category is a comma separated
* category id and category context id.
*
* @method setCategory
* @param {string} category
*/
setCategory(category) {
this.category = category;
}
/**
* Returns the saved category.
*
* @method getCategory
* @return {string}
*/
getCategory() {
return this.category;
}
/**
* Set the return URL for the form.
*
* @method setReturnUrl
* @param {string} url
*/
setReturnUrl(url) {
this.returnUrl = url;
}
/**
* Returns the return URL for the form.
*
* @method getReturnUrl
* @return {string}
*/
getReturnUrl() {
return this.returnUrl;
}
/**
* Set the course module id for the form.
*
* @method setCMID
* @param {Number} id
*/
setCMID(id) {
this.cmid = id;
}
/**
* Returns the course module id for the form.
*
* @method getCMID
* @return {Number}
*/
getCMID() {
return this.cmid;
}
/**
* Moves a given form element inside (a child of) a given tab element.
*
* Hides the 'legend' (e.g. header) element of the form element because the
* tab has the name.
*
* Moves the submit button into a footer element at the bottom of the form
* element for styling purposes.
*
* @method moveContentIntoTab
* @param {jquery} tabContent The form element to move into the tab.
* @param {jquey} tabElement The tab element for the form element to move into.
*/
moveContentIntoTab(tabContent, tabElement) {
// Hide the header because the tabs show us which part of the form we're
// looking at.
tabContent.find(SELECTORS.FORM_HEADER).addClass('hidden');
// Move the element inside a tab.
tabContent.wrap(tabElement);
}
/**
* Empty the tab content container and move all tabs from the form into the
* tab container element.
*
* @method moveTabsIntoTabContent
* @param {jquery} form The form element.
*/
moveTabsIntoTabContent(form) {
// Empty it to remove the loading icon.
const tabContent = this.getBody().find(SELECTORS.TAB_CONTENT).empty();
// Make sure all tabs are inside the tab content element.
form.find('[role="tabpanel"]').wrapAll(tabContent);
}
/**
* Make sure all of the tabs have a cancel button in their fotter to sit along
* side the submit button.
*
* @method moveCancelButtonToTabs
* @param {jquey} form The form element.
*/
moveCancelButtonToTabs(form) {
const cancelButton = form.find(SELECTORS.CANCEL_BUTTON_ELEMENT).addClass('ml-1');
const tabFooters = form.find('[data-region="footer"]');
// Remove the buttons container element.
cancelButton.closest(SELECTORS.BUTTON_CONTAINER).remove();
cancelButton.clone().appendTo(tabFooters);
}
/**
* Load the add random question form in a fragement and perform some transformation
* on the HTML to convert it into tabs for rendering in the modal.
*
* @method loadForm
* @return {promise} Resolved with form HTML and JS.
*/
loadForm() {
const cmid = this.getCMID();
const cat = this.getCategory();
const addonpage = this.getAddOnPageId();
const returnurl = this.getReturnUrl();
return Fragment.loadFragment(
'mod_quiz',
'add_random_question_form',
this.getContextId(),
{
addonpage,
cat,
returnurl,
cmid,
}
)
.then((html, js) =>{
const form = $(html);
const existingCategoryTabContent = form.find(SELECTORS.EXISTING_CATEGORY_TAB);
const existingCategoryTab = this.getBody().find(SELECTORS.EXISTING_CATEGORY_CONTAINER);
const newCategoryTabContent = form.find(SELECTORS.NEW_CATEGORY_TAB);
const newCategoryTab = this.getBody().find(SELECTORS.NEW_CATEGORY_CONTAINER);
// Transform the form into tabs for better rendering in the modal.
this.moveContentIntoTab(existingCategoryTabContent, existingCategoryTab);
this.moveContentIntoTab(newCategoryTabContent, newCategoryTab);
this.moveTabsIntoTabContent(form);
Templates.replaceNode(this.getBody().find(SELECTORS.TAB_CONTENT), form, js);
return;
})
.then(() => {
// Make sure the form change checker is disabled otherwise it'll stop the user from navigating away from the
// page once the modal is hidden.
FormChangeChecker.disableAllChecks();
// Add question to quiz.
this.getBody()[0].addEventListener('click', (e) => {
const button = e.target.closest(SELECTORS.SUBMIT_BUTTON_ELEMENT);
if (!button) {
return;
}
e.preventDefault();
// Add Random questions if the add random button was clicked.
const addRandomButton = e.target.closest(SELECTORS.ADD_RANDOM_BUTTON);
if (addRandomButton) {
const randomcount = document.querySelector(SELECTORS.SELECT_NUMBER_TO_ADD).value;
const filtercondition = document.querySelector(SELECTORS.FILTER_CONDITION_ELEMENT).dataset?.filtercondition;
this.addQuestions(cmid, addonpage, randomcount, filtercondition, '', '');
return;
}
// Add new category if the add category button was clicked.
const addCategoryButton = e.target.closest(SELECTORS.ADD_NEW_CATEGORY_BUTTON);
if (addCategoryButton) {
this.addQuestions(
cmid,
addonpage,
1,
'',
document.querySelector(SELECTORS.NEW_CATEGORY_ELEMENT).value,
document.querySelector(SELECTORS.PARENT_CATEGORY_ELEMENT).value
);
return;
}
});
})
.catch(Notification.exception);
}
/**
* Call web service function to add random questions
*
* @param {number} cmid course module id
* @param {number} addonpage the page where random questions will be added to
* @param {number} randomcount Number of random questions
* @param {string} filtercondition Filter condition
* @param {string} newcategory add new category
* @param {string} parentcategory parent category of new category
*/
async addQuestions(
cmid,
addonpage,
randomcount,
filtercondition,
newcategory,
parentcategory
) {
// We do not need to resolve this Pending because the form submission will result in a page redirect.
new Pending('mod-quiz/modal_add_random_questions');
const call = {
methodname: 'mod_quiz_add_random_questions',
args: {
cmid,
addonpage,
randomcount,
filtercondition,
newcategory,
parentcategory,
}
};
try {
const response = await fetchMany([call])[0];
const form = document.querySelector(SELECTORS.FORM_ELEMENT);
const messageInput = form.querySelector(SELECTORS.MESSAGE_INPUT);
messageInput.value = response.message;
form.submit();
} catch (e) {
Notification.exception(e);
}
}
/**
* Override the modal show function to load the form when this modal is first
* shown.
*
* @method show
*/
show() {
super.show(this);
if (!this.loadedForm) {
this.loadForm(window.location.search);
this.loadedForm = true;
}
}
}
ModalAddRandomQuestion.registerModalType();
@@ -0,0 +1,176 @@
// 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 <http://www.gnu.org/licenses/>.
/**
* Contain the logic for the question bank modal.
*
* @module mod_quiz/modal_quiz_question_bank
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import $ from 'jquery';
import Modal from './add_question_modal';
import * as Fragment from 'core/fragment';
import * as FormChangeChecker from 'core_form/changechecker';
import * as ModalEvents from 'core/modal_events';
const SELECTORS = {
ADD_TO_QUIZ_CONTAINER: 'td.addtoquizaction',
ANCHOR: 'a[href]',
PREVIEW_CONTAINER: 'td.previewquestionaction',
ADD_QUESTIONS_FORM: 'form#questionsubmit',
SORTERS: '.sorters',
};
export default class ModalQuizQuestionBank extends Modal {
static TYPE = 'mod_quiz-quiz-question-bank';
/**
* Create the question bank modal.
*
* @param {Number} contextId Current context id.
*/
static init(contextId) {
const selector = '.menu [data-action="questionbank"]';
document.addEventListener('click', (e) => {
const trigger = e.target.closest(selector);
if (!trigger) {
return;
}
e.preventDefault();
ModalQuizQuestionBank.create({
contextId,
title: trigger.dataset.header,
addOnPage: trigger.dataset.addonpage,
templateContext: {
hidden: true,
},
large: true,
});
});
}
/**
* Override the parent show function.
*
* Reload the body contents when the modal is shown. The current
* window URL is used to inform the new content that should be
* displayed.
*
* @method show
* @return {void}
*/
show() {
this.reloadBodyContent(window.location.search);
return super.show(this);
}
/**
* Replaces the current body contents with a new version of the question
* bank.
*
* The contents of the question bank are generated using the provided
* query string.
*
* @method reloadBodyContent
* @param {string} querystring URL encoded string.
*/
reloadBodyContent(querystring) {
// Load the question bank fragment to be displayed in the modal.
this.setBody(Fragment.loadFragment(
'mod_quiz',
'quiz_question_bank',
this.getContextId(),
{
querystring,
}
));
}
/**
* Update the URL of the anchor element that the user clicked on to make
* sure that the question is added to the correct page.
*
* @method handleAddToQuizEvent
* @param {event} e A JavaScript event
* @param {object} anchorElement The anchor element that was triggered
*/
handleAddToQuizEvent(e, anchorElement) {
// If the user clicks the plus icon to add the question to the page
// directly then we need to intercept the click in order to adjust the
// href and include the correct add on page id before the page is
// redirected.
const href = new URL(anchorElement.attr('href'));
href.searchParams.set('addonpage', this.getAddOnPageId());
anchorElement.attr('href', href);
}
/**
* Set up all of the event handling for the modal.
*
* @method registerEventListeners
*/
registerEventListeners() {
// Apply parent event listeners.
super.registerEventListeners(this);
this.getModal().on('submit', SELECTORS.ADD_QUESTIONS_FORM, (e) => {
// If the user clicks on the "Add selected questions to the quiz" button to add some questions to the page
// then we need to intercept the submit in order to include the correct "add on page id" before the form is
// submitted.
const formElement = $(e.currentTarget);
$('<input />').attr('type', 'hidden')
.attr('name', "addonpage")
.attr('value', this.getAddOnPageId())
.appendTo(formElement);
});
this.getModal().on('click', SELECTORS.ANCHOR, (e) => {
const anchorElement = $(e.currentTarget);
// If the anchor element was the add to quiz link.
if (anchorElement.closest(SELECTORS.ADD_TO_QUIZ_CONTAINER).length) {
this.handleAddToQuizEvent(e, anchorElement);
return;
}
// If the anchor element was a preview question link.
if (anchorElement.closest(SELECTORS.PREVIEW_CONTAINER).length) {
return;
}
// Sorting links have their own handler.
if (anchorElement.closest(SELECTORS.SORTERS).length) {
return;
}
// Anything else means reload the pop-up contents.
e.preventDefault();
this.reloadBodyContent(anchorElement.prop('search'));
});
// Disable the form change checker when the body is rendered.
this.getRoot().on(ModalEvents.bodyRendered, () => {
// Make sure the form change checker is disabled otherwise it'll stop the user from navigating away from the
// page once the modal is hidden.
FormChangeChecker.disableAllChecks();
});
}
}
ModalQuizQuestionBank.registerModalType();
+115
View File
@@ -0,0 +1,115 @@
// 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 <http://www.gnu.org/licenses/>.
/**
* This class manages the confirmation pop-up (also called the pre-flight check)
* that is sometimes shown when a use clicks the start attempt button.
*
* This is also responsible for opening the pop-up window, if the quiz requires to be in one.
*
* @module mod_quiz/preflightcheck
* @copyright 2016 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.1
*/
define(['jquery', 'core/yui', 'core_form/changechecker'], function($, Y, FormChangeChecker) {
/**
* @alias module:mod_quiz/preflightcheck
*/
var t = {
confirmDialogue: null,
/**
* Initialise the start attempt button.
*
* @param {String} startButton the id of the start attempt button that we will be enhancing.
* @param {String} confirmationTitle the title of the dialogue.
* @param {String} confirmationForm selector for the confirmation form to show in the dialogue.
* @param {String} popupoptions If not null, the quiz should be launced in a pop-up.
*/
init: function(startButton, confirmationTitle, confirmationForm, popupoptions) {
var finalStartButton = startButton;
Y.use('moodle-core-notification', function() {
if (Y.one(confirmationForm)) {
t.confirmDialogue = new M.core.dialogue({
headerContent: confirmationTitle,
bodyContent: Y.one(confirmationForm),
draggable: true,
visible: false,
center: true,
modal: true,
width: null,
extraClasses: ['mod_quiz_preflight_popup']
});
Y.one(startButton).on('click', t.displayDialogue);
Y.one('#id_cancel').on('click', t.hideDialogue);
finalStartButton = t.confirmDialogue.get('boundingBox').one('[name="submitbutton"]');
}
if (popupoptions) {
Y.one(finalStartButton).on('click', t.launchQuizPopup, t, popupoptions);
}
});
},
/**
* Display the dialogue.
* @param {Y.EventFacade} e the event being responded to, if any.
*/
displayDialogue: function(e) {
if (e) {
e.halt();
}
t.confirmDialogue.show();
},
/**
* Hide the dialogue.
* @param {Y.EventFacade} e the event being responded to, if any.
*/
hideDialogue: function(e) {
if (e) {
e.halt();
}
t.confirmDialogue.hide(e);
},
/**
* Event handler for the quiz start attempt button.
* @param {Event} e the event being responded to
* @param {Object} popupoptions
*/
launchQuizPopup: function(e, popupoptions) {
e.halt();
Y.use('io-form', function() {
var form = e.target.ancestor('form');
FormChangeChecker.resetFormDirtyState(form.getDOMNode());
window.openpopup(e, {
url: form.get('action') + '?' + Y.IO.stringify(form).replace(/\bcancel=/, 'x='),
windowname: 'quizpopup',
options: popupoptions,
fullscreen: true,
});
});
}
};
return t;
});
+76
View File
@@ -0,0 +1,76 @@
// 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 <http://www.gnu.org/licenses/>.
/**
* Render the question slot template for each question in the quiz edit view.
*
* @module mod_quiz/question_slot
* @copyright 2021 Catalyst IT Australia Pty Ltd
* @author Guillermo Gomez Arias <guillermogomez@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import {call as fetchMany} from 'core/ajax';
import Notification from 'core/notification';
/**
* Set the question version for the slot.
*
* @param {Number} slotId
* @param {Number} newVersion
* @return {Array} The modified question version
*/
const setQuestionVersion = (slotId, newVersion) => fetchMany([{
methodname: 'mod_quiz_set_question_version',
args: {
slotid: slotId,
newversion: newVersion,
}
}])[0];
/**
* Replace the container with a new version.
*/
const registerEventListeners = () => {
document.addEventListener('change', e => {
if (!e.target.matches('[data-action="mod_quiz-select_slot"][data-slot-id]')) {
return;
}
const slotId = e.target.dataset.slotId;
const newVersion = parseInt(e.target.value);
setQuestionVersion(slotId, newVersion)
.then(() => {
location.reload();
return;
})
.catch(Notification.exception);
});
};
/** @property {Boolean} eventsRegistered If the event has been registered or not */
let eventsRegistered = false;
/**
* Entrypoint of the js.
*/
export const init = () => {
if (eventsRegistered) {
return;
}
registerEventListeners();
};
@@ -0,0 +1,234 @@
// 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 <http://www.gnu.org/licenses/>.
/**
* JavaScript for the random_question_form_preview of the
* add_random_form class.
*
* @module mod_quiz/random_question_form_preview
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(
[
'jquery',
'core/ajax',
'core/str',
'core/notification',
'core/templates',
'core/paged_content_factory'
],
function(
$,
Ajax,
Str,
Notification,
Templates,
PagedContentFactory
) {
var ITEMS_PER_PAGE = 5;
var TEMPLATE_NAME = 'mod_quiz/random_question_form_preview_question_list';
var SELECTORS = {
LOADING_ICON_CONTAINER: '[data-region="overlay-icon-container"]',
QUESTION_COUNT_CONTAINER: '[data-region="question-count-container"]',
QUESTION_LIST_CONTAINER: '[data-region="question-list-container"]'
};
/**
* Show the loading spinner over the preview section.
*
* @param {jquery} root The root element.
*/
var showLoadingIcon = function(root) {
root.find(SELECTORS.LOADING_ICON_CONTAINER).removeClass('hidden');
};
/**
* Hide the loading spinner.
*
* @param {jquery} root The root element.
*/
var hideLoadingIcon = function(root) {
root.find(SELECTORS.LOADING_ICON_CONTAINER).addClass('hidden');
};
/**
* Render the section of text to show the question count.
*
* @param {jquery} root The root element.
* @param {int} questionCount The number of questions.
*/
var renderQuestionCount = function(root, questionCount) {
Str.get_string('questionsmatchingfilter', 'mod_quiz', questionCount)
.then(function(string) {
root.find(SELECTORS.QUESTION_COUNT_CONTAINER).html(string);
return;
})
.fail(Notification.exception);
};
/**
* Send a request to the server for more questions.
*
* @param {int} categoryId A question category id.
* @param {bool} includeSubcategories If the results should include subcategory questions
* @param {int[]} tagIds The list of tag ids that each question must have.
* @param {int} contextId The context where the questions will be added.
* @param {int} limit How many questions to retrieve.
* @param {int} offset How many questions to skip from the start of the result set.
* @return {promise} Resolved when the preview section has rendered.
*/
var requestQuestions = function(
categoryId,
includeSubcategories,
tagIds,
contextId,
limit,
offset
) {
var request = {
methodname: 'core_question_get_random_question_summaries',
args: {
categoryid: categoryId,
includesubcategories: includeSubcategories,
tagids: tagIds,
contextid: contextId,
limit: limit,
offset: offset
}
};
return Ajax.call([request])[0];
};
/**
* Build a paged content widget for questions with the given criteria. The
* criteria is used to fetch more questions from the server as the user
* requests new pages.
*
* @param {int} categoryId A question category id.
* @param {bool} includeSubcategories If the results should include subcategory questions
* @param {int[]} tagIds The list of tag ids that each question must have.
* @param {int} contextId The context where the questions will be added.
* @param {int} totalQuestionCount How many questions match the criteria above.
* @param {object[]} firstPageQuestions List of questions for the first page.
* @return {promise} A promise resolved with the HTML and JS for the paged content.
*/
var renderQuestionsAsPagedContent = function(
categoryId,
includeSubcategories,
tagIds,
contextId,
totalQuestionCount,
firstPageQuestions
) {
// Provide a callback, renderQuestionsPages,
// to control how the questions on each page are rendered.
return PagedContentFactory.createFromAjax(
totalQuestionCount,
ITEMS_PER_PAGE,
// Callback function to render the requested pages.
function(pagesData) {
return pagesData.map(function(pageData) {
var limit = pageData.limit;
var offset = pageData.offset;
if (offset == 0) {
// The first page is being requested and we've already got
// that data so we can just render it immediately.
return Templates.render(TEMPLATE_NAME, {questions: firstPageQuestions});
} else {
// Otherwise we need to ask the server for the data.
return requestQuestions(
categoryId,
includeSubcategories,
tagIds,
contextId,
limit,
offset
)
.then(function(response) {
var questions = response.questions;
return Templates.render(TEMPLATE_NAME, {questions: questions});
})
.fail(Notification.exception);
}
});
}
);
};
/**
* Re-render the preview section based on the provided filter criteria.
*
* @param {jquery} root The root element.
* @param {int} categoryId A question category id.
* @param {bool} includeSubcategories If the results should include subcategory questions
* @param {int[]} tagIds The list of tag ids that each question must have.
* @param {int} contextId The context where the questions will be added.
* @return {promise} Resolved when the preview section has rendered.
*/
var reload = function(root, categoryId, includeSubcategories, tagIds, contextId) {
// Show the loading spinner to tell the user that something is happening.
showLoadingIcon(root);
// Load the first set of questions.
return requestQuestions(categoryId, includeSubcategories, tagIds, contextId, ITEMS_PER_PAGE, 0)
.then(function(response) {
var totalCount = response.totalcount;
// Show the help message for the user to indicate how many questions
// match their filter criteria.
renderQuestionCount(root, totalCount);
return response;
})
.then(function(response) {
var totalQuestionCount = response.totalcount;
var questions = response.questions;
if (questions.length) {
// We received some questions so render them as paged content
// with a paging bar.
return renderQuestionsAsPagedContent(
categoryId,
includeSubcategories,
tagIds,
contextId,
totalQuestionCount,
questions
);
} else {
// If we didn't receive any questions then we can return empty
// HTML and JS to clear the preview section.
return $.Deferred().resolve('', '');
}
})
.then(function(html, js) {
// Show the user the question set.
var container = root.find(SELECTORS.QUESTION_LIST_CONTAINER);
Templates.replaceNodeContents(container, html, js);
return;
})
.always(function() {
hideLoadingIcon(root);
})
.fail(Notification.exception);
};
return {
reload: reload,
showLoadingIcon: showLoadingIcon,
hideLoadingIcon: hideLoadingIcon
};
});
+83
View File
@@ -0,0 +1,83 @@
// 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 <http://www.gnu.org/licenses/>.
/**
* This module has the code to make the Re-open attempt button work, if present.
*
* That is, it looks for buttons with HTML like
* &lt;button type="button" data-action="reopen-attempt" data-attempt-id="227000" data-after-action-url="/mod/quiz/report.php">
* and if that is clicked, it first shows an 'Are you sure' pop-up, and if they are sure,
* the attempt is re-opened, and then the page reloads.
*
* @module mod_quiz/reopen_attempt_ui
* @copyright 2023 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import {exception as displayException} from 'core/notification';
import {call as fetchMany} from 'core/ajax';
import {getString} from 'core/str';
import {saveCancelPromise} from 'core/notification';
/**
* Handle a click if it is on one of our buttons.
*
* @param {MouseEvent} e the click event.
*/
const reopenButtonClicked = async(e) => {
if (!(e.target instanceof HTMLElement) || !e.target.matches('button[data-action="reopen-attempt"]')) {
return;
}
e.preventDefault();
const attemptId = e.target.dataset.attemptId;
try {
// We fetch the confirmation message from the server now, so the message is based
// on the latest state of the attempt, rather than when the containing page loaded.
const messages = fetchMany([{
methodname: 'mod_quiz_get_reopen_attempt_confirmation',
args: {
"attemptid": attemptId,
},
}]);
await saveCancelPromise(
getString('reopenattemptareyousuretitle', 'mod_quiz'),
messages[0],
getString('reopenattempt', 'mod_quiz'),
{triggerElement: e.target},
);
await (fetchMany([{
methodname: 'mod_quiz_reopen_attempt',
args: {
"attemptid": attemptId,
},
}])[0]);
window.location = M.cfg.wwwroot + e.target.dataset.afterActionUrl;
} catch (error) {
if (error.type === 'modal-save-cancel:cancel') {
// User clicked Cancel, so do nothing.
return;
}
await displayException(error);
}
};
export const init = () => {
document.addEventListener('click', reopenButtonClicked);
};
+40
View File
@@ -0,0 +1,40 @@
// 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 <http://www.gnu.org/licenses/>.
/**
* Initialise the repaginate dialogue on quiz editing page.
*
* @module mod_quiz/repaginate
* @copyright 2019 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import Modal from 'core/modal';
export const init = () => {
document.addEventListener('click', (event) => {
const repaginateCommand = event.target.closest('#repaginatecommand');
if (!repaginateCommand) {
return;
}
event.preventDefault();
Modal.create({
title: repaginateCommand.dataset.header,
body: repaginateCommand.dataset.form,
large: false,
show: true,
});
});
};
@@ -0,0 +1,78 @@
// 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 <http://www.gnu.org/licenses/>.
/**
* A javascript module to handle submission confirmation for quiz.
*
* @module mod_quiz/submission_confirmation
* @copyright 2022 Huong Nguyen <huongnv13@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 4.1
*/
import {saveCancelPromise} from 'core/notification';
import Prefetch from 'core/prefetch';
import Templates from 'core/templates';
import {getString} from 'core/str';
const SELECTOR = {
attemptSubmitButton: '.path-mod-quiz .btn-finishattempt button',
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 = (unAnsweredQuestions) => {
const submitAction = document.querySelector(SELECTOR.attemptSubmitButton);
if (submitAction) {
submitAction.addEventListener('click', async(e) => {
e.preventDefault();
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;
}
});
}
};
/**
* Initialises.
* @param {int} unAnsweredQuestions Total number of unanswered questions
*/
export const init = (unAnsweredQuestions) => {
Prefetch.prefetchStrings('core', ['submit']);
Prefetch.prefetchStrings('core_admin', ['confirmation']);
Prefetch.prefetchStrings('quiz', ['submitallandfinish', 'submission_confirmation']);
Prefetch.prefetchTemplate(TEMPLATES.submissionConfirmation);
registerEventListeners(unAnsweredQuestions);
};
@@ -0,0 +1,63 @@
// 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 <http://www.gnu.org/licenses/>.
/**
* Event handling for the edit random question form.
*
* Dynamically saves the new filter condition before navigating back to the quiz question list.
*
* @module mod_quiz/update_random_question_filter_condition
* @author 2022 <nathannguyen@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import ajax from 'core/ajax';
import Notification from 'core/notification';
export const init = () => {
const SELECTORS = {
QUESTION_BANK_CONTAINER: '#questionbank_container',
FORM_ELEMENT: '#update_filter_condition_form',
UPDATE_BUTTON: '[name="update"]',
CANCEL_BUTTON: '[name="cancel"]',
MESSAGE_INPUT: '[name="message"]',
FILTER_CONDITION_ELEMENT: '[data-filtercondition]',
};
const questionBank = document.querySelector(SELECTORS.QUESTION_BANK_CONTAINER);
const form = document.querySelector(SELECTORS.FORM_ELEMENT);
const updateButton = form.querySelector(SELECTORS.UPDATE_BUTTON);
updateButton.addEventListener("click", async(e) => {
e.preventDefault();
const request = {
methodname: 'mod_quiz_update_filter_condition',
args: {
cmid: form.dataset?.cmid,
slotid: form.dataset?.slotid,
filtercondition: questionBank.querySelector(SELECTORS.FILTER_CONDITION_ELEMENT).dataset?.filtercondition,
}
};
try {
const response = await ajax.call([request])[0];
const messageInput = form.querySelector(SELECTORS.MESSAGE_INPUT);
messageInput.value = response.message;
form.submit();
} catch (e) {
Notification.exception(e);
}
});
};