From c6cf0cb951a671885f723fbb43f021f2d3b887b5 Mon Sep 17 00:00:00 2001 From: Mark Johnson Date: Thu, 20 Nov 2025 13:40:34 +0000 Subject: [PATCH] MDL-87264 questions: Make bank switcher more generic Currently, the question bank switcher assumes its being displayed inside a quiz. This means it can't easily be reused in other activities, or outside activity contexts. This change makes the inclusion and display of an activity's question bank on the list of available banks optional, and removes any references to the quiz. --- .upgradenotes/MDL-87264-2025112115054754.yml | 26 +++ public/lang/en/question.php | 4 + .../quiz/amd/build/add_question_modal.min.js | 4 +- .../amd/build/add_question_modal.min.js.map | 2 +- .../build/modal_add_random_question.min.js | 2 +- .../modal_add_random_question.min.js.map | 2 +- .../amd/build/modal_quiz_question_bank.min.js | 2 +- .../build/modal_quiz_question_bank.min.js.map | 2 +- public/mod/quiz/amd/src/add_question_modal.js | 50 +---- .../quiz/amd/src/modal_add_random_question.js | 97 +++++----- .../quiz/amd/src/modal_quiz_question_bank.js | 62 +++--- .../mod/quiz/classes/output/edit_renderer.php | 2 + public/mod/quiz/lang/en/deprecated.txt | 2 + public/mod/quiz/lang/en/quiz.php | 6 +- public/mod/quiz/lib.php | 7 + .../question/amd/build/bank_switcher.min.js | 21 +++ .../amd/build/bank_switcher.min.js.map | 1 + public/question/amd/src/bank_switcher.js | 177 ++++++++++++++++++ .../bulkmove/classes/output/bulk_move.php | 4 +- .../classes/external/search_shared_banks.php | 12 +- .../classes/local/bank/formatted_bank.php | 109 +++++++++++ .../local/bank/question_bank_helper.php | 86 +++++++-- .../classes/output/question_bank_list.php | 6 +- .../classes/output/switch_question_bank.php | 38 +++- public/question/classes/output/view_banks.php | 9 +- public/question/classes/route/api/bank.php | 70 ++++++- .../templates/switch_question_bank.mustache | 145 +++++--------- .../local/bank/question_bank_helper_test.php | 49 +++-- 28 files changed, 697 insertions(+), 300 deletions(-) create mode 100644 .upgradenotes/MDL-87264-2025112115054754.yml create mode 100644 public/question/amd/build/bank_switcher.min.js create mode 100644 public/question/amd/build/bank_switcher.min.js.map create mode 100644 public/question/amd/src/bank_switcher.js create mode 100644 public/question/classes/local/bank/formatted_bank.php diff --git a/.upgradenotes/MDL-87264-2025112115054754.yml b/.upgradenotes/MDL-87264-2025112115054754.yml new file mode 100644 index 00000000000..b4716068a46 --- /dev/null +++ b/.upgradenotes/MDL-87264-2025112115054754.yml @@ -0,0 +1,26 @@ +issueNumber: MDL-87264 +notes: + mod_quiz: + - message: > + The `mod_quiz_output_fragment_switch_question_bank()` Fragment API + callback is deprecated in favour of + `core_question\route\api\bank::banks()`, available via the route + `/api/rest/v2/core/question/banks?courseid=X`. + type: deprecated + - message: > + The "gobacktoquiz" and "selectquestionbank" lang strings have been deprecated. + These are only used by the question bank switching UI, so have been replaced + with the "switchergoback" and "switcherselectbank" strings in the core_question + component. + type: deprecated + core_question: + - message: > + The `\core_question\output\switch_question_bank` renderable is now deprecated, + as rendering of the switch_question_bank template is now all handled client-side + by the `core_question/bank_switcher` Javascript module. + type: deprecated + - message: > + The UI for switching question banks is now encapsulated in the + `core_question/bank_switcher` Javascript module. This takes an existing modal + and replaces the content with the switcher. It will then emit a custom + `bankSwitched` event on the modal's DOM element when a new bank is selected. diff --git a/public/lang/en/question.php b/public/lang/en/question.php index 18765824c55..9588ff5f5a9 100644 --- a/public/lang/en/question.php +++ b/public/lang/en/question.php @@ -82,6 +82,7 @@ $string['clicktounflag'] = 'Remove flag'; $string['clickunflag'] = 'Remove flag'; $string['contexterror'] = 'You shouldn\'t have got here if you\'re not moving a category to another context.'; $string['copy'] = 'Copy from {$a} and change links.'; +$string['coursenamebankname'] = '{$a->coursename} - {$a->bankname}'; $string['created'] = 'Created'; $string['createdefault'] = 'Create default question bank'; $string['createdby'] = 'Created by'; @@ -325,6 +326,9 @@ $string['shareincontext'] = 'Share in context for {$a}'; $string['stoponerror'] = 'Stop on error'; $string['stoponerror_help'] = 'This setting determines whether the import process stops when an error is detected, resulting in no questions being imported, or whether any questions containing errors are ignored and any valid questions are imported.'; $string['switchbank'] = 'Switch bank'; +$string['switchergoback'] = 'Go back'; +$string['switchersearchbyname'] = 'Search by name...'; +$string['switcherselectbank'] = 'Select question bank'; $string['tofilecategory'] = 'Write category to file'; $string['tofilecontext'] = 'Write context to file'; $string['topfor'] = 'Top for {$a}'; diff --git a/public/mod/quiz/amd/build/add_question_modal.min.js b/public/mod/quiz/amd/build/add_question_modal.min.js index 8c2fee6443c..9216d5c872a 100644 --- a/public/mod/quiz/amd/build/add_question_modal.min.js +++ b/public/mod/quiz/amd/build/add_question_modal.min.js @@ -1,10 +1,10 @@ -define("mod_quiz/add_question_modal",["exports","core/modal","core/fragment","core/str","core/form-autocomplete"],(function(_exports,_modal,Fragment,_str,_formAutocomplete){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 _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} +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 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_modal=_interopRequireDefault(_modal),Fragment=function(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]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(Fragment),_formAutocomplete=_interopRequireDefault(_formAutocomplete);class AddQuestionModal extends _modal.default{configure(modalConfig){modalConfig.large=!0,modalConfig.show=!0,modalConfig.removeOnClose=!0,this.setContextId(modalConfig.contextId),this.setAddOnPageId(modalConfig.addOnPage),this.quizCmId=modalConfig.quizCmId,this.bankCmId=modalConfig.bankCmId,this.originalTitle=modalConfig.title,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}async handleSwitchBankContentReload(Selector){var _document$querySelect;this.setTitle((0,_str.getString)("selectquestionbank","mod_quiz"));const el=document.createElement("button");el.classList.add("btn","btn-primary"),el.textContent=await(0,_str.getString)("gobacktoquiz","mod_quiz"),el.setAttribute("data-action","go-back"),el.setAttribute("value",this.bankCmId),this.setFooter(el),this.setBody(Fragment.loadFragment("mod_quiz","switch_question_bank",this.getContextId(),{quizcmid:this.quizCmId,bankcmid:this.bankCmId}));const placeholder=await(0,_str.getString)("searchbyname","mod_quiz");return await this.getBodyPromise(),await _formAutocomplete.default.enhance(Selector,!1,"core_question/question_banks_datasource",placeholder,!1,!0,"",!0),null===(_document$querySelect=document.querySelector(".search-banks .form-autocomplete-selection"))||void 0===_document$querySelect||_document$querySelect.classList.add("d-none"),this}}return _exports.default=AddQuestionModal,_exports.default})); + */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),this.quizCmId=modalConfig.quizCmId,this.bankCmId=modalConfig.bankCmId,this.courseId=modalConfig.courseId,this.originalTitle=modalConfig.title,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 \ No newline at end of file diff --git a/public/mod/quiz/amd/build/add_question_modal.min.js.map b/public/mod/quiz/amd/build/add_question_modal.min.js.map index fc4b9eebcf0..1953fcab333 100644 --- a/public/mod/quiz/amd/build/add_question_modal.min.js.map +++ b/public/mod/quiz/amd/build/add_question_modal.min.js.map @@ -1 +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 .\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 \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Modal from 'core/modal';\nimport * as Fragment from 'core/fragment';\nimport {getString} from 'core/str';\nimport AutoComplete from 'core/form-autocomplete';\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 // Store the quiz module id for when we need to POST to the quiz.\n // This is because the URL cmid param will change during filter operations as we will be in another bank context.\n this.quizCmId = modalConfig.quizCmId;\n this.bankCmId = modalConfig.bankCmId;\n\n // Store the original title of the modal, so we can revert back to it once we have switched to another bank.\n this.originalTitle = modalConfig.title;\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 * Update the modal with a list of banks to switch to and enhance the standard selects to Autocomplete fields.\n *\n * @param {String} Selector for the original select element.\n * @return {Promise} Modal.\n */\n async handleSwitchBankContentReload(Selector) {\n this.setTitle(getString('selectquestionbank', 'mod_quiz'));\n\n // Create a 'Go back' button and set it in the footer.\n const el = document.createElement('button');\n el.classList.add('btn', 'btn-primary');\n el.textContent = await getString('gobacktoquiz', 'mod_quiz');\n el.setAttribute('data-action', 'go-back');\n el.setAttribute('value', this.bankCmId);\n this.setFooter(el);\n\n this.setBody(\n Fragment.loadFragment(\n 'mod_quiz',\n 'switch_question_bank',\n this.getContextId(),\n {\n 'quizcmid': this.quizCmId,\n 'bankcmid': this.bankCmId,\n })\n );\n const placeholder = await getString('searchbyname', 'mod_quiz');\n await this.getBodyPromise();\n await AutoComplete.enhance(\n Selector,\n false,\n 'core_question/question_banks_datasource',\n placeholder,\n false,\n true,\n '',\n true\n );\n\n // Hide the selection element as we don't need it.\n document.querySelector('.search-banks .form-autocomplete-selection')?.classList.add('d-none');\n\n return this;\n }\n}\n"],"names":["AddQuestionModal","Modal","configure","modalConfig","large","show","removeOnClose","setContextId","contextId","setAddOnPageId","addOnPage","quizCmId","bankCmId","originalTitle","title","constructor","root","addOnPageId","id","getContextId","this","getAddOnPageId","Selector","setTitle","el","document","createElement","classList","add","textContent","setAttribute","setFooter","setBody","Fragment","loadFragment","placeholder","getBodyPromise","AutoComplete","enhance","querySelector"],"mappings":";;;;;;;61BA4BqBA,yBAAyBC,eAC1CC,UAAUC,aAENA,YAAYC,OAAQ,EAGpBD,YAAYE,MAAO,EACnBF,YAAYG,eAAgB,OAGvBC,aAAaJ,YAAYK,gBACzBC,eAAeN,YAAYO,gBAI3BC,SAAWR,YAAYQ,cACvBC,SAAWT,YAAYS,cAGvBC,cAAgBV,YAAYW,YAG3BZ,UAAUC,aAGpBY,YAAYC,YACFA,WAEDR,UAAY,UACZS,YAAc,KAUvBV,aAAaW,SACJV,UAAYU,GASrBC,sBACWC,KAAKZ,UAUhBC,eAAeS,SACND,YAAcC,GASvBG,wBACWD,KAAKH,gDASoBK,yCAC3BC,UAAS,kBAAU,qBAAsB,mBAGxCC,GAAKC,SAASC,cAAc,UAClCF,GAAGG,UAAUC,IAAI,MAAO,eACxBJ,GAAGK,kBAAoB,kBAAU,eAAgB,YACjDL,GAAGM,aAAa,cAAe,WAC/BN,GAAGM,aAAa,QAASV,KAAKR,eACzBmB,UAAUP,SAEVQ,QACDC,SAASC,aACL,WACA,uBACAd,KAAKD,eACL,UACgBC,KAAKT,kBACLS,KAAKR,kBAGvBuB,kBAAoB,kBAAU,eAAgB,yBAC9Cf,KAAKgB,uBACLC,0BAAaC,QACfhB,UACA,EACA,0CACAa,aACA,GACA,EACA,IACA,iCAIJV,SAASc,cAAc,sGAA+CZ,UAAUC,IAAI,UAE7ER"} \ No newline at end of file +{"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 .\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 \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 // Store the quiz module id for when we need to POST to the quiz.\n // This is because the URL cmid param will change during filter operations as we will be in another bank context.\n this.quizCmId = modalConfig.quizCmId;\n this.bankCmId = modalConfig.bankCmId;\n this.courseId = modalConfig.courseId;\n\n // Store the original title of the modal, so we can revert back to it once we have switched to another bank.\n this.originalTitle = modalConfig.title;\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"],"names":["AddQuestionModal","Modal","configure","modalConfig","large","show","removeOnClose","setContextId","contextId","setAddOnPageId","addOnPage","quizCmId","bankCmId","courseId","originalTitle","title","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,gBAI3BC,SAAWR,YAAYQ,cACvBC,SAAWT,YAAYS,cACvBC,SAAWV,YAAYU,cAGvBC,cAAgBX,YAAYY,YAG3Bb,UAAUC,aAGpBa,YAAYC,YACFA,WAEDT,UAAY,UACZU,YAAc,KAUvBX,aAAaY,SACJX,UAAYW,GASrBC,sBACWC,KAAKb,UAUhBC,eAAeU,SACND,YAAcC,GASvBG,wBACWD,KAAKH"} \ No newline at end of file diff --git a/public/mod/quiz/amd/build/modal_add_random_question.min.js b/public/mod/quiz/amd/build/modal_add_random_question.min.js index 8186390a1fc..efed2b7de91 100644 --- a/public/mod/quiz/amd/build/modal_add_random_question.min.js +++ b/public/mod/quiz/amd/build/modal_add_random_question.min.js @@ -1,3 +1,3 @@ -define("mod_quiz/modal_add_random_question",["exports","jquery","./add_question_modal","core/notification","core/fragment","core/templates","core_form/changechecker","core/ajax","core/pending"],(function(_exports,_jquery,_add_question_modal,Notification,Fragment,Templates,FormChangeChecker,_ajax,_pending){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}}function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_jquery=_interopRequireDefault(_jquery),_add_question_modal=_interopRequireDefault(_add_question_modal),Notification=_interopRequireWildcard(Notification),Fragment=_interopRequireWildcard(Fragment),Templates=_interopRequireWildcard(Templates),FormChangeChecker=_interopRequireWildcard(FormChangeChecker),_pending=_interopRequireDefault(_pending);const SELECTORS={ANCHOR:"a[href]",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"], input[type="submit"][name="update"]',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"]',SWITCH_TO_OTHER_BANK:'button[data-action="switch-question-bank"]',NEW_BANKMOD_ID:"data-newmodid",BANK_SEARCH:"#searchbanks",GO_BACK_BUTTON:'button[data-action="go-back"]',UPDATE_FILTER_BUTTON:'input[type="submit"][name="update"]'};class ModalAddRandomQuestion extends _add_question_modal.default{static init(contextId,bankCmId,category,returnUrl,quizCmId){let showNewCategory=!(arguments.length>5&&void 0!==arguments[5])||arguments[5];document.addEventListener("click",(e=>{const trigger=e.target.closest('.menu [data-action="addarandomquestion"], [data-action="editrandomquestion"]');trigger&&(e.preventDefault(),trigger.dataset.slotid&&(showNewCategory=!1),ModalAddRandomQuestion.create({contextId:contextId,bankCmId:bankCmId,category:category,returnUrl:returnUrl,quizCmId:quizCmId,showNewCategory:showNewCategory,title:trigger.dataset.header,addOnPage:trigger.dataset.addonpage,slotId:trigger.dataset.slotid,templateContext:{hidden:showNewCategory}}))}))}constructor(root){super(root),this.category=null,this.returnUrl=null,this.quizCmId=null,this.loadedForm=!1,this.slotId=0,this.savedFilterCondition=null}configure(modalConfig){var _modalConfig$slotId,_modalConfig$savedFil;modalConfig.removeOnClose=!0,this.setCategory(modalConfig.category),this.setReturnUrl(modalConfig.returnUrl),this.showNewCategory=modalConfig.showNewCategory,this.setSlotId(null!==(_modalConfig$slotId=modalConfig.slotId)&&void 0!==_modalConfig$slotId?_modalConfig$slotId:0),this.setSavedFilterCondition(null!==(_modalConfig$savedFil=modalConfig.savedFilterCondition)&&void 0!==_modalConfig$savedFil?_modalConfig$savedFil:null),super.configure(modalConfig)}setAddOnPageId(id){super.setAddOnPageId(id),this.getBody().find(SELECTORS.ADD_ON_PAGE_FORM_ELEMENT).val(id)}setCategory(category){this.category=category}getCategory(){return this.category}setReturnUrl(url){this.returnUrl=url}getReturnUrl(){return this.returnUrl}setSlotId(slotId){this.slotId=slotId}getSlotId(){return this.slotId}setSavedFilterCondition(filterCondition){this.savedFilterCondition=filterCondition}getSavedFilterCondition(){return this.savedFilterCondition}moveContentIntoTab(tabContent,tabElement){tabContent.find(SELECTORS.FORM_HEADER).addClass("hidden"),tabContent.wrap(tabElement)}moveTabsIntoTabContent(form){const tabContent=this.getBody().find(SELECTORS.TAB_CONTENT).empty();form.find('[role="tabpanel"]').wrapAll(tabContent)}moveCancelButtonToTabs(form){const cancelButton=form.find(SELECTORS.CANCEL_BUTTON_ELEMENT).addClass("ms-1"),tabFooters=form.find('[data-region="footer"]');cancelButton.closest(SELECTORS.BUTTON_CONTAINER).remove(),cancelButton.clone().appendTo(tabFooters)}loadForm(){const addonpage=this.getAddOnPageId(),returnurl=this.getReturnUrl(),quizcmid=this.quizCmId,bankcmid=this.bankCmId,savedfiltercondition=this.getSavedFilterCondition();return this.setSavedFilterCondition(null),Fragment.loadFragment("mod_quiz","add_random_question_form",this.getContextId(),{addonpage:null!=addonpage?addonpage:null,returnurl:returnurl,quizcmid:quizcmid,bankcmid:bankcmid,slotid:this.getSlotId(),savedfiltercondition:savedfiltercondition}).then(((html,js)=>{const form=(0,_jquery.default)(html);if(!this.getSlotId()){const existingCategoryTabContent=form.find(SELECTORS.EXISTING_CATEGORY_TAB),existingCategoryTab=this.getBody().find(SELECTORS.EXISTING_CATEGORY_CONTAINER),newCategoryTabContent=form.find(SELECTORS.NEW_CATEGORY_TAB),newCategoryTab=this.getBody().find(SELECTORS.NEW_CATEGORY_CONTAINER);this.moveContentIntoTab(existingCategoryTabContent,existingCategoryTab),this.moveContentIntoTab(newCategoryTabContent,newCategoryTab),this.moveTabsIntoTabContent(form)}Templates.replaceNode(this.getBody().find(SELECTORS.TAB_CONTENT),form,js)})).then((()=>{FormChangeChecker.disableAllChecks(),this.getBody()[0].addEventListener("click",(e=>{if(!e.target.closest(SELECTORS.SUBMIT_BUTTON_ELEMENT))return;e.preventDefault(),document.querySelector('#questionscontainer input[name="cmid"]').setAttribute("name",this.quizCmId);if(e.target.closest(SELECTORS.ADD_RANDOM_BUTTON)){var _document$querySelect;const randomcount=document.querySelector(SELECTORS.SELECT_NUMBER_TO_ADD).value,filtercondition=null===(_document$querySelect=document.querySelector(SELECTORS.FILTER_CONDITION_ELEMENT).dataset)||void 0===_document$querySelect?void 0:_document$querySelect.filtercondition;return void this.addQuestions(quizcmid,addonpage,randomcount,filtercondition,"","")}if(e.target.closest(SELECTORS.UPDATE_FILTER_BUTTON)){var _document$querySelect2;const filtercondition=null===(_document$querySelect2=document.querySelector(SELECTORS.FILTER_CONDITION_ELEMENT).dataset)||void 0===_document$querySelect2?void 0:_document$querySelect2.filtercondition;return void this.updateFilterCondition(quizcmid,this.getSlotId(),filtercondition)}e.target.closest(SELECTORS.ADD_NEW_CATEGORY_BUTTON)&&this.addQuestions(quizcmid,addonpage,1,"",document.querySelector(SELECTORS.NEW_CATEGORY_ELEMENT).value,document.querySelector(SELECTORS.PARENT_CATEGORY_ELEMENT).value)})),this.getModal().on("click",SELECTORS.SWITCH_TO_OTHER_BANK,(()=>{var _document$querySelect3;this.setSavedFilterCondition(null===(_document$querySelect3=document.querySelector(SELECTORS.FILTER_CONDITION_ELEMENT).dataset)||void 0===_document$querySelect3?void 0:_document$querySelect3.filtercondition),this.handleSwitchBankContentReload(SELECTORS.BANK_SEARCH).then((function(ModalQuizQuestionBank){var _$;return null===(_$=(0,_jquery.default)(SELECTORS.BANK_SEARCH))||void 0===_$||_$.on("change",(e=>{const bankCmId=(0,_jquery.default)(e.currentTarget).val();bankCmId>0&&ModalAddRandomQuestion.create({contextId:ModalQuizQuestionBank.getContextId(),bankCmId:bankCmId,category:ModalQuizQuestionBank.getCategory(),returnUrl:ModalQuizQuestionBank.getReturnUrl(),quizCmId:ModalQuizQuestionBank.quizCmId,title:ModalQuizQuestionBank.originalTitle,addOnPage:ModalQuizQuestionBank.getAddOnPageId(),templateContext:{hidden:ModalQuizQuestionBank.showNewCategory},showNewCategory:ModalQuizQuestionBank.showNewCategory,slotId:ModalQuizQuestionBank.getSlotId()}).then(ModalQuizQuestionBank.destroy()).catch(Notification.exception)})),ModalQuizQuestionBank}))})),this.getModal().on("click",SELECTORS.GO_BACK_BUTTON,(e=>{const anchorElement=(0,_jquery.default)(e.currentTarget);ModalAddRandomQuestion.create({contextId:this.getContextId(),bankCmId:anchorElement.attr("value"),category:this.getCategory(),returnUrl:this.getReturnUrl(),quizCmId:this.quizCmId,title:this.originalTitle,addOnPage:this.getAddOnPageId(),templateContext:{hidden:this.showNewCategory},showNewCategory:this.showNewCategory,savedFilterCondition:this.getSavedFilterCondition(),slotId:this.getSlotId()}).then(this.destroy()).catch(Notification.exception)})),this.getModal().on("click",SELECTORS.ANCHOR,(e=>{const anchorElement=(0,_jquery.default)(e.currentTarget);anchorElement.closest("a["+SELECTORS.NEW_BANKMOD_ID+"]").length&&ModalAddRandomQuestion.create({contextId:this.getContextId(),bankCmId:anchorElement.attr(SELECTORS.NEW_BANKMOD_ID),category:this.getCategory(),returnUrl:this.getReturnUrl(),quizCmId:this.quizCmId,title:this.originalTitle,addOnPage:this.getAddOnPageId(),templateContext:{hidden:this.showNewCategory},showNewCategory:this.showNewCategory,slotId:this.getSlotId()}).then(this.destroy()).catch(Notification.exception)}))})).catch(Notification.exception)}async addQuestions(quizcmid,addonpage,randomcount,filtercondition,newcategory,parentcategory){new _pending.default("mod-quiz/modal_add_random_questions");const call={methodname:"mod_quiz_add_random_questions",args:{cmid:quizcmid,addonpage:addonpage,randomcount:randomcount,filtercondition:filtercondition,newcategory:newcategory,parentcategory:parentcategory}};try{const response=await(0,_ajax.call)([call])[0],form=document.querySelector(SELECTORS.FORM_ELEMENT);form.querySelector(SELECTORS.MESSAGE_INPUT).value=response.message,form.submit()}catch(e){Notification.exception(e)}}async updateFilterCondition(quizcmid,slotid,filtercondition){new _pending.default("mod-quiz/modal_add_random_questions");const call={methodname:"mod_quiz_update_filter_condition",args:{cmid:quizcmid,slotid:slotid,filtercondition:filtercondition}};try{const response=await(0,_ajax.call)([call])[0],form=document.querySelector(SELECTORS.FORM_ELEMENT);form.querySelector(SELECTORS.MESSAGE_INPUT).value=response.message,form.submit()}catch(e){Notification.exception(e)}}show(){super.show(this),this.loadedForm||(this.tabHtml=this.getBody(),this.loadForm(window.location.search),this.loadedForm=!0)}}return _exports.default=ModalAddRandomQuestion,_defineProperty(ModalAddRandomQuestion,"TYPE","mod_quiz-quiz-add-random-question"),_defineProperty(ModalAddRandomQuestion,"TEMPLATE","mod_quiz/modal_add_random_question"),ModalAddRandomQuestion.registerModalType(),_exports.default})); +define("mod_quiz/modal_add_random_question",["exports","jquery","./add_question_modal","core/notification","core/fragment","core/templates","core_form/changechecker","core/ajax","core/pending","core_question/bank_switcher"],(function(_exports,_jquery,_add_question_modal,Notification,Fragment,Templates,FormChangeChecker,_ajax,_pending,_bank_switcher){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}}function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_jquery=_interopRequireDefault(_jquery),_add_question_modal=_interopRequireDefault(_add_question_modal),Notification=_interopRequireWildcard(Notification),Fragment=_interopRequireWildcard(Fragment),Templates=_interopRequireWildcard(Templates),FormChangeChecker=_interopRequireWildcard(FormChangeChecker),_pending=_interopRequireDefault(_pending),_bank_switcher=_interopRequireDefault(_bank_switcher);const SELECTORS={ANCHOR:"a[href]",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"], input[type="submit"][name="update"]',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"]',SWITCH_TO_OTHER_BANK:'button[data-action="switch-question-bank"]',NEW_BANKMOD_ID:"data-newmodid",BANK_SEARCH:"#searchbanks",GO_BACK_BUTTON:'button[data-action="go-back"]',UPDATE_FILTER_BUTTON:'input[type="submit"][name="update"]'};class ModalAddRandomQuestion extends _add_question_modal.default{static init(contextId,bankCmId,category,returnUrl,quizCmId){let showNewCategory=!(arguments.length>5&&void 0!==arguments[5])||arguments[5],courseId=arguments.length>6?arguments[6]:void 0;document.addEventListener("click",(e=>{const trigger=e.target.closest('.menu [data-action="addarandomquestion"], [data-action="editrandomquestion"]');trigger&&(e.preventDefault(),trigger.dataset.slotid&&(showNewCategory=!1),ModalAddRandomQuestion.create({contextId:contextId,bankCmId:bankCmId,category:category,returnUrl:returnUrl,quizCmId:quizCmId,showNewCategory:showNewCategory,title:trigger.dataset.header,addOnPage:trigger.dataset.addonpage,slotId:trigger.dataset.slotid,templateContext:{hidden:showNewCategory},courseId:courseId}))}))}constructor(root){super(root),this.category=null,this.returnUrl=null,this.quizCmId=null,this.loadedForm=!1,this.slotId=0,this.savedFilterCondition=null}configure(modalConfig){var _modalConfig$slotId,_modalConfig$savedFil;modalConfig.removeOnClose=!0,this.setCategory(modalConfig.category),this.setReturnUrl(modalConfig.returnUrl),this.showNewCategory=modalConfig.showNewCategory,this.setSlotId(null!==(_modalConfig$slotId=modalConfig.slotId)&&void 0!==_modalConfig$slotId?_modalConfig$slotId:0),this.setSavedFilterCondition(null!==(_modalConfig$savedFil=modalConfig.savedFilterCondition)&&void 0!==_modalConfig$savedFil?_modalConfig$savedFil:null),this.setCourseId(modalConfig.courseId),super.configure(modalConfig)}setAddOnPageId(id){super.setAddOnPageId(id),this.getBody().find(SELECTORS.ADD_ON_PAGE_FORM_ELEMENT).val(id)}setCategory(category){this.category=category}getCategory(){return this.category}setReturnUrl(url){this.returnUrl=url}getReturnUrl(){return this.returnUrl}setSlotId(slotId){this.slotId=slotId}getSlotId(){return this.slotId}setCourseId(courseId){this.courseId=courseId}getCourseId(){return this.courseId}setSavedFilterCondition(filterCondition){this.savedFilterCondition=filterCondition}getSavedFilterCondition(){return this.savedFilterCondition}moveContentIntoTab(tabContent,tabElement){tabContent.find(SELECTORS.FORM_HEADER).addClass("hidden"),tabContent.wrap(tabElement)}moveTabsIntoTabContent(form){const tabContent=this.getBody().find(SELECTORS.TAB_CONTENT).empty();form.find('[role="tabpanel"]').wrapAll(tabContent)}moveCancelButtonToTabs(form){const cancelButton=form.find(SELECTORS.CANCEL_BUTTON_ELEMENT).addClass("ms-1"),tabFooters=form.find('[data-region="footer"]');cancelButton.closest(SELECTORS.BUTTON_CONTAINER).remove(),cancelButton.clone().appendTo(tabFooters)}loadForm(){const addonpage=this.getAddOnPageId(),returnurl=this.getReturnUrl(),quizcmid=this.quizCmId,bankcmid=this.bankCmId,savedfiltercondition=this.getSavedFilterCondition();return this.setSavedFilterCondition(null),Fragment.loadFragment("mod_quiz","add_random_question_form",this.getContextId(),{addonpage:null!=addonpage?addonpage:null,returnurl:returnurl,quizcmid:quizcmid,bankcmid:bankcmid,slotid:this.getSlotId(),savedfiltercondition:savedfiltercondition}).then(((html,js)=>{const form=(0,_jquery.default)(html);if(!this.getSlotId()){const existingCategoryTabContent=form.find(SELECTORS.EXISTING_CATEGORY_TAB),existingCategoryTab=this.getBody().find(SELECTORS.EXISTING_CATEGORY_CONTAINER),newCategoryTabContent=form.find(SELECTORS.NEW_CATEGORY_TAB),newCategoryTab=this.getBody().find(SELECTORS.NEW_CATEGORY_CONTAINER);this.moveContentIntoTab(existingCategoryTabContent,existingCategoryTab),this.moveContentIntoTab(newCategoryTabContent,newCategoryTab),this.moveTabsIntoTabContent(form)}Templates.replaceNode(this.getBody().find(SELECTORS.TAB_CONTENT),form,js)})).then((()=>{FormChangeChecker.disableAllChecks(),this.getBody()[0].addEventListener("click",(e=>{if(!e.target.closest(SELECTORS.SUBMIT_BUTTON_ELEMENT))return;e.preventDefault(),document.querySelector('#questionscontainer input[name="cmid"]').setAttribute("name",this.quizCmId);if(e.target.closest(SELECTORS.ADD_RANDOM_BUTTON)){var _document$querySelect;const randomcount=document.querySelector(SELECTORS.SELECT_NUMBER_TO_ADD).value,filtercondition=null===(_document$querySelect=document.querySelector(SELECTORS.FILTER_CONDITION_ELEMENT).dataset)||void 0===_document$querySelect?void 0:_document$querySelect.filtercondition;return void this.addQuestions(quizcmid,addonpage,randomcount,filtercondition,"","")}if(e.target.closest(SELECTORS.UPDATE_FILTER_BUTTON)){var _document$querySelect2;const filtercondition=null===(_document$querySelect2=document.querySelector(SELECTORS.FILTER_CONDITION_ELEMENT).dataset)||void 0===_document$querySelect2?void 0:_document$querySelect2.filtercondition;return void this.updateFilterCondition(quizcmid,this.getSlotId(),filtercondition)}e.target.closest(SELECTORS.ADD_NEW_CATEGORY_BUTTON)&&this.addQuestions(quizcmid,addonpage,1,"",document.querySelector(SELECTORS.NEW_CATEGORY_ELEMENT).value,document.querySelector(SELECTORS.PARENT_CATEGORY_ELEMENT).value)}));const switcher=new _bank_switcher.default;this.getModal().on("click",SELECTORS.SWITCH_TO_OTHER_BANK,(()=>{var _document$querySelect3;this.setSavedFilterCondition(null===(_document$querySelect3=document.querySelector(SELECTORS.FILTER_CONDITION_ELEMENT).dataset)||void 0===_document$querySelect3?void 0:_document$querySelect3.filtercondition);try{switcher.show(this,this.courseId,this.getContextId(),this.bankCmId,this.quizCmId)}catch(ex){Notification.exception(ex)}})),this.getModal().get(0).addEventListener("bankSwitched",(async e=>{if(e.detail.cmid>0){const modalConfig={contextId:this.getContextId(),bankCmId:e.detail.cmid,category:this.getCategory(),returnUrl:this.getReturnUrl(),quizCmId:this.quizCmId,title:this.originalTitle,addOnPage:this.getAddOnPageId(),templateContext:{hidden:this.showNewCategory},showNewCategory:this.showNewCategory,slotId:this.getSlotId(),courseId:this.getCourseId()};e.detail.cmid===this.bankCmId&&(modalConfig.savedFilterCondition=this.getSavedFilterCondition());try{await ModalAddRandomQuestion.create(modalConfig),this.destroy()}catch(ex){await Notification.exception(ex)}}}))})).catch(Notification.exception)}async addQuestions(quizcmid,addonpage,randomcount,filtercondition,newcategory,parentcategory){new _pending.default("mod-quiz/modal_add_random_questions");const call={methodname:"mod_quiz_add_random_questions",args:{cmid:quizcmid,addonpage:addonpage,randomcount:randomcount,filtercondition:filtercondition,newcategory:newcategory,parentcategory:parentcategory}};try{const response=await(0,_ajax.call)([call])[0],form=document.querySelector(SELECTORS.FORM_ELEMENT);form.querySelector(SELECTORS.MESSAGE_INPUT).value=response.message,form.submit()}catch(e){Notification.exception(e)}}async updateFilterCondition(quizcmid,slotid,filtercondition){new _pending.default("mod-quiz/modal_add_random_questions");const call={methodname:"mod_quiz_update_filter_condition",args:{cmid:quizcmid,slotid:slotid,filtercondition:filtercondition}};try{const response=await(0,_ajax.call)([call])[0],form=document.querySelector(SELECTORS.FORM_ELEMENT);form.querySelector(SELECTORS.MESSAGE_INPUT).value=response.message,form.submit()}catch(e){Notification.exception(e)}}show(){super.show(this),this.loadedForm||(this.tabHtml=this.getBody(),this.loadForm(window.location.search),this.loadedForm=!0)}}return _exports.default=ModalAddRandomQuestion,_defineProperty(ModalAddRandomQuestion,"TYPE","mod_quiz-quiz-add-random-question"),_defineProperty(ModalAddRandomQuestion,"TEMPLATE","mod_quiz/modal_add_random_question"),ModalAddRandomQuestion.registerModalType(),_exports.default})); //# sourceMappingURL=modal_add_random_question.min.js.map \ No newline at end of file diff --git a/public/mod/quiz/amd/build/modal_add_random_question.min.js.map b/public/mod/quiz/amd/build/modal_add_random_question.min.js.map index cc6cab4e700..871f87a40b4 100644 --- a/public/mod/quiz/amd/build/modal_add_random_question.min.js.map +++ b/public/mod/quiz/amd/build/modal_add_random_question.min.js.map @@ -1 +1 @@ -{"version":3,"file":"modal_add_random_question.min.js","sources":["../src/modal_add_random_question.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Contain the logic for the add random question modal.\n *\n * @module mod_quiz/modal_add_random_question\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport Modal from './add_question_modal';\nimport * as Notification from 'core/notification';\nimport * as Fragment from 'core/fragment';\nimport * as Templates from 'core/templates';\nimport * as FormChangeChecker from 'core_form/changechecker';\nimport {call as fetchMany} from 'core/ajax';\nimport Pending from 'core/pending';\n\nconst SELECTORS = {\n ANCHOR: 'a[href]',\n EXISTING_CATEGORY_CONTAINER: '[data-region=\"existing-category-container\"]',\n EXISTING_CATEGORY_TAB: '#id_existingcategoryheader',\n NEW_CATEGORY_CONTAINER: '[data-region=\"new-category-container\"]',\n NEW_CATEGORY_TAB: '#id_newcategoryheader',\n TAB_CONTENT: '[data-region=\"tab-content\"]',\n ADD_ON_PAGE_FORM_ELEMENT: '[name=\"addonpage\"]',\n ADD_RANDOM_BUTTON: 'input[type=\"submit\"][name=\"addrandom\"]',\n ADD_NEW_CATEGORY_BUTTON: 'input[type=\"submit\"][name=\"newcategory\"]',\n SUBMIT_BUTTON_ELEMENT: 'input[type=\"submit\"][name=\"addrandom\"], '\n + 'input[type=\"submit\"][name=\"newcategory\"], '\n + 'input[type=\"submit\"][name=\"update\"]',\n FORM_HEADER: 'legend',\n SELECT_NUMBER_TO_ADD: '#menurandomcount',\n NEW_CATEGORY_ELEMENT: '#categoryname',\n PARENT_CATEGORY_ELEMENT: '#parentcategory',\n FILTER_CONDITION_ELEMENT: '[data-filtercondition]',\n FORM_ELEMENT: '#add_random_question_form',\n MESSAGE_INPUT: '[name=\"message\"]',\n SWITCH_TO_OTHER_BANK: 'button[data-action=\"switch-question-bank\"]',\n NEW_BANKMOD_ID: 'data-newmodid',\n BANK_SEARCH: '#searchbanks',\n GO_BACK_BUTTON: 'button[data-action=\"go-back\"]',\n UPDATE_FILTER_BUTTON: 'input[type=\"submit\"][name=\"update\"]',\n};\n\nexport default class ModalAddRandomQuestion extends Modal {\n static TYPE = 'mod_quiz-quiz-add-random-question';\n static TEMPLATE = 'mod_quiz/modal_add_random_question';\n\n /**\n * Create the add random question modal.\n *\n * @param {Number} contextId Current context id.\n * @param {Number} bankCmId Current question bank course module id.\n * @param {string} category Category id and category context id comma separated.\n * @param {string} returnUrl URL to return to after form submission.\n * @param {Number} quizCmId Current quiz course module id.\n * @param {boolean} showNewCategory Display the New category tab when selecting random questions.\n */\n static init(\n contextId,\n bankCmId,\n category,\n returnUrl,\n quizCmId,\n showNewCategory = true\n ) {\n const selector = '.menu [data-action=\"addarandomquestion\"], [data-action=\"editrandomquestion\"]';\n document.addEventListener('click', (e) => {\n const trigger = e.target.closest(selector);\n if (!trigger) {\n return;\n }\n e.preventDefault();\n\n if (trigger.dataset.slotid) {\n showNewCategory = false;\n }\n\n ModalAddRandomQuestion.create({\n contextId,\n bankCmId,\n category,\n returnUrl,\n quizCmId,\n showNewCategory,\n title: trigger.dataset.header,\n addOnPage: trigger.dataset.addonpage,\n slotId: trigger.dataset.slotid,\n templateContext: {\n hidden: showNewCategory,\n },\n });\n });\n }\n\n /**\n * Constructor for the Modal.\n *\n * @param {object} root The root jQuery element for the modal\n */\n constructor(root) {\n super(root);\n this.category = null;\n this.returnUrl = null;\n this.quizCmId = null;\n this.loadedForm = false;\n this.slotId = 0;\n this.savedFilterCondition = null;\n }\n\n configure(modalConfig) {\n modalConfig.removeOnClose = true;\n\n this.setCategory(modalConfig.category);\n this.setReturnUrl(modalConfig.returnUrl);\n this.showNewCategory = modalConfig.showNewCategory;\n this.setSlotId(modalConfig.slotId ?? 0);\n this.setSavedFilterCondition(modalConfig.savedFilterCondition ?? null);\n\n super.configure(modalConfig);\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 {int} id\n */\n setAddOnPageId(id) {\n super.setAddOnPageId(id);\n this.getBody().find(SELECTORS.ADD_ON_PAGE_FORM_ELEMENT).val(id);\n }\n\n /**\n * Set the category for this form. The category is a comma separated\n * category id and category context id.\n *\n * @method setCategory\n * @param {string} category\n */\n setCategory(category) {\n this.category = category;\n }\n\n /**\n * Returns the saved category.\n *\n * @method getCategory\n * @return {string}\n */\n getCategory() {\n return this.category;\n }\n\n /**\n * Set the return URL for the form.\n *\n * @method setReturnUrl\n * @param {string} url\n */\n setReturnUrl(url) {\n this.returnUrl = url;\n }\n\n /**\n * Returns the return URL for the form.\n *\n * @method getReturnUrl\n * @return {string}\n */\n getReturnUrl() {\n return this.returnUrl;\n }\n\n /**\n * Set the ID of the quiz slot, if we are editing an existing random question.\n *\n * @param {Number} slotId\n */\n setSlotId(slotId) {\n this.slotId = slotId;\n }\n\n /**\n * Get the current slot ID.\n *\n * @return {Number}\n */\n getSlotId() {\n return this.slotId;\n }\n\n /**\n * Store the current filterCondition JSON string.\n *\n * @param {String} filterCondition\n */\n setSavedFilterCondition(filterCondition) {\n this.savedFilterCondition = filterCondition;\n }\n\n /**\n * Return the saved filterCondition JSON string.\n *\n * @return {String}\n */\n getSavedFilterCondition() {\n return this.savedFilterCondition;\n }\n\n /**\n * Moves a given form element inside (a child of) a given tab element.\n *\n * Hides the 'legend' (e.g. header) element of the form element because the\n * tab has the name.\n *\n * Moves the submit button into a footer element at the bottom of the form\n * element for styling purposes.\n *\n * @method moveContentIntoTab\n * @param {jquery} tabContent The form element to move into the tab.\n * @param {jquey} tabElement The tab element for the form element to move into.\n */\n moveContentIntoTab(tabContent, tabElement) {\n // Hide the header because the tabs show us which part of the form we're\n // looking at.\n tabContent.find(SELECTORS.FORM_HEADER).addClass('hidden');\n // Move the element inside a tab.\n tabContent.wrap(tabElement);\n }\n\n /**\n * Empty the tab content container and move all tabs from the form into the\n * tab container element.\n *\n * @method moveTabsIntoTabContent\n * @param {jquery} form The form element.\n */\n moveTabsIntoTabContent(form) {\n // Empty it to remove the loading icon.\n const tabContent = this.getBody().find(SELECTORS.TAB_CONTENT).empty();\n // Make sure all tabs are inside the tab content element.\n form.find('[role=\"tabpanel\"]').wrapAll(tabContent);\n }\n\n /**\n * Make sure all of the tabs have a cancel button in their fotter to sit along\n * side the submit button.\n *\n * @method moveCancelButtonToTabs\n * @param {jquey} form The form element.\n */\n moveCancelButtonToTabs(form) {\n const cancelButton = form.find(SELECTORS.CANCEL_BUTTON_ELEMENT).addClass('ms-1');\n const tabFooters = form.find('[data-region=\"footer\"]');\n // Remove the buttons container element.\n cancelButton.closest(SELECTORS.BUTTON_CONTAINER).remove();\n cancelButton.clone().appendTo(tabFooters);\n }\n\n /**\n * Load the add random question form in a fragement and perform some transformation\n * on the HTML to convert it into tabs for rendering in the modal.\n *\n * @method loadForm\n * @return {promise} Resolved with form HTML and JS.\n */\n loadForm() {\n const addonpage = this.getAddOnPageId();\n const returnurl = this.getReturnUrl();\n const quizcmid = this.quizCmId;\n const bankcmid = this.bankCmId;\n const savedfiltercondition = this.getSavedFilterCondition();\n this.setSavedFilterCondition(null);\n\n return Fragment.loadFragment(\n 'mod_quiz',\n 'add_random_question_form',\n this.getContextId(),\n {\n addonpage: addonpage ?? null,\n returnurl,\n quizcmid,\n bankcmid,\n slotid: this.getSlotId(),\n savedfiltercondition,\n }\n )\n .then((html, js) => {\n const form = $(html);\n if (!this.getSlotId()) {\n const existingCategoryTabContent = form.find(SELECTORS.EXISTING_CATEGORY_TAB);\n const existingCategoryTab = this.getBody().find(SELECTORS.EXISTING_CATEGORY_CONTAINER);\n const newCategoryTabContent = form.find(SELECTORS.NEW_CATEGORY_TAB);\n const newCategoryTab = this.getBody().find(SELECTORS.NEW_CATEGORY_CONTAINER);\n\n // Transform the form into tabs for better rendering in the modal.\n this.moveContentIntoTab(existingCategoryTabContent, existingCategoryTab);\n this.moveContentIntoTab(newCategoryTabContent, newCategoryTab);\n this.moveTabsIntoTabContent(form);\n }\n\n Templates.replaceNode(this.getBody().find(SELECTORS.TAB_CONTENT), form, js);\n return;\n })\n .then(() => {\n // Make sure the form change checker is disabled otherwise it'll stop the user from navigating away from the\n // page once the modal is hidden.\n FormChangeChecker.disableAllChecks();\n\n // Add question to quiz.\n this.getBody()[0].addEventListener('click', (e) => {\n const button = e.target.closest(SELECTORS.SUBMIT_BUTTON_ELEMENT);\n if (!button) {\n return;\n }\n e.preventDefault();\n\n // Intercept the submission to adjust the POST params so that the quiz mod id is set and not the bank module id.\n document.querySelector('#questionscontainer input[name=\"cmid\"]').setAttribute('name', this.quizCmId);\n\n // Add Random questions if the add random button was clicked.\n const addRandomButton = e.target.closest(SELECTORS.ADD_RANDOM_BUTTON);\n if (addRandomButton) {\n const randomcount = document.querySelector(SELECTORS.SELECT_NUMBER_TO_ADD).value;\n const filtercondition = document.querySelector(SELECTORS.FILTER_CONDITION_ELEMENT).dataset?.filtercondition;\n\n this.addQuestions(quizcmid, addonpage, randomcount, filtercondition, '', '');\n return;\n }\n // Update the filter condition for the slot if the update button was clicked.\n const updateFilterButton = e.target.closest(SELECTORS.UPDATE_FILTER_BUTTON);\n if (updateFilterButton) {\n const filtercondition = document.querySelector(SELECTORS.FILTER_CONDITION_ELEMENT).dataset?.filtercondition;\n this.updateFilterCondition(quizcmid, this.getSlotId(), filtercondition);\n return;\n }\n // Add new category if the add category button was clicked.\n const addCategoryButton = e.target.closest(SELECTORS.ADD_NEW_CATEGORY_BUTTON);\n if (addCategoryButton) {\n this.addQuestions(\n quizcmid,\n addonpage,\n 1,\n '',\n document.querySelector(SELECTORS.NEW_CATEGORY_ELEMENT).value,\n document.querySelector(SELECTORS.PARENT_CATEGORY_ELEMENT).value\n );\n return;\n }\n });\n\n this.getModal().on('click', SELECTORS.SWITCH_TO_OTHER_BANK, () => {\n this.setSavedFilterCondition(\n document.querySelector(SELECTORS.FILTER_CONDITION_ELEMENT).dataset?.filtercondition\n );\n this.handleSwitchBankContentReload(SELECTORS.BANK_SEARCH)\n .then(function(ModalQuizQuestionBank) {\n $(SELECTORS.BANK_SEARCH)?.on('change', (e) => {\n const bankCmId = $(e.currentTarget).val();\n // Have to recreate the modal as we have already used the body for the switch bank content.\n if (bankCmId > 0) {\n ModalAddRandomQuestion.create({\n 'contextId': ModalQuizQuestionBank.getContextId(),\n 'bankCmId': bankCmId,\n 'category': ModalQuizQuestionBank.getCategory(),\n 'returnUrl': ModalQuizQuestionBank.getReturnUrl(),\n 'quizCmId': ModalQuizQuestionBank.quizCmId,\n 'title': ModalQuizQuestionBank.originalTitle,\n 'addOnPage': ModalQuizQuestionBank.getAddOnPageId(),\n 'templateContext': {hidden: ModalQuizQuestionBank.showNewCategory},\n 'showNewCategory': ModalQuizQuestionBank.showNewCategory,\n 'slotId': ModalQuizQuestionBank.getSlotId(),\n })\n .then(ModalQuizQuestionBank.destroy())\n .catch(Notification.exception);\n }\n });\n return ModalQuizQuestionBank;\n });\n });\n\n this.getModal().on('click', SELECTORS.GO_BACK_BUTTON, (e) => {\n const anchorElement = $(e.currentTarget);\n // Have to recreate the modal as we have already used the body for the switch bank content.\n ModalAddRandomQuestion.create({\n 'contextId': this.getContextId(),\n 'bankCmId': anchorElement.attr('value'),\n 'category': this.getCategory(),\n 'returnUrl': this.getReturnUrl(),\n 'quizCmId': this.quizCmId,\n 'title': this.originalTitle,\n 'addOnPage': this.getAddOnPageId(),\n 'templateContext': {hidden: this.showNewCategory},\n 'showNewCategory': this.showNewCategory,\n 'savedFilterCondition': this.getSavedFilterCondition(),\n 'slotId': this.getSlotId(),\n }).then(this.destroy()).catch(Notification.exception);\n });\n\n this.getModal().on('click', SELECTORS.ANCHOR, (e) => {\n const anchorElement = $(e.currentTarget);\n // Have to recreate the modal as we have already used the body for the switch bank content.\n if (anchorElement.closest('a[' + SELECTORS.NEW_BANKMOD_ID + ']').length) {\n ModalAddRandomQuestion.create({\n 'contextId': this.getContextId(),\n 'bankCmId': anchorElement.attr(SELECTORS.NEW_BANKMOD_ID),\n 'category': this.getCategory(),\n 'returnUrl': this.getReturnUrl(),\n 'quizCmId': this.quizCmId,\n 'title': this.originalTitle,\n 'addOnPage': this.getAddOnPageId(),\n 'templateContext': {hidden: this.showNewCategory},\n 'showNewCategory': this.showNewCategory,\n 'slotId': this.getSlotId(),\n }).then(this.destroy()).catch(Notification.exception);\n }\n });\n })\n .catch(Notification.exception);\n }\n\n /**\n * Call web service function to add random questions\n *\n * @param {number} quizcmid the course module id of the quiz to add questions to.\n * @param {number} addonpage the page where random questions will be added to\n * @param {number} randomcount Number of random questions\n * @param {string} filtercondition Filter condition\n * @param {string} newcategory add new category\n * @param {string} parentcategory parent category of new category\n */\n async addQuestions(\n quizcmid,\n addonpage,\n randomcount,\n filtercondition,\n newcategory,\n parentcategory\n ) {\n // We do not need to resolve this Pending because the form submission will result in a page redirect.\n new Pending('mod-quiz/modal_add_random_questions');\n const call = {\n methodname: 'mod_quiz_add_random_questions',\n args: {\n cmid: quizcmid,\n addonpage,\n randomcount,\n filtercondition,\n newcategory,\n parentcategory,\n }\n };\n try {\n const response = await fetchMany([call])[0];\n const form = document.querySelector(SELECTORS.FORM_ELEMENT);\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 * Call web service function to update the filter condition for an existing slot.\n *\n * @param {number} quizcmid the course module id of the quiz.\n * @param {number} slotid The slot the random question is in.\n * @param {string} filtercondition The new filter condition.\n */\n async updateFilterCondition(\n quizcmid,\n slotid,\n filtercondition,\n ) {\n // We do not need to resolve this Pending because the form submission will result in a page redirect.\n new Pending('mod-quiz/modal_add_random_questions');\n const call = {\n methodname: 'mod_quiz_update_filter_condition',\n args: {\n cmid: quizcmid,\n slotid,\n filtercondition,\n }\n };\n try {\n const response = await fetchMany([call])[0];\n const form = document.querySelector(SELECTORS.FORM_ELEMENT);\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 * Override the modal show function to load the form when this modal is first\n * shown.\n *\n * @method show\n */\n show() {\n super.show(this);\n\n if (!this.loadedForm) {\n this.tabHtml = this.getBody();\n this.loadForm(window.location.search);\n this.loadedForm = true;\n }\n }\n}\n\nModalAddRandomQuestion.registerModalType();\n"],"names":["SELECTORS","ANCHOR","EXISTING_CATEGORY_CONTAINER","EXISTING_CATEGORY_TAB","NEW_CATEGORY_CONTAINER","NEW_CATEGORY_TAB","TAB_CONTENT","ADD_ON_PAGE_FORM_ELEMENT","ADD_RANDOM_BUTTON","ADD_NEW_CATEGORY_BUTTON","SUBMIT_BUTTON_ELEMENT","FORM_HEADER","SELECT_NUMBER_TO_ADD","NEW_CATEGORY_ELEMENT","PARENT_CATEGORY_ELEMENT","FILTER_CONDITION_ELEMENT","FORM_ELEMENT","MESSAGE_INPUT","SWITCH_TO_OTHER_BANK","NEW_BANKMOD_ID","BANK_SEARCH","GO_BACK_BUTTON","UPDATE_FILTER_BUTTON","ModalAddRandomQuestion","Modal","contextId","bankCmId","category","returnUrl","quizCmId","showNewCategory","document","addEventListener","e","trigger","target","closest","preventDefault","dataset","slotid","create","title","header","addOnPage","addonpage","slotId","templateContext","hidden","constructor","root","loadedForm","savedFilterCondition","configure","modalConfig","removeOnClose","setCategory","setReturnUrl","setSlotId","setSavedFilterCondition","setAddOnPageId","id","getBody","find","val","getCategory","this","url","getReturnUrl","getSlotId","filterCondition","getSavedFilterCondition","moveContentIntoTab","tabContent","tabElement","addClass","wrap","moveTabsIntoTabContent","form","empty","wrapAll","moveCancelButtonToTabs","cancelButton","CANCEL_BUTTON_ELEMENT","tabFooters","BUTTON_CONTAINER","remove","clone","appendTo","loadForm","getAddOnPageId","returnurl","quizcmid","bankcmid","savedfiltercondition","Fragment","loadFragment","getContextId","then","html","js","existingCategoryTabContent","existingCategoryTab","newCategoryTabContent","newCategoryTab","Templates","replaceNode","FormChangeChecker","disableAllChecks","querySelector","setAttribute","randomcount","value","filtercondition","_document$querySelect","addQuestions","_document$querySelect2","updateFilterCondition","getModal","on","_document$querySelect3","handleSwitchBankContentReload","ModalQuizQuestionBank","currentTarget","originalTitle","destroy","catch","Notification","exception","anchorElement","attr","length","newcategory","parentcategory","Pending","call","methodname","args","cmid","response","message","submit","show","tabHtml","window","location","search","registerModalType"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"modal_add_random_question.min.js","sources":["../src/modal_add_random_question.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Contain the logic for the add random question modal.\n *\n * @module mod_quiz/modal_add_random_question\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport Modal from './add_question_modal';\nimport * as Notification from 'core/notification';\nimport * as Fragment from 'core/fragment';\nimport * as Templates from 'core/templates';\nimport * as FormChangeChecker from 'core_form/changechecker';\nimport {call as fetchMany} from 'core/ajax';\nimport Pending from 'core/pending';\nimport BankSwitcher from 'core_question/bank_switcher';\n\nconst SELECTORS = {\n ANCHOR: 'a[href]',\n EXISTING_CATEGORY_CONTAINER: '[data-region=\"existing-category-container\"]',\n EXISTING_CATEGORY_TAB: '#id_existingcategoryheader',\n NEW_CATEGORY_CONTAINER: '[data-region=\"new-category-container\"]',\n NEW_CATEGORY_TAB: '#id_newcategoryheader',\n TAB_CONTENT: '[data-region=\"tab-content\"]',\n ADD_ON_PAGE_FORM_ELEMENT: '[name=\"addonpage\"]',\n ADD_RANDOM_BUTTON: 'input[type=\"submit\"][name=\"addrandom\"]',\n ADD_NEW_CATEGORY_BUTTON: 'input[type=\"submit\"][name=\"newcategory\"]',\n SUBMIT_BUTTON_ELEMENT: 'input[type=\"submit\"][name=\"addrandom\"], '\n + 'input[type=\"submit\"][name=\"newcategory\"], '\n + 'input[type=\"submit\"][name=\"update\"]',\n FORM_HEADER: 'legend',\n SELECT_NUMBER_TO_ADD: '#menurandomcount',\n NEW_CATEGORY_ELEMENT: '#categoryname',\n PARENT_CATEGORY_ELEMENT: '#parentcategory',\n FILTER_CONDITION_ELEMENT: '[data-filtercondition]',\n FORM_ELEMENT: '#add_random_question_form',\n MESSAGE_INPUT: '[name=\"message\"]',\n SWITCH_TO_OTHER_BANK: 'button[data-action=\"switch-question-bank\"]',\n NEW_BANKMOD_ID: 'data-newmodid',\n BANK_SEARCH: '#searchbanks',\n GO_BACK_BUTTON: 'button[data-action=\"go-back\"]',\n UPDATE_FILTER_BUTTON: 'input[type=\"submit\"][name=\"update\"]',\n};\n\nexport default class ModalAddRandomQuestion extends Modal {\n static TYPE = 'mod_quiz-quiz-add-random-question';\n static TEMPLATE = 'mod_quiz/modal_add_random_question';\n\n /**\n * Create the add random question modal.\n *\n * @param {Number} contextId Current context id.\n * @param {Number} bankCmId Current question bank course module id.\n * @param {string} category Category id and category context id comma separated.\n * @param {string} returnUrl URL to return to after form submission.\n * @param {Number} quizCmId Current quiz course module id.\n * @param {boolean} showNewCategory Display the New category tab when selecting random questions.\n * @param {Number} courseId Current course id, required for switching banks.\n */\n static init(\n contextId,\n bankCmId,\n category,\n returnUrl,\n quizCmId,\n showNewCategory = true,\n courseId,\n ) {\n const selector = '.menu [data-action=\"addarandomquestion\"], [data-action=\"editrandomquestion\"]';\n document.addEventListener('click', (e) => {\n const trigger = e.target.closest(selector);\n if (!trigger) {\n return;\n }\n e.preventDefault();\n\n if (trigger.dataset.slotid) {\n showNewCategory = false;\n }\n\n ModalAddRandomQuestion.create({\n contextId,\n bankCmId,\n category,\n returnUrl,\n quizCmId,\n showNewCategory,\n title: trigger.dataset.header,\n addOnPage: trigger.dataset.addonpage,\n slotId: trigger.dataset.slotid,\n templateContext: {\n hidden: showNewCategory,\n },\n courseId,\n });\n });\n }\n\n /**\n * Constructor for the Modal.\n *\n * @param {object} root The root jQuery element for the modal\n */\n constructor(root) {\n super(root);\n this.category = null;\n this.returnUrl = null;\n this.quizCmId = null;\n this.loadedForm = false;\n this.slotId = 0;\n this.savedFilterCondition = null;\n }\n\n configure(modalConfig) {\n modalConfig.removeOnClose = true;\n\n this.setCategory(modalConfig.category);\n this.setReturnUrl(modalConfig.returnUrl);\n this.showNewCategory = modalConfig.showNewCategory;\n this.setSlotId(modalConfig.slotId ?? 0);\n this.setSavedFilterCondition(modalConfig.savedFilterCondition ?? null);\n this.setCourseId(modalConfig.courseId);\n\n super.configure(modalConfig);\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 {int} id\n */\n setAddOnPageId(id) {\n super.setAddOnPageId(id);\n this.getBody().find(SELECTORS.ADD_ON_PAGE_FORM_ELEMENT).val(id);\n }\n\n /**\n * Set the category for this form. The category is a comma separated\n * category id and category context id.\n *\n * @method setCategory\n * @param {string} category\n */\n setCategory(category) {\n this.category = category;\n }\n\n /**\n * Returns the saved category.\n *\n * @method getCategory\n * @return {string}\n */\n getCategory() {\n return this.category;\n }\n\n /**\n * Set the return URL for the form.\n *\n * @method setReturnUrl\n * @param {string} url\n */\n setReturnUrl(url) {\n this.returnUrl = url;\n }\n\n /**\n * Returns the return URL for the form.\n *\n * @method getReturnUrl\n * @return {string}\n */\n getReturnUrl() {\n return this.returnUrl;\n }\n\n /**\n * Set the ID of the quiz slot, if we are editing an existing random question.\n *\n * @param {Number} slotId\n */\n setSlotId(slotId) {\n this.slotId = slotId;\n }\n\n /**\n * Get the current slot ID.\n *\n * @return {Number}\n */\n getSlotId() {\n return this.slotId;\n }\n\n /**\n * Store the ID of the current course\n *\n * @param {Number} courseId\n */\n setCourseId(courseId) {\n this.courseId = courseId;\n }\n\n /**\n * Get the current course ID.\n *\n * @return {Number}\n */\n getCourseId() {\n return this.courseId;\n }\n\n /**\n * Store the current filterCondition JSON string.\n *\n * @param {String} filterCondition\n */\n setSavedFilterCondition(filterCondition) {\n this.savedFilterCondition = filterCondition;\n }\n\n /**\n * Return the saved filterCondition JSON string.\n *\n * @return {String}\n */\n getSavedFilterCondition() {\n return this.savedFilterCondition;\n }\n\n /**\n * Moves a given form element inside (a child of) a given tab element.\n *\n * Hides the 'legend' (e.g. header) element of the form element because the\n * tab has the name.\n *\n * Moves the submit button into a footer element at the bottom of the form\n * element for styling purposes.\n *\n * @method moveContentIntoTab\n * @param {jquery} tabContent The form element to move into the tab.\n * @param {jquey} tabElement The tab element for the form element to move into.\n */\n moveContentIntoTab(tabContent, tabElement) {\n // Hide the header because the tabs show us which part of the form we're\n // looking at.\n tabContent.find(SELECTORS.FORM_HEADER).addClass('hidden');\n // Move the element inside a tab.\n tabContent.wrap(tabElement);\n }\n\n /**\n * Empty the tab content container and move all tabs from the form into the\n * tab container element.\n *\n * @method moveTabsIntoTabContent\n * @param {jquery} form The form element.\n */\n moveTabsIntoTabContent(form) {\n // Empty it to remove the loading icon.\n const tabContent = this.getBody().find(SELECTORS.TAB_CONTENT).empty();\n // Make sure all tabs are inside the tab content element.\n form.find('[role=\"tabpanel\"]').wrapAll(tabContent);\n }\n\n /**\n * Make sure all of the tabs have a cancel button in their fotter to sit along\n * side the submit button.\n *\n * @method moveCancelButtonToTabs\n * @param {jquey} form The form element.\n */\n moveCancelButtonToTabs(form) {\n const cancelButton = form.find(SELECTORS.CANCEL_BUTTON_ELEMENT).addClass('ms-1');\n const tabFooters = form.find('[data-region=\"footer\"]');\n // Remove the buttons container element.\n cancelButton.closest(SELECTORS.BUTTON_CONTAINER).remove();\n cancelButton.clone().appendTo(tabFooters);\n }\n\n /**\n * Load the add random question form in a fragement and perform some transformation\n * on the HTML to convert it into tabs for rendering in the modal.\n *\n * @method loadForm\n * @return {promise} Resolved with form HTML and JS.\n */\n loadForm() {\n const addonpage = this.getAddOnPageId();\n const returnurl = this.getReturnUrl();\n const quizcmid = this.quizCmId;\n const bankcmid = this.bankCmId;\n const savedfiltercondition = this.getSavedFilterCondition();\n this.setSavedFilterCondition(null);\n\n return Fragment.loadFragment(\n 'mod_quiz',\n 'add_random_question_form',\n this.getContextId(),\n {\n addonpage: addonpage ?? null,\n returnurl,\n quizcmid,\n bankcmid,\n slotid: this.getSlotId(),\n savedfiltercondition,\n }\n )\n .then((html, js) => {\n const form = $(html);\n if (!this.getSlotId()) {\n const existingCategoryTabContent = form.find(SELECTORS.EXISTING_CATEGORY_TAB);\n const existingCategoryTab = this.getBody().find(SELECTORS.EXISTING_CATEGORY_CONTAINER);\n const newCategoryTabContent = form.find(SELECTORS.NEW_CATEGORY_TAB);\n const newCategoryTab = this.getBody().find(SELECTORS.NEW_CATEGORY_CONTAINER);\n\n // Transform the form into tabs for better rendering in the modal.\n this.moveContentIntoTab(existingCategoryTabContent, existingCategoryTab);\n this.moveContentIntoTab(newCategoryTabContent, newCategoryTab);\n this.moveTabsIntoTabContent(form);\n }\n\n Templates.replaceNode(this.getBody().find(SELECTORS.TAB_CONTENT), form, js);\n return;\n })\n .then(() => {\n // Make sure the form change checker is disabled otherwise it'll stop the user from navigating away from the\n // page once the modal is hidden.\n FormChangeChecker.disableAllChecks();\n\n // Add question to quiz.\n this.getBody()[0].addEventListener('click', (e) => {\n const button = e.target.closest(SELECTORS.SUBMIT_BUTTON_ELEMENT);\n if (!button) {\n return;\n }\n e.preventDefault();\n\n // Intercept the submission to adjust the POST params so that the quiz mod id is set and not the bank module id.\n document.querySelector('#questionscontainer input[name=\"cmid\"]').setAttribute('name', this.quizCmId);\n\n // Add Random questions if the add random button was clicked.\n const addRandomButton = e.target.closest(SELECTORS.ADD_RANDOM_BUTTON);\n if (addRandomButton) {\n const randomcount = document.querySelector(SELECTORS.SELECT_NUMBER_TO_ADD).value;\n const filtercondition = document.querySelector(SELECTORS.FILTER_CONDITION_ELEMENT).dataset?.filtercondition;\n\n this.addQuestions(quizcmid, addonpage, randomcount, filtercondition, '', '');\n return;\n }\n // Update the filter condition for the slot if the update button was clicked.\n const updateFilterButton = e.target.closest(SELECTORS.UPDATE_FILTER_BUTTON);\n if (updateFilterButton) {\n const filtercondition = document.querySelector(SELECTORS.FILTER_CONDITION_ELEMENT).dataset?.filtercondition;\n this.updateFilterCondition(quizcmid, this.getSlotId(), filtercondition);\n return;\n }\n // Add new category if the add category button was clicked.\n const addCategoryButton = e.target.closest(SELECTORS.ADD_NEW_CATEGORY_BUTTON);\n if (addCategoryButton) {\n this.addQuestions(\n quizcmid,\n addonpage,\n 1,\n '',\n document.querySelector(SELECTORS.NEW_CATEGORY_ELEMENT).value,\n document.querySelector(SELECTORS.PARENT_CATEGORY_ELEMENT).value\n );\n return;\n }\n });\n\n const switcher = new BankSwitcher();\n this.getModal().on('click', SELECTORS.SWITCH_TO_OTHER_BANK, () => {\n this.setSavedFilterCondition(\n document.querySelector(SELECTORS.FILTER_CONDITION_ELEMENT).dataset?.filtercondition\n );\n try {\n switcher.show(this, this.courseId, this.getContextId(), this.bankCmId, this.quizCmId);\n } catch (ex) {\n Notification.exception(ex);\n }\n });\n this.getModal().get(0).addEventListener('bankSwitched', async(e) => {\n if (e.detail.cmid > 0) {\n const modalConfig = {\n 'contextId': this.getContextId(),\n 'bankCmId': e.detail.cmid,\n 'category': this.getCategory(),\n 'returnUrl': this.getReturnUrl(),\n 'quizCmId': this.quizCmId,\n 'title': this.originalTitle,\n 'addOnPage': this.getAddOnPageId(),\n 'templateContext': {hidden: this.showNewCategory},\n 'showNewCategory': this.showNewCategory,\n 'slotId': this.getSlotId(),\n 'courseId': this.getCourseId(),\n };\n if (e.detail.cmid === this.bankCmId) {\n // We're displaying the same bank as before, keep the current filters.\n modalConfig.savedFilterCondition = this.getSavedFilterCondition();\n }\n try {\n await ModalAddRandomQuestion.create(modalConfig);\n this.destroy();\n } catch (ex) {\n await Notification.exception(ex);\n }\n }\n });\n })\n .catch(Notification.exception);\n }\n\n /**\n * Call web service function to add random questions\n *\n * @param {number} quizcmid the course module id of the quiz to add questions to.\n * @param {number} addonpage the page where random questions will be added to\n * @param {number} randomcount Number of random questions\n * @param {string} filtercondition Filter condition\n * @param {string} newcategory add new category\n * @param {string} parentcategory parent category of new category\n */\n async addQuestions(\n quizcmid,\n addonpage,\n randomcount,\n filtercondition,\n newcategory,\n parentcategory\n ) {\n // We do not need to resolve this Pending because the form submission will result in a page redirect.\n new Pending('mod-quiz/modal_add_random_questions');\n const call = {\n methodname: 'mod_quiz_add_random_questions',\n args: {\n cmid: quizcmid,\n addonpage,\n randomcount,\n filtercondition,\n newcategory,\n parentcategory,\n }\n };\n try {\n const response = await fetchMany([call])[0];\n const form = document.querySelector(SELECTORS.FORM_ELEMENT);\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 * Call web service function to update the filter condition for an existing slot.\n *\n * @param {number} quizcmid the course module id of the quiz.\n * @param {number} slotid The slot the random question is in.\n * @param {string} filtercondition The new filter condition.\n */\n async updateFilterCondition(\n quizcmid,\n slotid,\n filtercondition,\n ) {\n // We do not need to resolve this Pending because the form submission will result in a page redirect.\n new Pending('mod-quiz/modal_add_random_questions');\n const call = {\n methodname: 'mod_quiz_update_filter_condition',\n args: {\n cmid: quizcmid,\n slotid,\n filtercondition,\n }\n };\n try {\n const response = await fetchMany([call])[0];\n const form = document.querySelector(SELECTORS.FORM_ELEMENT);\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 * Override the modal show function to load the form when this modal is first\n * shown.\n *\n * @method show\n */\n show() {\n super.show(this);\n\n if (!this.loadedForm) {\n this.tabHtml = this.getBody();\n this.loadForm(window.location.search);\n this.loadedForm = true;\n }\n }\n}\n\nModalAddRandomQuestion.registerModalType();\n"],"names":["SELECTORS","ANCHOR","EXISTING_CATEGORY_CONTAINER","EXISTING_CATEGORY_TAB","NEW_CATEGORY_CONTAINER","NEW_CATEGORY_TAB","TAB_CONTENT","ADD_ON_PAGE_FORM_ELEMENT","ADD_RANDOM_BUTTON","ADD_NEW_CATEGORY_BUTTON","SUBMIT_BUTTON_ELEMENT","FORM_HEADER","SELECT_NUMBER_TO_ADD","NEW_CATEGORY_ELEMENT","PARENT_CATEGORY_ELEMENT","FILTER_CONDITION_ELEMENT","FORM_ELEMENT","MESSAGE_INPUT","SWITCH_TO_OTHER_BANK","NEW_BANKMOD_ID","BANK_SEARCH","GO_BACK_BUTTON","UPDATE_FILTER_BUTTON","ModalAddRandomQuestion","Modal","contextId","bankCmId","category","returnUrl","quizCmId","showNewCategory","courseId","document","addEventListener","e","trigger","target","closest","preventDefault","dataset","slotid","create","title","header","addOnPage","addonpage","slotId","templateContext","hidden","constructor","root","loadedForm","savedFilterCondition","configure","modalConfig","removeOnClose","setCategory","setReturnUrl","setSlotId","setSavedFilterCondition","setCourseId","setAddOnPageId","id","getBody","find","val","getCategory","this","url","getReturnUrl","getSlotId","getCourseId","filterCondition","getSavedFilterCondition","moveContentIntoTab","tabContent","tabElement","addClass","wrap","moveTabsIntoTabContent","form","empty","wrapAll","moveCancelButtonToTabs","cancelButton","CANCEL_BUTTON_ELEMENT","tabFooters","BUTTON_CONTAINER","remove","clone","appendTo","loadForm","getAddOnPageId","returnurl","quizcmid","bankcmid","savedfiltercondition","Fragment","loadFragment","getContextId","then","html","js","existingCategoryTabContent","existingCategoryTab","newCategoryTabContent","newCategoryTab","Templates","replaceNode","FormChangeChecker","disableAllChecks","querySelector","setAttribute","randomcount","value","filtercondition","_document$querySelect","addQuestions","_document$querySelect2","updateFilterCondition","switcher","BankSwitcher","getModal","on","_document$querySelect3","show","ex","Notification","exception","get","async","detail","cmid","originalTitle","destroy","catch","newcategory","parentcategory","Pending","call","methodname","args","response","message","submit","tabHtml","window","location","search","registerModalType"],"mappings":"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"} \ No newline at end of file diff --git a/public/mod/quiz/amd/build/modal_quiz_question_bank.min.js b/public/mod/quiz/amd/build/modal_quiz_question_bank.min.js index 9afcb6bfa3c..114713c5276 100644 --- a/public/mod/quiz/amd/build/modal_quiz_question_bank.min.js +++ b/public/mod/quiz/amd/build/modal_quiz_question_bank.min.js @@ -1,3 +1,3 @@ -define("mod_quiz/modal_quiz_question_bank",["exports","./add_question_modal","core/fragment","core_form/changechecker","core/modal_events","core/notification"],(function(_exports,_add_question_modal,Fragment,FormChangeChecker,ModalEvents,Notification){var obj;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}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_add_question_modal=(obj=_add_question_modal)&&obj.__esModule?obj:{default:obj},Fragment=_interopRequireWildcard(Fragment),FormChangeChecker=_interopRequireWildcard(FormChangeChecker),ModalEvents=_interopRequireWildcard(ModalEvents),Notification=_interopRequireWildcard(Notification);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",SELECTORS_SWITCH_TO_OTHER_BANK='button[data-action="switch-question-bank"]',SELECTORS_NEW_BANKMOD_ID="data-newmodid",SELECTORS_BANK_SEARCH="#searchbanks",SELECTORS_GO_BACK_BUTTON='button[data-action="go-back"]',SELECTORS_ADD_ON_PAGE_FORM_ELEMENT='input[name="addonpage"]',SELECTORS_CMID_FORM_ELEMENT='form#questionsubmit input[name="cmid"]';class ModalQuizQuestionBank extends _add_question_modal.default{static init(contextId,bankCmId,quizCmId){document.addEventListener("click",(e=>{const trigger=e.target.closest('.menu [data-action="questionbank"]');trigger&&(e.preventDefault(),ModalQuizQuestionBank.create({contextId:contextId,quizCmId:quizCmId,bankCmId:bankCmId,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.hideFooter(),this.setTitle(this.originalTitle),this.setBody(Fragment.loadFragment("mod_quiz","quiz_question_bank",this.getContextId(),{querystring:querystring,quizcmid:this.quizCmId,bankcmid:this.bankCmId}))}handleAddToQuizEvent(e,anchorElement){const href=new URL(anchorElement.getAttribute("href"));href.searchParams.set("addonpage",this.getAddOnPageId()),href.searchParams.set("cmid",this.quizCmId),anchorElement.setAttribute("href",href)}registerEventListeners(){super.registerEventListeners(this),this.getModal().on("submit",SELECTORS_ADD_QUESTIONS_FORM,(e=>{const formElement=e.currentTarget;document.querySelector(SELECTORS_ADD_ON_PAGE_FORM_ELEMENT).setAttribute("value",this.getAddOnPageId()),document.querySelector(SELECTORS_CMID_FORM_ELEMENT).setAttribute("value",this.quizCmId);const actionUrl=new URL(formElement.getAttribute("action"));actionUrl.searchParams.set("cmid",this.quizCmId),formElement.setAttribute("action",actionUrl.toString())})),this.getModal().on("click",SELECTORS_SWITCH_TO_OTHER_BANK,(()=>{this.handleSwitchBankContentReload(SELECTORS_BANK_SEARCH).then((function(ModalQuizQuestionBank){var _document$querySelect;null===(_document$querySelect=document.querySelector(SELECTORS_BANK_SEARCH))||void 0===_document$querySelect||_document$querySelect.addEventListener("change",(e=>{const bankCmId=e.currentTarget.value;bankCmId>0&&(ModalQuizQuestionBank.bankCmId=bankCmId,ModalQuizQuestionBank.reloadBodyContent(window.location.search))})),document.querySelector(SELECTORS_GO_BACK_BUTTON).addEventListener("click",(e=>{ModalQuizQuestionBank.bankCmId=e.currentTarget.value,ModalQuizQuestionBank.reloadBodyContent(window.location.search)}))})).catch(Notification.exception)})),this.getModal().on("click",SELECTORS_ANCHOR,(e=>{const anchorElement=e.currentTarget;if(anchorElement.closest(SELECTORS_ADD_TO_QUIZ_CONTAINER))this.handleAddToQuizEvent(e,anchorElement);else if(!anchorElement.closest(SELECTORS_PREVIEW_CONTAINER)&&!anchorElement.closest(SELECTORS_SORTERS)){if(anchorElement.closest("a["+SELECTORS_NEW_BANKMOD_ID+"]")){this.bankCmId=anchorElement.getAttribute(SELECTORS_NEW_BANKMOD_ID);const url=new URL(location.href);url.searchParams.delete("filter"),history.pushState({},"",url)}e.preventDefault(),this.reloadBodyContent(anchorElement.search)}})),this.getRoot().on(ModalEvents.bodyRendered,(()=>{FormChangeChecker.disableAllChecks()}))}}return _exports.default=ModalQuizQuestionBank,function(obj,key,value){key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value}(ModalQuizQuestionBank,"TYPE","mod_quiz-quiz-question-bank"),ModalQuizQuestionBank.registerModalType(),_exports.default})); +define("mod_quiz/modal_quiz_question_bank",["exports","./add_question_modal","core/fragment","core_form/changechecker","core/modal_events","core/notification","core_question/bank_switcher"],(function(_exports,_add_question_modal,Fragment,FormChangeChecker,ModalEvents,Notification,_bank_switcher){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,_add_question_modal=_interopRequireDefault(_add_question_modal),Fragment=_interopRequireWildcard(Fragment),FormChangeChecker=_interopRequireWildcard(FormChangeChecker),ModalEvents=_interopRequireWildcard(ModalEvents),Notification=_interopRequireWildcard(Notification),_bank_switcher=_interopRequireDefault(_bank_switcher);const SELECTORS_ADD_TO_QUIZ_CONTAINER="td.addtoquizaction",SELECTORS_ANCHOR="a[href]",SELECTORS_ADD_QUESTIONS_FORM="form#questionsubmit",SELECTORS_SWITCH_TO_OTHER_BANK='button[data-action="switch-question-bank"]',SELECTORS_ADD_ON_PAGE_FORM_ELEMENT='input[name="addonpage"]',SELECTORS_CMID_FORM_ELEMENT='form#questionsubmit input[name="cmid"]';class ModalQuizQuestionBank extends _add_question_modal.default{static init(contextId,bankCmId,quizCmId,courseId){document.addEventListener("click",(e=>{const trigger=e.target.closest('.menu [data-action="questionbank"]');trigger&&(e.preventDefault(),ModalQuizQuestionBank.create({contextId:contextId,quizCmId:quizCmId,bankCmId:bankCmId,title:trigger.dataset.header,addOnPage:trigger.dataset.addonpage,templateContext:{hidden:!0},large:!0,courseId:courseId}))}))}show(){return this.reloadBodyContent(window.location.search),super.show(this)}reloadBodyContent(querystring){this.hideFooter(),this.setTitle(this.originalTitle),this.setBody(Fragment.loadFragment("mod_quiz","quiz_question_bank",this.getContextId(),{querystring:querystring,quizcmid:this.quizCmId,bankcmid:this.bankCmId}))}handleAddToQuizEvent(e,anchorElement){const href=new URL(anchorElement.getAttribute("href"));href.searchParams.set("addonpage",this.getAddOnPageId()),href.searchParams.set("cmid",this.quizCmId),anchorElement.setAttribute("href",href)}registerEventListeners(){super.registerEventListeners(this),this.getModal().on("submit",SELECTORS_ADD_QUESTIONS_FORM,(e=>{const formElement=e.currentTarget;document.querySelector(SELECTORS_ADD_ON_PAGE_FORM_ELEMENT).setAttribute("value",this.getAddOnPageId()),document.querySelector(SELECTORS_CMID_FORM_ELEMENT).setAttribute("value",this.quizCmId);const actionUrl=new URL(formElement.getAttribute("action"));actionUrl.searchParams.set("cmid",this.quizCmId),formElement.setAttribute("action",actionUrl.toString())}));const switcher=new _bank_switcher.default;this.getModal().on("click",SELECTORS_SWITCH_TO_OTHER_BANK,(()=>{try{switcher.show(this,this.courseId,this.getContextId(),this.bankCmId,this.quizCmId)}catch(ex){Notification.exception(ex)}})),this.getModal().get(0).addEventListener("bankSwitched",(e=>{const bankCmId=e.detail.cmid;if(bankCmId>0){const url=new URL(location.href);url.searchParams.delete("filter"),history.pushState({},"",url),this.bankCmId=bankCmId,this.reloadBodyContent(window.location.search)}})),this.getModal().on("click",SELECTORS_ANCHOR,(e=>{const anchorElement=e.currentTarget;anchorElement.closest(SELECTORS_ADD_TO_QUIZ_CONTAINER)?this.handleAddToQuizEvent(e,anchorElement):e.preventDefault()})),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 \ No newline at end of file diff --git a/public/mod/quiz/amd/build/modal_quiz_question_bank.min.js.map b/public/mod/quiz/amd/build/modal_quiz_question_bank.min.js.map index 5d2437e72fa..24e66083914 100644 --- a/public/mod/quiz/amd/build/modal_quiz_question_bank.min.js.map +++ b/public/mod/quiz/amd/build/modal_quiz_question_bank.min.js.map @@ -1 +1 @@ -{"version":3,"file":"modal_quiz_question_bank.min.js","sources":["../src/modal_quiz_question_bank.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Contain the logic for the question bank modal.\n *\n * @module mod_quiz/modal_quiz_question_bank\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Modal from './add_question_modal';\nimport * as Fragment from 'core/fragment';\nimport * as FormChangeChecker from 'core_form/changechecker';\nimport * as ModalEvents from 'core/modal_events';\nimport * as Notification from 'core/notification';\n\nconst SELECTORS = {\n ADD_TO_QUIZ_CONTAINER: 'td.addtoquizaction',\n ANCHOR: 'a[href]',\n PREVIEW_CONTAINER: 'td.previewquestionaction',\n ADD_QUESTIONS_FORM: 'form#questionsubmit',\n SORTERS: '.sorters',\n SWITCH_TO_OTHER_BANK: 'button[data-action=\"switch-question-bank\"]',\n NEW_BANKMOD_ID: 'data-newmodid',\n BANK_SEARCH: '#searchbanks',\n GO_BACK_BUTTON: 'button[data-action=\"go-back\"]',\n ADD_ON_PAGE_FORM_ELEMENT: 'input[name=\"addonpage\"]',\n CMID_FORM_ELEMENT: 'form#questionsubmit input[name=\"cmid\"]',\n};\n\nexport default class ModalQuizQuestionBank extends Modal {\n static TYPE = 'mod_quiz-quiz-question-bank';\n\n /**\n * Create the question bank modal.\n *\n * @param {Number} contextId Current module context id.\n * @param {Number} bankCmId Current question bank course module id.\n * @param {Number} quizCmId Current quiz course module id.\n */\n static init(contextId, bankCmId, quizCmId) {\n const selector = '.menu [data-action=\"questionbank\"]';\n document.addEventListener('click', (e) => {\n const trigger = e.target.closest(selector);\n if (!trigger) {\n return;\n }\n e.preventDefault();\n\n ModalQuizQuestionBank.create({\n contextId,\n quizCmId,\n bankCmId,\n title: trigger.dataset.header,\n addOnPage: trigger.dataset.addonpage,\n templateContext: {\n hidden: true,\n },\n large: true,\n });\n });\n }\n\n /**\n * Override the parent show function.\n *\n * Reload the body contents when the modal is shown. The current\n * window URL is used to inform the new content that should be\n * displayed.\n *\n * @method show\n * @return {void}\n */\n show() {\n this.reloadBodyContent(window.location.search);\n return super.show(this);\n }\n\n /**\n * Replaces the current body contents with a new version of the question\n * bank.\n *\n * The contents of the question bank are generated using the provided\n * query string.\n *\n * @method reloadBodyContent\n * @param {string} querystring URL encoded string.\n */\n reloadBodyContent(querystring) {\n // Load the question bank fragment to be displayed in the modal and hide the 'go back' button.\n this.hideFooter();\n this.setTitle(this.originalTitle);\n this.setBody(Fragment.loadFragment(\n 'mod_quiz',\n 'quiz_question_bank',\n this.getContextId(),\n {\n querystring,\n quizcmid: this.quizCmId,\n bankcmid: this.bankCmId,\n }\n ));\n }\n\n /**\n * Update the URL of the anchor element that the user clicked on to make\n * sure that the question is added to the correct page.\n *\n * @method handleAddToQuizEvent\n * @param {event} e A JavaScript event\n * @param {object} anchorElement The anchor element that was triggered\n */\n handleAddToQuizEvent(e, anchorElement) {\n // If the user clicks the plus icon to add the question to the page\n // directly then we need to intercept the click in order to adjust the\n // href and include the correct add on page id and cmid before the page is\n // redirected.\n const href = new URL(anchorElement.getAttribute('href'));\n href.searchParams.set('addonpage', this.getAddOnPageId());\n href.searchParams.set('cmid', this.quizCmId);\n anchorElement.setAttribute('href', href);\n }\n\n /**\n * Set up all of the event handling for the modal.\n *\n * @method registerEventListeners\n */\n registerEventListeners() {\n // Apply parent event listeners.\n super.registerEventListeners(this);\n\n this.getModal().on('submit', SELECTORS.ADD_QUESTIONS_FORM, (e) => {\n // If the user clicks on the \"Add selected questions to the quiz\" button to add some questions to the page\n // then we need to intercept the submit in order to include the correct \"add on page id\"\n // and the quizmod id before the form is submitted.\n const formElement = e.currentTarget;\n document.querySelector(SELECTORS.ADD_ON_PAGE_FORM_ELEMENT).setAttribute('value', this.getAddOnPageId());\n\n // We also need to set the form cmid & action as the quiz modid as this could be coming from a module that isn't a quiz.\n document.querySelector(SELECTORS.CMID_FORM_ELEMENT).setAttribute('value', this.quizCmId);\n const actionUrl = new URL(formElement.getAttribute('action'));\n actionUrl.searchParams.set('cmid', this.quizCmId);\n formElement.setAttribute('action', actionUrl.toString());\n });\n\n this.getModal().on('click', SELECTORS.SWITCH_TO_OTHER_BANK, () => {\n this.handleSwitchBankContentReload(SELECTORS.BANK_SEARCH)\n .then(function(ModalQuizQuestionBank) {\n document.querySelector(SELECTORS.BANK_SEARCH)?.addEventListener('change', (e) => {\n const bankCmId = e.currentTarget.value;\n if (bankCmId > 0) {\n ModalQuizQuestionBank.bankCmId = bankCmId;\n ModalQuizQuestionBank.reloadBodyContent(window.location.search);\n }\n });\n document.querySelector(SELECTORS.GO_BACK_BUTTON).addEventListener('click', (e) => {\n ModalQuizQuestionBank.bankCmId = e.currentTarget.value;\n ModalQuizQuestionBank.reloadBodyContent(window.location.search);\n });\n }\n )\n .catch(Notification.exception);\n });\n\n this.getModal().on('click', SELECTORS.ANCHOR, (e) => {\n const anchorElement = e.currentTarget;\n\n // If the anchor element was the add to quiz link.\n if (anchorElement.closest(SELECTORS.ADD_TO_QUIZ_CONTAINER)) {\n this.handleAddToQuizEvent(e, anchorElement);\n return;\n }\n\n // If the anchor element was a preview question link.\n if (anchorElement.closest(SELECTORS.PREVIEW_CONTAINER)) {\n return;\n }\n\n // Sorting links have their own handler.\n if (anchorElement.closest(SELECTORS.SORTERS)) {\n return;\n }\n\n if (anchorElement.closest('a[' + SELECTORS.NEW_BANKMOD_ID + ']')) {\n this.bankCmId = anchorElement.getAttribute(SELECTORS.NEW_BANKMOD_ID);\n\n // We need to clear the filter as we are about to reload the content.\n const url = new URL(location.href);\n url.searchParams.delete('filter');\n history.pushState({}, '', url);\n }\n\n // Anything else means reload the pop-up contents.\n e.preventDefault();\n this.reloadBodyContent(anchorElement.search);\n });\n\n // Disable the form change checker when the body is rendered.\n this.getRoot().on(ModalEvents.bodyRendered, () => {\n // Make sure the form change checker is disabled otherwise it'll stop the user from navigating away from the\n // page once the modal is hidden.\n FormChangeChecker.disableAllChecks();\n });\n }\n}\n\nModalQuizQuestionBank.registerModalType();\n"],"names":["SELECTORS","ModalQuizQuestionBank","Modal","contextId","bankCmId","quizCmId","document","addEventListener","e","trigger","target","closest","preventDefault","create","title","dataset","header","addOnPage","addonpage","templateContext","hidden","large","show","reloadBodyContent","window","location","search","super","this","querystring","hideFooter","setTitle","originalTitle","setBody","Fragment","loadFragment","getContextId","quizcmid","bankcmid","handleAddToQuizEvent","anchorElement","href","URL","getAttribute","searchParams","set","getAddOnPageId","setAttribute","registerEventListeners","getModal","on","formElement","currentTarget","querySelector","actionUrl","toString","handleSwitchBankContentReload","then","value","catch","Notification","exception","url","delete","history","pushState","getRoot","ModalEvents","bodyRendered","FormChangeChecker","disableAllChecks","registerModalType"],"mappings":"6iDA6BMA,gCACqB,qBADrBA,iBAEM,UAFNA,4BAGiB,2BAHjBA,6BAIkB,sBAJlBA,kBAKO,WALPA,+BAMoB,6CANpBA,yBAOc,gBAPdA,sBAQW,eARXA,yBASc,gCATdA,mCAUwB,0BAVxBA,4BAWiB,+CAGFC,8BAA8BC,wCAUnCC,UAAWC,SAAUC,UAE7BC,SAASC,iBAAiB,SAAUC,UAC1BC,QAAUD,EAAEE,OAAOC,QAFZ,sCAGRF,UAGLD,EAAEI,iBAEFX,sBAAsBY,OAAO,CACzBV,UAAAA,UACAE,SAAAA,SACAD,SAAAA,SACAU,MAAOL,QAAQM,QAAQC,OACvBC,UAAWR,QAAQM,QAAQG,UAC3BC,gBAAiB,CACbC,QAAQ,GAEZC,OAAO,QAenBC,mBACSC,kBAAkBC,OAAOC,SAASC,QAChCC,MAAML,KAAKM,MAatBL,kBAAkBM,kBAETC,kBACAC,SAASH,KAAKI,oBACdC,QAAQC,SAASC,aAClB,WACA,qBACAP,KAAKQ,eACL,CACIP,YAAAA,YACAQ,SAAUT,KAAKvB,SACfiC,SAAUV,KAAKxB,YAa3BmC,qBAAqB/B,EAAGgC,qBAKdC,KAAO,IAAIC,IAAIF,cAAcG,aAAa,SAChDF,KAAKG,aAAaC,IAAI,YAAajB,KAAKkB,kBACxCL,KAAKG,aAAaC,IAAI,OAAQjB,KAAKvB,UACnCmC,cAAcO,aAAa,OAAQN,MAQvCO,+BAEUA,uBAAuBpB,WAExBqB,WAAWC,GAAG,SAAUlD,8BAA+BQ,UAIlD2C,YAAc3C,EAAE4C,cACtB9C,SAAS+C,cAAcrD,oCAAoC+C,aAAa,QAASnB,KAAKkB,kBAGtFxC,SAAS+C,cAAcrD,6BAA6B+C,aAAa,QAASnB,KAAKvB,gBACzEiD,UAAY,IAAIZ,IAAIS,YAAYR,aAAa,WACnDW,UAAUV,aAAaC,IAAI,OAAQjB,KAAKvB,UACxC8C,YAAYJ,aAAa,SAAUO,UAAUC,oBAG5CN,WAAWC,GAAG,QAASlD,gCAAgC,UACnDwD,8BAA8BxD,uBAC9ByD,MAAK,SAASxD,+EACPK,SAAS+C,cAAcrD,+EAAwBO,iBAAiB,UAAWC,UACjEJ,SAAWI,EAAE4C,cAAcM,MAC7BtD,SAAW,IACXH,sBAAsBG,SAAWA,SACjCH,sBAAsBsB,kBAAkBC,OAAOC,SAASC,YAGhEpB,SAAS+C,cAAcrD,0BAA0BO,iBAAiB,SAAUC,IACxEP,sBAAsBG,SAAWI,EAAE4C,cAAcM,MACjDzD,sBAAsBsB,kBAAkBC,OAAOC,SAASC,cAInEiC,MAAMC,aAAaC,mBAGvBZ,WAAWC,GAAG,QAASlD,kBAAmBQ,UACrCgC,cAAgBhC,EAAE4C,iBAGpBZ,cAAc7B,QAAQX,sCACjBuC,qBAAqB/B,EAAGgC,wBAK7BA,cAAc7B,QAAQX,+BAKtBwC,cAAc7B,QAAQX,uBAItBwC,cAAc7B,QAAQ,KAAOX,yBAA2B,KAAM,MACzDI,SAAWoC,cAAcG,aAAa3C,gCAGrC8D,IAAM,IAAIpB,IAAIjB,SAASgB,MAC7BqB,IAAIlB,aAAamB,OAAO,UACxBC,QAAQC,UAAU,GAAI,GAAIH,KAI9BtD,EAAEI,sBACGW,kBAAkBiB,cAAcd,iBAIpCwC,UAAUhB,GAAGiB,YAAYC,cAAc,KAGxCC,kBAAkBC,+MA5KTrE,6BACH,+BAgLlBA,sBAAsBsE"} \ No newline at end of file +{"version":3,"file":"modal_quiz_question_bank.min.js","sources":["../src/modal_quiz_question_bank.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Contain the logic for the question bank modal.\n *\n * @module mod_quiz/modal_quiz_question_bank\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Modal from './add_question_modal';\nimport * as Fragment from 'core/fragment';\nimport * as FormChangeChecker from 'core_form/changechecker';\nimport * as ModalEvents from 'core/modal_events';\nimport * as Notification from 'core/notification';\nimport BankSwitcher from 'core_question/bank_switcher';\n\nconst SELECTORS = {\n ADD_TO_QUIZ_CONTAINER: 'td.addtoquizaction',\n ANCHOR: 'a[href]',\n PREVIEW_CONTAINER: 'td.previewquestionaction',\n ADD_QUESTIONS_FORM: 'form#questionsubmit',\n SORTERS: '.sorters',\n SWITCH_TO_OTHER_BANK: 'button[data-action=\"switch-question-bank\"]',\n NEW_BANKMOD_ID: 'data-newmodid',\n BANK_SEARCH: '#searchbanks',\n GO_BACK_BUTTON: 'button[data-action=\"go-back\"]',\n ADD_ON_PAGE_FORM_ELEMENT: 'input[name=\"addonpage\"]',\n CMID_FORM_ELEMENT: 'form#questionsubmit input[name=\"cmid\"]',\n};\n\nexport default class ModalQuizQuestionBank extends Modal {\n static TYPE = 'mod_quiz-quiz-question-bank';\n\n /**\n * Create the question bank modal.\n *\n * @param {Number} contextId Current module context id.\n * @param {Number} bankCmId Current question bank course module id.\n * @param {Number} quizCmId Current quiz course module id.\n * @param {Number} courseId Current course id, required for switching banks.\n */\n static init(contextId, bankCmId, quizCmId, courseId) {\n const selector = '.menu [data-action=\"questionbank\"]';\n document.addEventListener('click', (e) => {\n const trigger = e.target.closest(selector);\n if (!trigger) {\n return;\n }\n e.preventDefault();\n\n ModalQuizQuestionBank.create({\n contextId,\n quizCmId,\n bankCmId,\n title: trigger.dataset.header,\n addOnPage: trigger.dataset.addonpage,\n templateContext: {\n hidden: true,\n },\n large: true,\n courseId,\n });\n });\n }\n\n /**\n * Override the parent show function.\n *\n * Reload the body contents when the modal is shown. The current\n * window URL is used to inform the new content that should be\n * displayed.\n *\n * @method show\n * @return {void}\n */\n show() {\n this.reloadBodyContent(window.location.search);\n return super.show(this);\n }\n\n /**\n * Replaces the current body contents with a new version of the question\n * bank.\n *\n * The contents of the question bank are generated using the provided\n * query string.\n *\n * @method reloadBodyContent\n * @param {string} querystring URL encoded string.\n */\n reloadBodyContent(querystring) {\n // Load the question bank fragment to be displayed in the modal and hide the 'go back' button.\n this.hideFooter();\n this.setTitle(this.originalTitle);\n this.setBody(Fragment.loadFragment(\n 'mod_quiz',\n 'quiz_question_bank',\n this.getContextId(),\n {\n querystring,\n quizcmid: this.quizCmId,\n bankcmid: this.bankCmId,\n }\n ));\n }\n\n /**\n * Update the URL of the anchor element that the user clicked on to make\n * sure that the question is added to the correct page.\n *\n * @method handleAddToQuizEvent\n * @param {event} e A JavaScript event\n * @param {object} anchorElement The anchor element that was triggered\n */\n handleAddToQuizEvent(e, anchorElement) {\n // If the user clicks the plus icon to add the question to the page\n // directly then we need to intercept the click in order to adjust the\n // href and include the correct add on page id and cmid before the page is\n // redirected.\n const href = new URL(anchorElement.getAttribute('href'));\n href.searchParams.set('addonpage', this.getAddOnPageId());\n href.searchParams.set('cmid', this.quizCmId);\n anchorElement.setAttribute('href', href);\n }\n\n /**\n * Set up all of the event handling for the modal.\n *\n * @method registerEventListeners\n */\n registerEventListeners() {\n // Apply parent event listeners.\n super.registerEventListeners(this);\n\n this.getModal().on('submit', SELECTORS.ADD_QUESTIONS_FORM, (e) => {\n // If the user clicks on the \"Add selected questions to the quiz\" button to add some questions to the page\n // then we need to intercept the submit in order to include the correct \"add on page id\"\n // and the quizmod id before the form is submitted.\n const formElement = e.currentTarget;\n document.querySelector(SELECTORS.ADD_ON_PAGE_FORM_ELEMENT).setAttribute('value', this.getAddOnPageId());\n\n // We also need to set the form cmid & action as the quiz modid as this could be coming from a module that isn't a quiz.\n document.querySelector(SELECTORS.CMID_FORM_ELEMENT).setAttribute('value', this.quizCmId);\n const actionUrl = new URL(formElement.getAttribute('action'));\n actionUrl.searchParams.set('cmid', this.quizCmId);\n formElement.setAttribute('action', actionUrl.toString());\n });\n\n const switcher = new BankSwitcher();\n\n this.getModal().on('click', SELECTORS.SWITCH_TO_OTHER_BANK, () => {\n try {\n switcher.show(this, this.courseId, this.getContextId(), this.bankCmId, this.quizCmId);\n } catch (ex) {\n Notification.exception(ex);\n }\n });\n\n this.getModal().get(0).addEventListener('bankSwitched', (e) => {\n const bankCmId = e.detail.cmid;\n if (bankCmId > 0) {\n // We need to clear the filter as we are about to reload the content.\n const url = new URL(location.href);\n url.searchParams.delete('filter');\n history.pushState({}, '', url);\n\n this.bankCmId = bankCmId;\n this.reloadBodyContent(window.location.search);\n }\n });\n\n this.getModal().on('click', SELECTORS.ANCHOR, (e) => {\n const anchorElement = e.currentTarget;\n\n // If the anchor element was the add to quiz link.\n if (anchorElement.closest(SELECTORS.ADD_TO_QUIZ_CONTAINER)) {\n this.handleAddToQuizEvent(e, anchorElement);\n return;\n }\n\n e.preventDefault();\n });\n\n // Disable the form change checker when the body is rendered.\n this.getRoot().on(ModalEvents.bodyRendered, () => {\n // Make sure the form change checker is disabled otherwise it'll stop the user from navigating away from the\n // page once the modal is hidden.\n FormChangeChecker.disableAllChecks();\n });\n }\n}\n\nModalQuizQuestionBank.registerModalType();\n"],"names":["SELECTORS","ModalQuizQuestionBank","Modal","contextId","bankCmId","quizCmId","courseId","document","addEventListener","e","trigger","target","closest","preventDefault","create","title","dataset","header","addOnPage","addonpage","templateContext","hidden","large","show","reloadBodyContent","window","location","search","super","this","querystring","hideFooter","setTitle","originalTitle","setBody","Fragment","loadFragment","getContextId","quizcmid","bankcmid","handleAddToQuizEvent","anchorElement","href","URL","getAttribute","searchParams","set","getAddOnPageId","setAttribute","registerEventListeners","getModal","on","formElement","currentTarget","querySelector","actionUrl","toString","switcher","BankSwitcher","ex","Notification","exception","get","detail","cmid","url","delete","history","pushState","getRoot","ModalEvents","bodyRendered","FormChangeChecker","disableAllChecks","registerModalType"],"mappings":"0sDA8BMA,gCACqB,qBADrBA,iBAEM,UAFNA,6BAIkB,sBAJlBA,+BAMoB,6CANpBA,mCAUwB,0BAVxBA,4BAWiB,+CAGFC,8BAA8BC,wCAWnCC,UAAWC,SAAUC,SAAUC,UAEvCC,SAASC,iBAAiB,SAAUC,UAC1BC,QAAUD,EAAEE,OAAOC,QAFZ,sCAGRF,UAGLD,EAAEI,iBAEFZ,sBAAsBa,OAAO,CACzBX,UAAAA,UACAE,SAAAA,SACAD,SAAAA,SACAW,MAAOL,QAAQM,QAAQC,OACvBC,UAAWR,QAAQM,QAAQG,UAC3BC,gBAAiB,CACbC,QAAQ,GAEZC,OAAO,EACPhB,SAAAA,eAeZiB,mBACSC,kBAAkBC,OAAOC,SAASC,QAChCC,MAAML,KAAKM,MAatBL,kBAAkBM,kBAETC,kBACAC,SAASH,KAAKI,oBACdC,QAAQC,SAASC,aAClB,WACA,qBACAP,KAAKQ,eACL,CACIP,YAAAA,YACAQ,SAAUT,KAAKxB,SACfkC,SAAUV,KAAKzB,YAa3BoC,qBAAqB/B,EAAGgC,qBAKdC,KAAO,IAAIC,IAAIF,cAAcG,aAAa,SAChDF,KAAKG,aAAaC,IAAI,YAAajB,KAAKkB,kBACxCL,KAAKG,aAAaC,IAAI,OAAQjB,KAAKxB,UACnCoC,cAAcO,aAAa,OAAQN,MAQvCO,+BAEUA,uBAAuBpB,WAExBqB,WAAWC,GAAG,SAAUnD,8BAA+BS,UAIlD2C,YAAc3C,EAAE4C,cACtB9C,SAAS+C,cAActD,oCAAoCgD,aAAa,QAASnB,KAAKkB,kBAGtFxC,SAAS+C,cAActD,6BAA6BgD,aAAa,QAASnB,KAAKxB,gBACzEkD,UAAY,IAAIZ,IAAIS,YAAYR,aAAa,WACnDW,UAAUV,aAAaC,IAAI,OAAQjB,KAAKxB,UACxC+C,YAAYJ,aAAa,SAAUO,UAAUC,qBAG3CC,SAAW,IAAIC,4BAEhBR,WAAWC,GAAG,QAASnD,gCAAgC,SAEpDyD,SAASlC,KAAKM,KAAMA,KAAKvB,SAAUuB,KAAKQ,eAAgBR,KAAKzB,SAAUyB,KAAKxB,UAC9E,MAAOsD,IACLC,aAAaC,UAAUF,aAI1BT,WAAWY,IAAI,GAAGtD,iBAAiB,gBAAiBC,UAC/CL,SAAWK,EAAEsD,OAAOC,QACtB5D,SAAW,EAAG,OAER6D,IAAM,IAAItB,IAAIjB,SAASgB,MAC7BuB,IAAIpB,aAAaqB,OAAO,UACxBC,QAAQC,UAAU,GAAI,GAAIH,UAErB7D,SAAWA,cACXoB,kBAAkBC,OAAOC,SAASC,iBAI1CuB,WAAWC,GAAG,QAASnD,kBAAmBS,UACrCgC,cAAgBhC,EAAE4C,cAGpBZ,cAAc7B,QAAQZ,sCACjBwC,qBAAqB/B,EAAGgC,eAIjChC,EAAEI,yBAIDwD,UAAUlB,GAAGmB,YAAYC,cAAc,KAGxCC,kBAAkBC,6FA5JZ,iDADGxE,6HAkKrBA,sBAAsByE"} \ No newline at end of file diff --git a/public/mod/quiz/amd/src/add_question_modal.js b/public/mod/quiz/amd/src/add_question_modal.js index e9f9cf44993..569293e8c00 100644 --- a/public/mod/quiz/amd/src/add_question_modal.js +++ b/public/mod/quiz/amd/src/add_question_modal.js @@ -22,9 +22,6 @@ */ import Modal from 'core/modal'; -import * as Fragment from 'core/fragment'; -import {getString} from 'core/str'; -import AutoComplete from 'core/form-autocomplete'; export default class AddQuestionModal extends Modal { configure(modalConfig) { @@ -43,6 +40,7 @@ export default class AddQuestionModal extends Modal { // This is because the URL cmid param will change during filter operations as we will be in another bank context. this.quizCmId = modalConfig.quizCmId; this.bankCmId = modalConfig.bankCmId; + this.courseId = modalConfig.courseId; // Store the original title of the modal, so we can revert back to it once we have switched to another bank. this.originalTitle = modalConfig.title; @@ -99,50 +97,4 @@ export default class AddQuestionModal extends Modal { getAddOnPageId() { return this.addOnPageId; } - - /** - * Update the modal with a list of banks to switch to and enhance the standard selects to Autocomplete fields. - * - * @param {String} Selector for the original select element. - * @return {Promise} Modal. - */ - async handleSwitchBankContentReload(Selector) { - this.setTitle(getString('selectquestionbank', 'mod_quiz')); - - // Create a 'Go back' button and set it in the footer. - const el = document.createElement('button'); - el.classList.add('btn', 'btn-primary'); - el.textContent = await getString('gobacktoquiz', 'mod_quiz'); - el.setAttribute('data-action', 'go-back'); - el.setAttribute('value', this.bankCmId); - this.setFooter(el); - - this.setBody( - Fragment.loadFragment( - 'mod_quiz', - 'switch_question_bank', - this.getContextId(), - { - 'quizcmid': this.quizCmId, - 'bankcmid': this.bankCmId, - }) - ); - const placeholder = await getString('searchbyname', 'mod_quiz'); - await this.getBodyPromise(); - await AutoComplete.enhance( - Selector, - false, - 'core_question/question_banks_datasource', - placeholder, - false, - true, - '', - true - ); - - // Hide the selection element as we don't need it. - document.querySelector('.search-banks .form-autocomplete-selection')?.classList.add('d-none'); - - return this; - } } diff --git a/public/mod/quiz/amd/src/modal_add_random_question.js b/public/mod/quiz/amd/src/modal_add_random_question.js index 12d05f4fc9b..0aada464d70 100644 --- a/public/mod/quiz/amd/src/modal_add_random_question.js +++ b/public/mod/quiz/amd/src/modal_add_random_question.js @@ -29,6 +29,7 @@ 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'; +import BankSwitcher from 'core_question/bank_switcher'; const SELECTORS = { ANCHOR: 'a[href]', @@ -70,6 +71,7 @@ export default class ModalAddRandomQuestion extends Modal { * @param {string} returnUrl URL to return to after form submission. * @param {Number} quizCmId Current quiz course module id. * @param {boolean} showNewCategory Display the New category tab when selecting random questions. + * @param {Number} courseId Current course id, required for switching banks. */ static init( contextId, @@ -77,7 +79,8 @@ export default class ModalAddRandomQuestion extends Modal { category, returnUrl, quizCmId, - showNewCategory = true + showNewCategory = true, + courseId, ) { const selector = '.menu [data-action="addarandomquestion"], [data-action="editrandomquestion"]'; document.addEventListener('click', (e) => { @@ -104,6 +107,7 @@ export default class ModalAddRandomQuestion extends Modal { templateContext: { hidden: showNewCategory, }, + courseId, }); }); } @@ -131,6 +135,7 @@ export default class ModalAddRandomQuestion extends Modal { this.showNewCategory = modalConfig.showNewCategory; this.setSlotId(modalConfig.slotId ?? 0); this.setSavedFilterCondition(modalConfig.savedFilterCondition ?? null); + this.setCourseId(modalConfig.courseId); super.configure(modalConfig); } @@ -206,6 +211,24 @@ export default class ModalAddRandomQuestion extends Modal { return this.slotId; } + /** + * Store the ID of the current course + * + * @param {Number} courseId + */ + setCourseId(courseId) { + this.courseId = courseId; + } + + /** + * Get the current course ID. + * + * @return {Number} + */ + getCourseId() { + return this.courseId; + } + /** * Store the current filterCondition JSON string. * @@ -366,61 +389,22 @@ export default class ModalAddRandomQuestion extends Modal { } }); + const switcher = new BankSwitcher(); this.getModal().on('click', SELECTORS.SWITCH_TO_OTHER_BANK, () => { this.setSavedFilterCondition( document.querySelector(SELECTORS.FILTER_CONDITION_ELEMENT).dataset?.filtercondition ); - this.handleSwitchBankContentReload(SELECTORS.BANK_SEARCH) - .then(function(ModalQuizQuestionBank) { - $(SELECTORS.BANK_SEARCH)?.on('change', (e) => { - const bankCmId = $(e.currentTarget).val(); - // Have to recreate the modal as we have already used the body for the switch bank content. - if (bankCmId > 0) { - ModalAddRandomQuestion.create({ - 'contextId': ModalQuizQuestionBank.getContextId(), - 'bankCmId': bankCmId, - 'category': ModalQuizQuestionBank.getCategory(), - 'returnUrl': ModalQuizQuestionBank.getReturnUrl(), - 'quizCmId': ModalQuizQuestionBank.quizCmId, - 'title': ModalQuizQuestionBank.originalTitle, - 'addOnPage': ModalQuizQuestionBank.getAddOnPageId(), - 'templateContext': {hidden: ModalQuizQuestionBank.showNewCategory}, - 'showNewCategory': ModalQuizQuestionBank.showNewCategory, - 'slotId': ModalQuizQuestionBank.getSlotId(), - }) - .then(ModalQuizQuestionBank.destroy()) - .catch(Notification.exception); - } - }); - return ModalQuizQuestionBank; - }); + try { + switcher.show(this, this.courseId, this.getContextId(), this.bankCmId, this.quizCmId); + } catch (ex) { + Notification.exception(ex); + } }); - - this.getModal().on('click', SELECTORS.GO_BACK_BUTTON, (e) => { - const anchorElement = $(e.currentTarget); - // Have to recreate the modal as we have already used the body for the switch bank content. - ModalAddRandomQuestion.create({ - 'contextId': this.getContextId(), - 'bankCmId': anchorElement.attr('value'), - 'category': this.getCategory(), - 'returnUrl': this.getReturnUrl(), - 'quizCmId': this.quizCmId, - 'title': this.originalTitle, - 'addOnPage': this.getAddOnPageId(), - 'templateContext': {hidden: this.showNewCategory}, - 'showNewCategory': this.showNewCategory, - 'savedFilterCondition': this.getSavedFilterCondition(), - 'slotId': this.getSlotId(), - }).then(this.destroy()).catch(Notification.exception); - }); - - this.getModal().on('click', SELECTORS.ANCHOR, (e) => { - const anchorElement = $(e.currentTarget); - // Have to recreate the modal as we have already used the body for the switch bank content. - if (anchorElement.closest('a[' + SELECTORS.NEW_BANKMOD_ID + ']').length) { - ModalAddRandomQuestion.create({ + this.getModal().get(0).addEventListener('bankSwitched', async(e) => { + if (e.detail.cmid > 0) { + const modalConfig = { 'contextId': this.getContextId(), - 'bankCmId': anchorElement.attr(SELECTORS.NEW_BANKMOD_ID), + 'bankCmId': e.detail.cmid, 'category': this.getCategory(), 'returnUrl': this.getReturnUrl(), 'quizCmId': this.quizCmId, @@ -429,7 +413,18 @@ export default class ModalAddRandomQuestion extends Modal { 'templateContext': {hidden: this.showNewCategory}, 'showNewCategory': this.showNewCategory, 'slotId': this.getSlotId(), - }).then(this.destroy()).catch(Notification.exception); + 'courseId': this.getCourseId(), + }; + if (e.detail.cmid === this.bankCmId) { + // We're displaying the same bank as before, keep the current filters. + modalConfig.savedFilterCondition = this.getSavedFilterCondition(); + } + try { + await ModalAddRandomQuestion.create(modalConfig); + this.destroy(); + } catch (ex) { + await Notification.exception(ex); + } } }); }) diff --git a/public/mod/quiz/amd/src/modal_quiz_question_bank.js b/public/mod/quiz/amd/src/modal_quiz_question_bank.js index 5fac3a76bff..731b6a12b50 100644 --- a/public/mod/quiz/amd/src/modal_quiz_question_bank.js +++ b/public/mod/quiz/amd/src/modal_quiz_question_bank.js @@ -26,6 +26,7 @@ import * as Fragment from 'core/fragment'; import * as FormChangeChecker from 'core_form/changechecker'; import * as ModalEvents from 'core/modal_events'; import * as Notification from 'core/notification'; +import BankSwitcher from 'core_question/bank_switcher'; const SELECTORS = { ADD_TO_QUIZ_CONTAINER: 'td.addtoquizaction', @@ -50,8 +51,9 @@ export default class ModalQuizQuestionBank extends Modal { * @param {Number} contextId Current module context id. * @param {Number} bankCmId Current question bank course module id. * @param {Number} quizCmId Current quiz course module id. + * @param {Number} courseId Current course id, required for switching banks. */ - static init(contextId, bankCmId, quizCmId) { + static init(contextId, bankCmId, quizCmId, courseId) { const selector = '.menu [data-action="questionbank"]'; document.addEventListener('click', (e) => { const trigger = e.target.closest(selector); @@ -70,6 +72,7 @@ export default class ModalQuizQuestionBank extends Modal { hidden: true, }, large: true, + courseId, }); }); } @@ -157,23 +160,27 @@ export default class ModalQuizQuestionBank extends Modal { formElement.setAttribute('action', actionUrl.toString()); }); + const switcher = new BankSwitcher(); + this.getModal().on('click', SELECTORS.SWITCH_TO_OTHER_BANK, () => { - this.handleSwitchBankContentReload(SELECTORS.BANK_SEARCH) - .then(function(ModalQuizQuestionBank) { - document.querySelector(SELECTORS.BANK_SEARCH)?.addEventListener('change', (e) => { - const bankCmId = e.currentTarget.value; - if (bankCmId > 0) { - ModalQuizQuestionBank.bankCmId = bankCmId; - ModalQuizQuestionBank.reloadBodyContent(window.location.search); - } - }); - document.querySelector(SELECTORS.GO_BACK_BUTTON).addEventListener('click', (e) => { - ModalQuizQuestionBank.bankCmId = e.currentTarget.value; - ModalQuizQuestionBank.reloadBodyContent(window.location.search); - }); - } - ) - .catch(Notification.exception); + try { + switcher.show(this, this.courseId, this.getContextId(), this.bankCmId, this.quizCmId); + } catch (ex) { + Notification.exception(ex); + } + }); + + this.getModal().get(0).addEventListener('bankSwitched', (e) => { + const bankCmId = e.detail.cmid; + if (bankCmId > 0) { + // We need to clear the filter as we are about to reload the content. + const url = new URL(location.href); + url.searchParams.delete('filter'); + history.pushState({}, '', url); + + this.bankCmId = bankCmId; + this.reloadBodyContent(window.location.search); + } }); this.getModal().on('click', SELECTORS.ANCHOR, (e) => { @@ -185,28 +192,7 @@ export default class ModalQuizQuestionBank extends Modal { return; } - // If the anchor element was a preview question link. - if (anchorElement.closest(SELECTORS.PREVIEW_CONTAINER)) { - return; - } - - // Sorting links have their own handler. - if (anchorElement.closest(SELECTORS.SORTERS)) { - return; - } - - if (anchorElement.closest('a[' + SELECTORS.NEW_BANKMOD_ID + ']')) { - this.bankCmId = anchorElement.getAttribute(SELECTORS.NEW_BANKMOD_ID); - - // We need to clear the filter as we are about to reload the content. - const url = new URL(location.href); - url.searchParams.delete('filter'); - history.pushState({}, '', url); - } - - // Anything else means reload the pop-up contents. e.preventDefault(); - this.reloadBodyContent(anchorElement.search); }); // Disable the form change checker when the body is rendered. diff --git a/public/mod/quiz/classes/output/edit_renderer.php b/public/mod/quiz/classes/output/edit_renderer.php index 83176d4a344..a8cdfff6017 100644 --- a/public/mod/quiz/classes/output/edit_renderer.php +++ b/public/mod/quiz/classes/output/edit_renderer.php @@ -122,6 +122,7 @@ class edit_renderer extends \plugin_renderer_base { $thiscontext->id, $quizobj->get_cm()->id, $quizobj->get_cm()->id, + $quizobj->get_courseid(), ]); $this->page->requires->js_call_amd('mod_quiz/modal_add_random_question', 'init', [ @@ -131,6 +132,7 @@ class edit_renderer extends \plugin_renderer_base { $pageurl->out_as_local_url(true), $pageurl->param('cmid'), \core\plugininfo\qbank::is_plugin_enabled(\qbank_managecategories\helper::PLUGINNAME), + $quizobj->get_courseid(), ]); // Include the question chooser. diff --git a/public/mod/quiz/lang/en/deprecated.txt b/public/mod/quiz/lang/en/deprecated.txt index 4e33c9b3234..46dbba18e3b 100644 --- a/public/mod/quiz/lang/en/deprecated.txt +++ b/public/mod/quiz/lang/en/deprecated.txt @@ -5,3 +5,5 @@ groupoverridesdeleted,mod_quiz randomcoursecatwithsubcat,mod_quiz randomcoursewithsubcat,mod_quiz randomsystemwithsubcat,mod_quiz +selectquestionbank,mod_quiz +gobacktoquiz,mod_quiz diff --git a/public/mod/quiz/lang/en/quiz.php b/public/mod/quiz/lang/en/quiz.php index 3ce69a22f26..81de4c729b5 100644 --- a/public/mod/quiz/lang/en/quiz.php +++ b/public/mod/quiz/lang/en/quiz.php @@ -1012,7 +1012,6 @@ $string['selectedattempts'] = 'Selected attempts...'; $string['selectmultipleitems'] = 'Select multiple items'; $string['selectmultipletoolbar'] = 'Select multiple toolbar'; $string['selectnone'] = 'Deselect all'; -$string['selectquestionbank'] = 'Select question bank'; $string['selectquestionslot'] = 'Select question {$a}'; $string['selectquestiontype'] = '-- Select question type --'; $string['sendnotificationopendatesoon'] = 'Notify user of an approaching quiz open date'; @@ -1155,7 +1154,6 @@ $string['yourfinalgradeis'] = 'Your final grade for this quiz is {$a}.'; $string['questionversion'] = 'v{$a}'; $string['questionversionlatest'] = 'v{$a} (latest)'; $string['alwayslatest'] = 'Always latest'; -$string['gobacktoquiz'] = 'Go back'; // Deprecated since Moodle 4.5. $string['attemptsdeleted'] = 'Quiz attempts deleted'; @@ -1167,3 +1165,7 @@ $string['groupoverridesdeleted'] = 'Group overrides deleted'; $string['randomcoursecatwithsubcat'] = 'Any category inside course category {$a}'; $string['randomcoursewithsubcat'] = 'Any category in this course'; $string['randomsystemwithsubcat'] = 'Any system-level category'; + +// Deprecated since Moodle 5.2. +$string['gobacktoquiz'] = 'Go back'; +$string['selectquestionbank'] = 'Select question bank'; diff --git a/public/mod/quiz/lib.php b/public/mod/quiz/lib.php index 74990c16ca9..3e6b9434bef 100644 --- a/public/mod/quiz/lib.php +++ b/public/mod/quiz/lib.php @@ -2490,8 +2490,15 @@ function mod_quiz_output_fragment_quiz_question_bank($args): string { * @param array $args provided by the AJAX request. * @return string html to render to the modal. */ +#[\core\attribute\deprecated( + replacement: 'core_question\route\api\bank::switcher', + since: '5.2', + reason: 'Replaced with a non-quiz-specific API endpoint', + mdl: 'MDL-87264', +)] function mod_quiz_output_fragment_switch_question_bank($args): string { global $USER, $COURSE, $OUTPUT; + \core\deprecation::emit_deprecation(__FUNCTION__); $quizcmid = clean_param($args['quizcmid'], PARAM_INT); diff --git a/public/question/amd/build/bank_switcher.min.js b/public/question/amd/build/bank_switcher.min.js new file mode 100644 index 00000000000..17ffacc2cdc --- /dev/null +++ b/public/question/amd/build/bank_switcher.min.js @@ -0,0 +1,21 @@ +define("core_question/bank_switcher",["exports","core/str","core/fetch","core/templates","core/form-autocomplete"],(function(_exports,_str,_fetch,_templates,_formAutocomplete){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} +/** + * Bank switcher module for switching between question banks. + * + * The BankSwitcher class is used to display the bank switching UI in an existing Modal. This can either be a new Modal + * created for the purpose, or an existing modal, in which case it will replace the existing title, body and footer. + * + * When a new bank is selected, it will emit a custom `bankSwitched` event on the modal's DOM element, + * with the cmid of the selected bank in `event.details.cmid`. For example: + * + * const modal = Modal.create(...); + * const switcher = new BankSwitcher(); + * switcher.show(modal, courseId, cmId); + * modal.getModal().get(0).addEventListener('bankSwitched', (e) => window.console.log(e.details.cmid)); + * + * @module core_question/bank_switcher + * @copyright 2025 Catalyst IT Europe Ltd. + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_fetch=_interopRequireDefault(_fetch),_templates=_interopRequireDefault(_templates),_formAutocomplete=_interopRequireDefault(_formAutocomplete);const SELECTORS_ANCHOR="a[data-newmodid]",SELECTORS_AUTOCOMPLETE_SELECTION=".search-banks .form-autocomplete-selection",SELECTORS_BANK_SEARCH="#searchbanks",SELECTORS_BANK_SWITCHER=".bank-switcher",SELECTORS_GO_BACK_BUTTON='button[data-action="go-back"]',SELECTORS_MODAL='div[data-region="modal"]';return _exports.default=class{async show(modal,courseId,contextId){var _modalBody$querySelec;let currentCmId=arguments.length>3&&void 0!==arguments[3]?arguments[3]:null,activityCmId=arguments.length>4&&void 0!==arguments[4]?arguments[4]:null;if(!courseId)throw new Error("courseId is required by the bank switcher.");if(modal.setTitle((0,_str.get_string)("switcherselectbank","core_question")),currentCmId){const el=document.createElement("button");el.classList.add("btn","btn-primary"),el.textContent=await(0,_str.get_string)("switchergoback","core_question"),el.setAttribute("data-action","go-back"),el.setAttribute("value",currentCmId),el.addEventListener("click",this.clickListener),modal.setFooter(el)}const params={course:courseId,includeshared:!0,includerecent:!0};activityCmId&&(params.currentmodule=activityCmId);const banksResponse=await _fetch.default.performGet("question","banks",{params:params}),{banks:banks}=await banksResponse.json(),templateContext={contextid:contextId,hasactivitybank:!1,hascoursesharedbanks:!1,coursesharedbanks:[],hasrecentlyviewedbanks:!1,recentlyviewedbanks:[]};banks.forEach((bank=>bank.current?(templateContext.hasactivitybank=!0,void(templateContext.activitybank={name:bank.name,cmid:bank.modid})):bank.recent?(templateContext.hasrecentlyviewedbanks=!0,void templateContext.recentlyviewedbanks.push({coursenamebankname:bank.coursenamebankname,modid:bank.modid})):(templateContext.hascoursesharedbanks=!0,void templateContext.coursesharedbanks.push({name:bank.name,modid:bank.modid})))),modal.setBody(_templates.default.render("core_question/switch_question_bank",templateContext));const placeholder=await(0,_str.get_string)("switchersearchbyname","core_question");await modal.getBodyPromise(),await _formAutocomplete.default.enhance(SELECTORS_BANK_SEARCH,!1,"core_question/question_banks_datasource",placeholder,!1,!0,"",!0);const modalBody=modal.getBody()[0];null===(_modalBody$querySelec=modalBody.querySelector(SELECTORS_AUTOCOMPLETE_SELECTION))||void 0===_modalBody$querySelec||_modalBody$querySelec.classList.add("d-none");const switcherElement=modalBody.querySelector(SELECTORS_BANK_SWITCHER);switcherElement.addEventListener("click",this.clickListener),switcherElement.addEventListener("change",this.changeListener)}clickListener(e){const goBack=e.target.closest(SELECTORS_GO_BACK_BUTTON);let cmid;if(goBack)cmid=goBack.value;else{const anchor=e.target.closest(SELECTORS_ANCHOR);if(!anchor)return;cmid=anchor.dataset.newmodid}e.preventDefault();const modalElement=e.target.closest(SELECTORS_MODAL),bankSwitched=new CustomEvent("bankSwitched",{detail:{cmid:cmid}});modalElement.dispatchEvent(bankSwitched)}changeListener(e){const search=e.target.closest(SELECTORS_BANK_SEARCH);if(!search)return;const bankCmId=search.value;if(bankCmId>0){const modalElement=e.target.closest(SELECTORS_MODAL),bankSwitched=new CustomEvent("bankSwitched",{detail:{cmid:bankCmId}});modalElement.dispatchEvent(bankSwitched)}}},_exports.default})); + +//# sourceMappingURL=bank_switcher.min.js.map \ No newline at end of file diff --git a/public/question/amd/build/bank_switcher.min.js.map b/public/question/amd/build/bank_switcher.min.js.map new file mode 100644 index 00000000000..601f358a3c9 --- /dev/null +++ b/public/question/amd/build/bank_switcher.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"bank_switcher.min.js","sources":["../src/bank_switcher.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\nimport {get_string as getString} from 'core/str';\nimport Fetch from 'core/fetch';\nimport Templates from 'core/templates';\nimport AutoComplete from 'core/form-autocomplete';\n\n/**\n * Bank switcher module for switching between question banks.\n *\n * The BankSwitcher class is used to display the bank switching UI in an existing Modal. This can either be a new Modal\n * created for the purpose, or an existing modal, in which case it will replace the existing title, body and footer.\n *\n * When a new bank is selected, it will emit a custom `bankSwitched` event on the modal's DOM element,\n * with the cmid of the selected bank in `event.details.cmid`. For example:\n *\n * const modal = Modal.create(...);\n * const switcher = new BankSwitcher();\n * switcher.show(modal, courseId, cmId);\n * modal.getModal().get(0).addEventListener('bankSwitched', (e) => window.console.log(e.details.cmid));\n *\n * @module core_question/bank_switcher\n * @copyright 2025 Catalyst IT Europe Ltd.\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nconst SELECTORS = {\n ANCHOR: 'a[data-newmodid]',\n AUTOCOMPLETE_SELECTION: '.search-banks .form-autocomplete-selection',\n BANK_SEARCH: '#searchbanks',\n BANK_SWITCHER: '.bank-switcher',\n GO_BACK_BUTTON: 'button[data-action=\"go-back\"]',\n MODAL: 'div[data-region=\"modal\"]',\n};\n\nexport default class BankSwitcher {\n /**\n * Show the bank switcher.\n *\n * This will replace the content of the given modal with the bank switcher, and emit a bankChanged event on the modal\n * when a bank is selected (either a new bank, or the current one again).\n *\n * @param {Modal} modal The modal to display the switcher within.\n * @param {Number} courseId The course ID to display banks from.\n * @param {Number} contextId The context the bank switcher is being displayed in, for text filters.\n * @param {Number} currentCmId The cmid of the currently selected question bank.\n * @param {Number} activityCmId The cmid of activity we are currently in (if any).\n * @return {Promise} Resolves once the switcher has finished displaying.\n */\n async show(modal, courseId, contextId, currentCmId = null, activityCmId = null) {\n if (!courseId) {\n throw new Error('courseId is required by the bank switcher.');\n }\n modal.setTitle(getString('switcherselectbank', 'core_question'));\n\n if (currentCmId) {\n // Create a 'Go back' button and set it in the footer.\n const el = document.createElement('button');\n el.classList.add('btn', 'btn-primary');\n el.textContent = await getString('switchergoback', 'core_question');\n el.setAttribute('data-action', 'go-back');\n el.setAttribute('value', currentCmId);\n el.addEventListener('click', this.clickListener);\n modal.setFooter(el);\n }\n\n const params = {\n course: courseId,\n includeshared: true,\n includerecent: true,\n };\n if (activityCmId) {\n params.currentmodule = activityCmId;\n }\n const banksResponse = await Fetch.performGet('question', 'banks', {params});\n const {banks} = await banksResponse.json();\n const templateContext = {\n contextid: contextId,\n hasactivitybank: false,\n hascoursesharedbanks: false,\n coursesharedbanks: [],\n hasrecentlyviewedbanks: false,\n recentlyviewedbanks: [],\n };\n banks.forEach((bank) => {\n if (bank.current) {\n templateContext.hasactivitybank = true;\n templateContext.activitybank = {\n name: bank.name,\n cmid: bank.modid,\n };\n return;\n }\n if (bank.recent) {\n templateContext.hasrecentlyviewedbanks = true;\n templateContext.recentlyviewedbanks.push({\n coursenamebankname: bank.coursenamebankname,\n modid: bank.modid,\n });\n return;\n }\n templateContext.hascoursesharedbanks = true;\n templateContext.coursesharedbanks.push({\n name: bank.name,\n modid: bank.modid,\n });\n });\n\n modal.setBody(\n Templates.render('core_question/switch_question_bank', templateContext)\n );\n const placeholder = await getString('switchersearchbyname', 'core_question');\n await modal.getBodyPromise();\n await AutoComplete.enhance(\n SELECTORS.BANK_SEARCH,\n false,\n 'core_question/question_banks_datasource',\n placeholder,\n false,\n true,\n '',\n true\n );\n\n const modalBody = modal.getBody()[0];\n // Hide the selection element as we don't need it.\n modalBody.querySelector(SELECTORS.AUTOCOMPLETE_SELECTION)?.classList.add('d-none');\n\n const switcherElement = modalBody.querySelector(SELECTORS.BANK_SWITCHER);\n switcherElement.addEventListener('click', this.clickListener);\n switcherElement.addEventListener('change', this.changeListener);\n }\n\n clickListener(e) {\n const goBack = e.target.closest(SELECTORS.GO_BACK_BUTTON);\n let cmid;\n if (goBack) {\n cmid = goBack.value;\n } else {\n const anchor = e.target.closest(SELECTORS.ANCHOR);\n if (!anchor) {\n return;\n }\n cmid = anchor.dataset.newmodid;\n }\n e.preventDefault();\n const modalElement = e.target.closest(SELECTORS.MODAL);\n const bankSwitched = new CustomEvent('bankSwitched', {detail: {cmid}});\n modalElement.dispatchEvent(bankSwitched);\n }\n\n changeListener(e) {\n const search = e.target.closest(SELECTORS.BANK_SEARCH);\n if (!search) {\n return;\n }\n const bankCmId = search.value;\n if (bankCmId > 0) {\n const modalElement = e.target.closest(SELECTORS.MODAL);\n const bankSwitched = new CustomEvent('bankSwitched', {detail: {cmid: bankCmId}});\n modalElement.dispatchEvent(bankSwitched);\n }\n }\n}\n"],"names":["SELECTORS","modal","courseId","contextId","currentCmId","activityCmId","Error","setTitle","el","document","createElement","classList","add","textContent","setAttribute","addEventListener","this","clickListener","setFooter","params","course","includeshared","includerecent","currentmodule","banksResponse","Fetch","performGet","banks","json","templateContext","contextid","hasactivitybank","hascoursesharedbanks","coursesharedbanks","hasrecentlyviewedbanks","recentlyviewedbanks","forEach","bank","current","activitybank","name","cmid","modid","recent","push","coursenamebankname","setBody","Templates","render","placeholder","getBodyPromise","AutoComplete","enhance","modalBody","getBody","querySelector","switcherElement","changeListener","e","goBack","target","closest","value","anchor","dataset","newmodid","preventDefault","modalElement","bankSwitched","CustomEvent","detail","dispatchEvent","search","bankCmId"],"mappings":";;;;;;;;;;;;;;;;;;2OAuCMA,iBACM,mBADNA,iCAEsB,6CAFtBA,sBAGW,eAHXA,wBAIa,iBAJbA,yBAKc,gCALdA,gBAMK,oEAiBIC,MAAOC,SAAUC,yCAAWC,mEAAc,KAAMC,oEAAe,SACjEH,eACK,IAAII,MAAM,iDAEpBL,MAAMM,UAAS,mBAAU,qBAAsB,kBAE3CH,YAAa,OAEPI,GAAKC,SAASC,cAAc,UAClCF,GAAGG,UAAUC,IAAI,MAAO,eACxBJ,GAAGK,kBAAoB,mBAAU,iBAAkB,iBACnDL,GAAGM,aAAa,cAAe,WAC/BN,GAAGM,aAAa,QAASV,aACzBI,GAAGO,iBAAiB,QAASC,KAAKC,eAClChB,MAAMiB,UAAUV,UAGdW,OAAS,CACXC,OAAQlB,SACRmB,eAAe,EACfC,eAAe,GAEfjB,eACAc,OAAOI,cAAgBlB,oBAErBmB,oBAAsBC,eAAMC,WAAW,WAAY,QAAS,CAACP,OAAAA,UAC7DQ,MAACA,aAAeH,cAAcI,OAC9BC,gBAAkB,CACpBC,UAAW3B,UACX4B,iBAAiB,EACjBC,sBAAsB,EACtBC,kBAAmB,GACnBC,wBAAwB,EACxBC,oBAAqB,IAEzBR,MAAMS,SAASC,MACPA,KAAKC,SACLT,gBAAgBE,iBAAkB,OAClCF,gBAAgBU,aAAe,CAC3BC,KAAMH,KAAKG,KACXC,KAAMJ,KAAKK,SAIfL,KAAKM,QACLd,gBAAgBK,wBAAyB,OACzCL,gBAAgBM,oBAAoBS,KAAK,CACrCC,mBAAoBR,KAAKQ,mBACzBH,MAAOL,KAAKK,UAIpBb,gBAAgBG,sBAAuB,OACvCH,gBAAgBI,kBAAkBW,KAAK,CACnCJ,KAAMH,KAAKG,KACXE,MAAOL,KAAKK,WAIpBzC,MAAM6C,QACFC,mBAAUC,OAAO,qCAAsCnB,wBAErDoB,kBAAoB,mBAAU,uBAAwB,uBACtDhD,MAAMiD,uBACNC,0BAAaC,QACfpD,uBACA,EACA,0CACAiD,aACA,GACA,EACA,IACA,SAGEI,UAAYpD,MAAMqD,UAAU,iCAElCD,UAAUE,cAAcvD,0FAAmCW,UAAUC,IAAI,gBAEnE4C,gBAAkBH,UAAUE,cAAcvD,yBAChDwD,gBAAgBzC,iBAAiB,QAASC,KAAKC,eAC/CuC,gBAAgBzC,iBAAiB,SAAUC,KAAKyC,gBAGpDxC,cAAcyC,SACJC,OAASD,EAAEE,OAAOC,QAAQ7D,8BAC5ByC,QACAkB,OACAlB,KAAOkB,OAAOG,UACX,OACGC,OAASL,EAAEE,OAAOC,QAAQ7D,sBAC3B+D,cAGLtB,KAAOsB,OAAOC,QAAQC,SAE1BP,EAAEQ,uBACIC,aAAeT,EAAEE,OAAOC,QAAQ7D,iBAChCoE,aAAe,IAAIC,YAAY,eAAgB,CAACC,OAAQ,CAAC7B,KAAAA,QAC/D0B,aAAaI,cAAcH,cAG/BX,eAAeC,SACLc,OAASd,EAAEE,OAAOC,QAAQ7D,2BAC3BwE,oBAGCC,SAAWD,OAAOV,SACpBW,SAAW,EAAG,OACRN,aAAeT,EAAEE,OAAOC,QAAQ7D,iBAChCoE,aAAe,IAAIC,YAAY,eAAgB,CAACC,OAAQ,CAAC7B,KAAMgC,YACrEN,aAAaI,cAAcH"} \ No newline at end of file diff --git a/public/question/amd/src/bank_switcher.js b/public/question/amd/src/bank_switcher.js new file mode 100644 index 00000000000..de16b31022b --- /dev/null +++ b/public/question/amd/src/bank_switcher.js @@ -0,0 +1,177 @@ +// 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 . + +import {get_string as getString} from 'core/str'; +import Fetch from 'core/fetch'; +import Templates from 'core/templates'; +import AutoComplete from 'core/form-autocomplete'; + +/** + * Bank switcher module for switching between question banks. + * + * The BankSwitcher class is used to display the bank switching UI in an existing Modal. This can either be a new Modal + * created for the purpose, or an existing modal, in which case it will replace the existing title, body and footer. + * + * When a new bank is selected, it will emit a custom `bankSwitched` event on the modal's DOM element, + * with the cmid of the selected bank in `event.details.cmid`. For example: + * + * const modal = Modal.create(...); + * const switcher = new BankSwitcher(); + * switcher.show(modal, courseId, cmId); + * modal.getModal().get(0).addEventListener('bankSwitched', (e) => window.console.log(e.details.cmid)); + * + * @module core_question/bank_switcher + * @copyright 2025 Catalyst IT Europe Ltd. + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +const SELECTORS = { + ANCHOR: 'a[data-newmodid]', + AUTOCOMPLETE_SELECTION: '.search-banks .form-autocomplete-selection', + BANK_SEARCH: '#searchbanks', + BANK_SWITCHER: '.bank-switcher', + GO_BACK_BUTTON: 'button[data-action="go-back"]', + MODAL: 'div[data-region="modal"]', +}; + +export default class BankSwitcher { + /** + * Show the bank switcher. + * + * This will replace the content of the given modal with the bank switcher, and emit a bankChanged event on the modal + * when a bank is selected (either a new bank, or the current one again). + * + * @param {Modal} modal The modal to display the switcher within. + * @param {Number} courseId The course ID to display banks from. + * @param {Number} contextId The context the bank switcher is being displayed in, for text filters. + * @param {Number} currentCmId The cmid of the currently selected question bank. + * @param {Number} activityCmId The cmid of activity we are currently in (if any). + * @return {Promise} Resolves once the switcher has finished displaying. + */ + async show(modal, courseId, contextId, currentCmId = null, activityCmId = null) { + if (!courseId) { + throw new Error('courseId is required by the bank switcher.'); + } + modal.setTitle(getString('switcherselectbank', 'core_question')); + + if (currentCmId) { + // Create a 'Go back' button and set it in the footer. + const el = document.createElement('button'); + el.classList.add('btn', 'btn-primary'); + el.textContent = await getString('switchergoback', 'core_question'); + el.setAttribute('data-action', 'go-back'); + el.setAttribute('value', currentCmId); + el.addEventListener('click', this.clickListener); + modal.setFooter(el); + } + + const params = { + course: courseId, + includeshared: true, + includerecent: true, + }; + if (activityCmId) { + params.currentmodule = activityCmId; + } + const banksResponse = await Fetch.performGet('question', 'banks', {params}); + const {banks} = await banksResponse.json(); + const templateContext = { + contextid: contextId, + hasactivitybank: false, + hascoursesharedbanks: false, + coursesharedbanks: [], + hasrecentlyviewedbanks: false, + recentlyviewedbanks: [], + }; + banks.forEach((bank) => { + if (bank.current) { + templateContext.hasactivitybank = true; + templateContext.activitybank = { + name: bank.name, + cmid: bank.modid, + }; + return; + } + if (bank.recent) { + templateContext.hasrecentlyviewedbanks = true; + templateContext.recentlyviewedbanks.push({ + coursenamebankname: bank.coursenamebankname, + modid: bank.modid, + }); + return; + } + templateContext.hascoursesharedbanks = true; + templateContext.coursesharedbanks.push({ + name: bank.name, + modid: bank.modid, + }); + }); + + modal.setBody( + Templates.render('core_question/switch_question_bank', templateContext) + ); + const placeholder = await getString('switchersearchbyname', 'core_question'); + await modal.getBodyPromise(); + await AutoComplete.enhance( + SELECTORS.BANK_SEARCH, + false, + 'core_question/question_banks_datasource', + placeholder, + false, + true, + '', + true + ); + + const modalBody = modal.getBody()[0]; + // Hide the selection element as we don't need it. + modalBody.querySelector(SELECTORS.AUTOCOMPLETE_SELECTION)?.classList.add('d-none'); + + const switcherElement = modalBody.querySelector(SELECTORS.BANK_SWITCHER); + switcherElement.addEventListener('click', this.clickListener); + switcherElement.addEventListener('change', this.changeListener); + } + + clickListener(e) { + const goBack = e.target.closest(SELECTORS.GO_BACK_BUTTON); + let cmid; + if (goBack) { + cmid = goBack.value; + } else { + const anchor = e.target.closest(SELECTORS.ANCHOR); + if (!anchor) { + return; + } + cmid = anchor.dataset.newmodid; + } + e.preventDefault(); + const modalElement = e.target.closest(SELECTORS.MODAL); + const bankSwitched = new CustomEvent('bankSwitched', {detail: {cmid}}); + modalElement.dispatchEvent(bankSwitched); + } + + changeListener(e) { + const search = e.target.closest(SELECTORS.BANK_SEARCH); + if (!search) { + return; + } + const bankCmId = search.value; + if (bankCmId > 0) { + const modalElement = e.target.closest(SELECTORS.MODAL); + const bankSwitched = new CustomEvent('bankSwitched', {detail: {cmid: bankCmId}}); + modalElement.dispatchEvent(bankSwitched); + } + } +} diff --git a/public/question/bank/bulkmove/classes/output/bulk_move.php b/public/question/bank/bulkmove/classes/output/bulk_move.php index f50bfbfb656..036e2857b95 100644 --- a/public/question/bank/bulkmove/classes/output/bulk_move.php +++ b/public/question/bank/bulkmove/classes/output/bulk_move.php @@ -72,14 +72,14 @@ class bulk_move implements \renderable, \templatable { currentbankid: $this->currentbankid, filtercontext: $currentbankcm->context, limit: 1, - )[0]; + )[0]->get_formatted(); } else { $banktorender = question_bank_helper::get_activity_instances_with_private_questions( incourseids: [$currentbankcm->course], havingcap: ['moodle/question:add'], currentbankid: $this->currentbankid, filtercontext: $currentbankcm->context, - )[0]; + )[0]->get_formatted(); } $categoryselector = new question_category_selector( diff --git a/public/question/classes/external/search_shared_banks.php b/public/question/classes/external/search_shared_banks.php index 9293c2aaa23..531e8dfd386 100644 --- a/public/question/classes/external/search_shared_banks.php +++ b/public/question/classes/external/search_shared_banks.php @@ -22,6 +22,7 @@ use core_external\external_function_parameters; use core_external\external_single_structure; use core_external\external_multiple_structure; use core_external\external_value; +use core_question\local\bank\formatted_bank; use core_question\local\bank\question_bank_helper; use core\context; @@ -135,10 +136,13 @@ class search_shared_banks extends external_api { ); $suggestions = array_map( - fn($sharedbank) => [ - 'value' => $sharedbank->modid, - 'label' => $sharedbank->coursenamebankname, - ], + function (formatted_bank $sharedbank): array { + $sharedbank = $sharedbank->get_formatted(); + return [ + 'value' => $sharedbank->modid, + 'label' => $sharedbank->coursenamebankname, + ]; + }, $sharedbanks, ); diff --git a/public/question/classes/local/bank/formatted_bank.php b/public/question/classes/local/bank/formatted_bank.php new file mode 100644 index 00000000000..566cd523f1e --- /dev/null +++ b/public/question/classes/local/bank/formatted_bank.php @@ -0,0 +1,109 @@ +. + +namespace core_question\local\bank; + +use core\context; +use core_course\cm_info; +use JsonSerializable; + +/** + * Data class to hold bank info and categories, and return them with formatted names for output. + * + * @package core_question + * @copyright 2026 onwards Catalyst IT EU {@link https://catalyst-eu.net} + * @author Mark Johnson + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class formatted_bank implements JsonSerializable { + /** + * @var bool True if the bank is the module currently being viewed. + */ + public bool $current; + + /** + * Constructor + * + * @param cm_info $cminfo The course module info for the bank's course module + * @param context $filtercontext The context used for applying filters when formatting strings. + * @param array $questioncategories Array of categories belonging to the bank. + * @param bool $shared True if the bank contains shared questions, false if it contains private questions. + * @param bool $recent True if the bank was recently viewed by the user. + */ + public function __construct( + /** @var cm_info $cminfo Course module info. */ + public cm_info $cminfo, + /** @var context $filtercontext The context used for applying filters when formatting strings. */ + public context $filtercontext, + /** @var array $questioncategories Array of categories belonging to the bank. */ + public array $questioncategories, + /** @var bool True if the bank contains shared questions, false if it contains private questions. */ + public bool $shared, + /** @var bool True if the bank was recently viewed by the user. */ + public bool $recent, + ) { + } + + /** + * Return the question bank formatted for output. + * + * @return \stdClass + */ + public function get_formatted(): \stdClass { + $filteroptions = [ + 'escape' => false, + 'context' => $this->filtercontext, + ]; + $formattedname = $this->cminfo->get_formatted_name($filteroptions); + return (object) [ + 'name' => $formattedname, + 'modid' => $this->cminfo->id, + 'contextid' => $this->cminfo->context->id, + 'coursenamebankname' => get_string( + 'coursenamebankname', + 'question', + (object) [ + 'coursename' => format_string($this->cminfo->get_course()->shortname, true, $filteroptions), + 'bankname' => $formattedname, + ], + ), + 'cminfo' => $this->cminfo, + 'questioncategories' => $this->questioncategories, + 'shared' => $this->shared, + 'recent' => $this->recent, + ]; + } + + /** + * Return the formatted object for encoding as JSON, for example by web service routes. + */ + public function jsonSerialize(): \stdClass { + return $this->get_formatted(); + } + + /** + * Format multiple banks. + * + * @param formatted_bank[] $banks The formatted_bank objects to return formatted. + * @return array The formatted bank data. + */ + public static function format_banks(array $banks): array { + return array_map( + fn($bank) => $bank->get_formatted(), + $banks, + ); + } +} diff --git a/public/question/classes/local/bank/question_bank_helper.php b/public/question/classes/local/bank/question_bank_helper.php index 16a82518f22..21274e1d5d0 100644 --- a/public/question/classes/local/bank/question_bank_helper.php +++ b/public/question/classes/local/bank/question_bank_helper.php @@ -19,6 +19,8 @@ namespace core_question\local\bank; use cm_info; use context; use context_course; +use core\context\course; +use core\context\module; use core\context_helper; use core\di; use core\task\manager; @@ -366,7 +368,7 @@ class question_bank_helper { * @param int $notincourseid if supplied don't return any in this course id * @param ?context $filtercontext Optional context to use for all string filtering, useful for performance when calling with * parameters that will get banks across multiple contexts. - * @return cm_info[] + * @return formatted_bank[] */ public static function get_recently_used_open_banks( int $userid, @@ -397,7 +399,7 @@ class question_bank_helper { if (!empty($havingcap) && !(new question_edit_contexts($context))->have_one_cap($havingcap)) { continue; } - $record = self::get_formatted_bank($cm, filtercontext: $filtercontext); + $record = self::get_formatted_bank($cm, filtercontext: $filtercontext, isrecent: true); $banks[] = $record; } @@ -445,9 +447,15 @@ class question_bank_helper { * Used in qbank_bulkmove/bulk_move.mustache * @param ?context $filtercontext Optional context in which to apply filters. * - * @return stdClass + * @return formatted_bank */ - private static function get_formatted_bank(stdClass $cm, int $currentbankid = 0, ?context $filtercontext = null): stdClass { + private static function get_formatted_bank( + stdClass $cm, + int $currentbankid = 0, + ?context $filtercontext = null, + bool $isshared = true, + bool $isrecent = false, + ): formatted_bank { $cminfo = cm_info::create($cm); $concatedcats = !empty($cm->cats) ? explode(self::CATEGORY_SEPARATOR, $cm->cats) : []; @@ -461,21 +469,14 @@ class question_bank_helper { return $cat; }, $concatedcats); - $bank = new stdClass(); - $filteroptions = ['escape' => false]; - if (!is_null($filtercontext)) { - $filteroptions['context'] = $filtercontext; - } - $bank->name = $cminfo->get_formatted_name($filteroptions); - $bank->modid = $cminfo->id; - $bank->contextid = $cminfo->context->id; - if (!isset($filteroptions['context'])) { - $filteroptions['context'] = context_course::instance($cminfo->get_course()->id); - } - $bank->coursenamebankname = format_string($cminfo->get_course()->shortname, true, $filteroptions) . " - {$bank->name}"; - $bank->cminfo = $cminfo; - $bank->questioncategories = $categories; - return $bank; + $filtercontext ??= context_course::instance($cminfo->get_course()->id); + return new formatted_bank( + $cminfo, + $filtercontext, + $categories, + $isshared, + $isrecent, + ); } /** @@ -737,4 +738,51 @@ class question_bank_helper { // As a failsafe, limit the length of the final string in case the lang string is too long. return shorten_text($bankname, self::BANK_NAME_MAX_LENGTH); } + + /** + * Return a list of question banks. + * + * By default, this will just return a list of shared question banks (activities with FEATURE_PUBLISHES_QUESTIONS) in the given + * course context. + * Other options allow you to include private question banks and banks recently accessed by the user + * (which may be in other courses). + * + * @param course $coursecontext The course context to find shared and private banks within. + * @param module|null $modulecontext If set, use this instead of $coursecontext when applying text filters on bank names. + * @param bool $includeshared Include banks with shared questions. + * @param bool $includerecent Include banks recently viewed by the user. + * @return formatted_bank[] + */ + public static function get_banks_for_course( + course $coursecontext, + ?module $modulecontext = null, + bool $includeshared = true, + bool $includerecent = false, + ): array { + global $USER; + if ($modulecontext) { + $context = $modulecontext; + } else { + $context = $coursecontext; + } + $capabilities = ['moodle/question:useall', 'moodle/question:usemine']; + $banks = []; + if ($includeshared) { + $sharedbanks = self::get_activity_instances_with_shareable_questions( + incourseids: [$coursecontext->instanceid], + havingcap: $capabilities, + filtercontext: $context, + ); + $banks = array_merge($banks, $sharedbanks); + } + if ($includerecent) { + $recentbanks = self::get_recently_used_open_banks( + $USER->id, + filtercontext: $context, + havingcap: $capabilities, + ); + $banks = array_merge($banks, $recentbanks); + } + return $banks; + } } diff --git a/public/question/classes/output/question_bank_list.php b/public/question/classes/output/question_bank_list.php index c2acdb66060..c540765dab7 100644 --- a/public/question/classes/output/question_bank_list.php +++ b/public/question/classes/output/question_bank_list.php @@ -18,6 +18,7 @@ namespace core_question\output; use action_link; use core\output\pix_icon; +use core_question\local\bank\formatted_bank; use renderer_base; use core_courseformat\output\local\content\cm\controlmenu; use core_question\local\bank\question_bank_helper; @@ -34,10 +35,10 @@ class question_bank_list implements \renderable, \templatable { /** * Instantiate the output class. * - * @param array $bankinstances {@see question_bank_helper::get_activity_instances_with_shareable_questions()} + * @param formatted_bank[] $bankinstances {@see question_bank_helper::get_activity_instances_with_shareable_questions()} */ public function __construct( - /** @var array $bankinstances */ + /** @var formatted_bank[] $bankinstances */ protected readonly array $bankinstances ) { } @@ -51,6 +52,7 @@ class question_bank_list implements \renderable, \templatable { public function export_for_template(renderer_base $output): array { $banks = []; foreach ($this->bankinstances as $instance) { + $instance = $instance->get_formatted(); if (plugin_supports('mod', $instance->cminfo->modname, FEATURE_PUBLISHES_QUESTIONS)) { $returnurl = question_bank_helper::get_url_for_qbank_list($instance->cminfo->course); $format = course_get_format($instance->cminfo->course); diff --git a/public/question/classes/output/switch_question_bank.php b/public/question/classes/output/switch_question_bank.php index b958249be8b..f427068f569 100644 --- a/public/question/classes/output/switch_question_bank.php +++ b/public/question/classes/output/switch_question_bank.php @@ -26,12 +26,21 @@ namespace core_question\output; use cm_info; +use core\attribute\deprecated; +use core\deprecation; +use core_question\local\bank\formatted_bank; use core_question\local\bank\question_bank_helper; use renderer_base; /** * Get the switch question bank rendered content. Displays lists of shared banks the viewing user has access to. */ +#[deprecated( + replacement: 'core_question/bank_switcher Javascript module', + since: 5.2, + reason: 'Bank switching interface is now rendered client-side.', + mdl: 'MDL-87264', +)] class switch_question_bank implements \renderable, \templatable { /** @@ -41,6 +50,12 @@ class switch_question_bank implements \renderable, \templatable { * @param int $courseid of the current course. * @param int $userid of the user viewing the page. */ + #[deprecated( + replacement: 'core_question/bank_switcher Javascript module', + since: 5.2, + reason: 'Bank switching interface is now rendered client-side.', + mdl: 'MDL-87264', + )] public function __construct( /** @var int quiz course module id */ private readonly int $quizcmid, @@ -49,6 +64,7 @@ class switch_question_bank implements \renderable, \templatable { /** @var int id of the user viewing the page */ private readonly int $userid ) { + deprecation::emit_deprecation([$this, __FUNCTION__]); } /** @@ -57,18 +73,28 @@ class switch_question_bank implements \renderable, \templatable { * @param renderer_base $output * @return array */ + #[deprecated( + replacement: 'core_question/bank_switcher Javascript module', + since: 5.2, + reason: 'Bank switching interface is now rendered client-side.', + mdl: 'MDL-87264', + )] public function export_for_template(renderer_base $output) { - + deprecation::emit_deprecation([$this, __FUNCTION__]); [, $cm] = get_module_from_cmid($this->quizcmid); $cminfo = cm_info::create($cm); $capabilities = ['moodle/question:useall', 'moodle/question:usemine']; - $coursesharedbanks = question_bank_helper::get_activity_instances_with_shareable_questions( - incourseids: [$this->courseid], - havingcap: $capabilities, - filtercontext: $cminfo->context, + $coursesharedbanks = formatted_bank::format_banks( + question_bank_helper::get_activity_instances_with_shareable_questions( + incourseids: [$this->courseid], + havingcap: $capabilities, + filtercontext: $cminfo->context, + ), + ); + $recentlyviewedbanks = formatted_bank::format_banks( + question_bank_helper::get_recently_used_open_banks($this->userid, havingcap: $capabilities), ); - $recentlyviewedbanks = question_bank_helper::get_recently_used_open_banks($this->userid, havingcap: $capabilities); return [ 'quizname' => $cminfo->get_formatted_name(), diff --git a/public/question/classes/output/view_banks.php b/public/question/classes/output/view_banks.php index 944b41422a6..dd4bd57b98c 100644 --- a/public/question/classes/output/view_banks.php +++ b/public/question/classes/output/view_banks.php @@ -17,6 +17,7 @@ namespace core_question\output; use context_course; +use core_question\local\bank\formatted_bank; use core_question\local\bank\question_bank_helper; use renderer_base; use single_button; @@ -35,14 +36,14 @@ class view_banks implements \templatable, \renderable { /** * Create a new view_banks instance. * - * @param array $sharedbanks {@see question_bank_helper::get_activity_instances_with_shareable_questions()} - * @param array $privatebanks {@see question_bank_helper::get_activity_instances_with_private_questions()} + * @param formatted_bank[] $sharedbanks {@see question_bank_helper::get_activity_instances_with_shareable_questions()} + * @param formatted_bank[] $privatebanks {@see question_bank_helper::get_activity_instances_with_private_questions()} * @param stdClass $course the viewing course. */ public function __construct( - /** @var array Banks that can be shared */ + /** @var formatted_bank[] Banks that can be shared */ protected readonly array $sharedbanks, - /** @var array Banks that cannot be shared */ + /** @var formatted_bank[] Banks that cannot be shared */ protected readonly array $privatebanks, /** @var stdClass current course object */ protected readonly stdClass $course, diff --git a/public/question/classes/route/api/bank.php b/public/question/classes/route/api/bank.php index 3c8ee01ae43..64f256f6361 100644 --- a/public/question/classes/route/api/bank.php +++ b/public/question/classes/route/api/bank.php @@ -20,12 +20,19 @@ use core\context\course; use core\context\module; use core\exception\required_capability_exception; use core\param; +use core\router\parameters\query_course; +use core\router\parameters\query_coursemodule; use core\router\require_login; use core\router\route; -use core\router\schema\parameters\path_parameter; +use core\router\schema\example; +use core\router\schema\objects\array_of_things; +use core\router\schema\objects\schema_object; +use core\router\schema\parameters\query_parameter; +use core\router\schema\response\content\json_media_type; use core\router\schema\response\payload_response; -use core_question\local\bank\question_edit_contexts; -use core_question\local\bank\question_version_status; +use core\router\schema\response\response; +use core_question\local\bank\formatted_bank; +use core_question\local\bank\question_bank_helper; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -115,4 +122,61 @@ class bank { response: $response, ); } + + /** + * Return a list of formatted question banks matching the parameters. + * + * @param ServerRequestInterface $request + * @param ResponseInterface $response + * @param course $coursecontext The course context. + * @param module $currentmodulecontext The module context. + * @param question_bank_helper $helper Injected dependency. + * @return payload_response A list of question banks with formatted names, and whether they are shared and recently used. + */ + #[route( + path: '/banks', // Resolves to /api/rest/v2/question/banks. + queryparams: [ + new query_course(required: true), + new query_coursemodule('currentmodule'), + new query_parameter(name: 'includeshared', type: param::BOOL, default: true), + new query_parameter(name: 'includerecent', type: param::BOOL, default: false), + ], + responses: [ + new response( + statuscode: 200, + description: 'OK', + content: [ + new json_media_type( + schema: new schema_object( + content: [ + 'banks' => new array_of_things(thingtype: formatted_bank::class), + ], + ), + ), + ], + ), + ], + requirelogin: new require_login(true, courseattributename: 'course'), + )] + public function banks( + ServerRequestInterface $request, + ResponseInterface $response, + course $coursecontext, + module $currentmodulecontext, + question_bank_helper $helper, + ): payload_response { + $params = $request->getQueryParams(); + $banks = $helper::get_banks_for_course( + $coursecontext, + $currentmodulecontext, + $params['includeshared'], + $params['includerecent'], + ); + return new payload_response( + request: $request, + response: $response, + payload: ['banks' => $banks], + ); + } + } diff --git a/public/question/templates/switch_question_bank.mustache b/public/question/templates/switch_question_bank.mustache index 725b28ff155..57592601f91 100644 --- a/public/question/templates/switch_question_bank.mustache +++ b/public/question/templates/switch_question_bank.mustache @@ -19,122 +19,77 @@ Example context (json): { - "quizname": "Quiz 1", - "quizcmid": 1, - "quizcontextid": 1, + "contextid": 1, + "hasactivitybank": true, + "activitybank": { + "name": "Quiz 1", + "cmid": 1 + }, "hascoursesharedbanks": true, "coursesharedbanks": [ { "name": "Question bank 1", - "modid": "2", - "contextid": 2, - "coursenamebankname": "c1 - Question bank 1", - "cminfo": {}, - "questioncategories": [] + "modid": "2" }, { "name": "Question bank 2", - "modid": "3", - "contextid": 3, - "coursenamebankname": "c1 - Question bank 2", - "cminfo": {}, - "questioncategories": [] + "modid": "3" } ], "hasrecentlyviewedbanks": true, "recentlyviewedbanks": [ { - "name": "Question bank 3", "modid": "4", - "contextid": 4, - "coursenamebankname": "c2 - Question bank 4", - "cminfo": {}, - "questioncategories": [] + "coursenamebankname": "c2 - Question bank 4" }, { - "name": "Question bank 4", "modid": "6", - "contextid": 6, - "coursenamebankname": "c3 - Question bank 5", - "cminfo": {}, - "questioncategories": [] - } - ], - "hassharedbanks": true, - "sharedbanks": [ - { - "name": "Question bank 1", - "modid": "2", - "contextid": 2, - "coursenamebankname": "c1 - Question bank 1", - "cminfo": {}, - "questioncategories": [] - }, - { - "name": "Question bank 2", - "modid": "3", - "contextid": 3, - "coursenamebankname": "c1 - Question bank 2", - "cminfo": {}, - "questioncategories": [] - }, - { - "name": "Question bank 3", - "modid": "4", - "contextid": 4, - "coursenamebankname": "c2 - Question bank 4", - "cminfo": {}, - "questioncategories": [] - }, - { - "name": "Question bank 4", - "modid": "6", - "contextid": 6, - "coursenamebankname": "c3 - Question bank 5", - "cminfo": {}, - "questioncategories": [] + "coursenamebankname": "c3 - Question bank 5" } ] } }} -
-
{{#str}}quizquestionbank, core_question{{/str}}
- {{quizname}} -
+
+ {{#hasactivitybank}} +
+
{{#str}}quizquestionbank, core_question{{/str}}
+ {{activitybank.name}} +
+
+ {{/hasactivitybank}} -
+ {{#hascoursesharedbanks}} +
+
{{#str}}banksincourse, core_question{{/str}}
+ {{#coursesharedbanks}} + + {{/coursesharedbanks}} +
+
+ {{/hascoursesharedbanks}} -{{#hascoursesharedbanks}} -
-
{{#str}}banksincourse, core_question{{/str}}
- {{#coursesharedbanks}} - - {{/coursesharedbanks}} + {{#hasrecentlyviewedbanks}} +
+
{{#str}}recentlyviewedquestionbanks, core_question{{/str}}
+ {{#recentlyviewedbanks}} + + {{/recentlyviewedbanks}} +
+
+ {{/hasrecentlyviewedbanks}} + + +
+
{{#str}}otherquestionbank, core_question{{/str}}
+
-
-{{/hascoursesharedbanks}} - -{{#hasrecentlyviewedbanks}} -
-
{{#str}}recentlyviewedquestionbanks, core_question{{/str}}
- {{#recentlyviewedbanks}} - - {{/recentlyviewedbanks}} -
-
-{{/hasrecentlyviewedbanks}} - - -
-
{{#str}}otherquestionbank, core_question{{/str}}
-
diff --git a/public/question/tests/local/bank/question_bank_helper_test.php b/public/question/tests/local/bank/question_bank_helper_test.php index c877de22060..ea53f088b01 100644 --- a/public/question/tests/local/bank/question_bank_helper_test.php +++ b/public/question/tests/local/bank/question_bank_helper_test.php @@ -17,6 +17,7 @@ namespace core_question; use core\exception\coding_exception; +use core_question\local\bank\formatted_bank; use core_question\local\bank\question_bank_helper; /** @@ -127,7 +128,8 @@ final class question_bank_helper_test extends \advanced_testcase { ); $count = 0; - foreach ($sharedbanks as $courseinstance) { + foreach ($sharedbanks as $sharedbank) { + $courseinstance = $sharedbank->get_formatted(); // Must all be mod_qbanks. $this->assertEquals('qbank', $courseinstance->cminfo->modname); // Must have 2 categories each bank. @@ -148,7 +150,8 @@ final class question_bank_helper_test extends \advanced_testcase { ); $count = 0; - foreach ($privatebanks as $courseinstance) { + foreach ($privatebanks as $privatebank) { + $courseinstance = $privatebank->get_formatted(); // Must all be mod_quiz. $this->assertEquals('quiz', $courseinstance->cminfo->modname); // Must have 1 category in each bank. @@ -239,7 +242,9 @@ final class question_bank_helper_test extends \advanced_testcase { $this->assertCount(22, $allsharedbanks); // Searching for "2", we get the 4 banks with "2" in the name. - $twobanks = question_bank_helper::get_activity_instances_with_shareable_questions(search: '2'); + $twobanks = formatted_bank::format_banks( + question_bank_helper::get_activity_instances_with_shareable_questions(search: '2'), + ); $this->assertCount(4, $twobanks); $this->assertEquals( [$sharedmods[2]->cmid, $sharedmods[12]->cmid, $sharedmods[20]->cmid, $sharedmods[21]->cmid], @@ -247,12 +252,16 @@ final class question_bank_helper_test extends \advanced_testcase { ); // Searching for "Shared bank" with no limit, we should get all 21, but not "Another bank". - $sharedbanks = question_bank_helper::get_activity_instances_with_shareable_questions(search: 'Shared bank'); + $sharedbanks = formatted_bank::format_banks( + question_bank_helper::get_activity_instances_with_shareable_questions(search: 'Shared bank'), + ); $this->assertCount(21, $sharedbanks); $this->assertEmpty(array_filter($sharedbanks, fn($bank) => in_array($bank->name, ['Another bank']))); // Searching for "Shared bank" with a limit of 20, we should get all except number 21 and "Another bank". - $limitedbanks = question_bank_helper::get_activity_instances_with_shareable_questions(search: 'Shared bank', limit: 20); + $limitedbanks = formatted_bank::format_banks( + question_bank_helper::get_activity_instances_with_shareable_questions(search: 'Shared bank', limit: 20), + ); $this->assertCount(20, $limitedbanks); $this->assertEmpty(array_filter($limitedbanks, fn($bank) => in_array($bank->name, ['Shared bank 21', 'Another bank']))); } @@ -413,14 +422,18 @@ final class question_bank_helper_test extends \advanced_testcase { $viewedorder = array_reverse($banks); // Check that the courseid filter works. - $recentlyviewed = question_bank_helper::get_recently_used_open_banks($user->id, $course1->id); + $recentlyviewed = formatted_bank::format_banks( + question_bank_helper::get_recently_used_open_banks($user->id, $course1->id), + ); $this->assertCount(3, $recentlyviewed); // We should have the viewed banks in course 2. $courseviewed = array_slice($banks, 3, 3); $this->assertEqualsCanonicalizing(array_column($recentlyviewed, 'modid'), array_column($courseviewed, 'cmid')); // Check that the capability filter works. - $recentlyviewed = question_bank_helper::get_recently_used_open_banks($user->id, havingcap: ['moodle/question:useall']); + $recentlyviewed = formatted_bank::format_banks( + question_bank_helper::get_recently_used_open_banks($user->id, havingcap: ['moodle/question:useall']), + ); $this->assertCount(2, $recentlyviewed); // We should have the 2 most recently viewed banks in course 1. $capabilityviewed = array_slice($banks, 1, 2); @@ -431,7 +444,7 @@ final class question_bank_helper_test extends \advanced_testcase { // We only keep a record of 5 maximum. $this->assertCount(5, $recentlyviewed); foreach ($recentlyviewed as $order => $record) { - $this->assertEquals($viewedorder[$order]->cmid, $record->modid); + $this->assertEquals($viewedorder[$order]->cmid, $record->cminfo->id); } // Now if we view one of those again it should get bumped to the front of the list. @@ -444,12 +457,12 @@ final class question_bank_helper_test extends \advanced_testcase { // We should still have 5 maximum. $this->assertCount(5, $recentlyviewed); // The recently viewed on got bumped to the front. - $this->assertEquals($banks[2]->cmid, $recentlyviewed[0]->modid); + $this->assertEquals($banks[2]->cmid, $recentlyviewed[0]->cminfo->id); // The others got sorted accordingly behind it. - $this->assertEquals($banks[5]->cmid, $recentlyviewed[1]->modid); - $this->assertEquals($banks[4]->cmid, $recentlyviewed[2]->modid); - $this->assertEquals($banks[3]->cmid, $recentlyviewed[3]->modid); - $this->assertEquals($banks[1]->cmid, $recentlyviewed[4]->modid); + $this->assertEquals($banks[5]->cmid, $recentlyviewed[1]->cminfo->id); + $this->assertEquals($banks[4]->cmid, $recentlyviewed[2]->cminfo->id); + $this->assertEquals($banks[3]->cmid, $recentlyviewed[3]->cminfo->id); + $this->assertEquals($banks[1]->cmid, $recentlyviewed[4]->cminfo->id); // Now create a quiz and trigger the bank view of it. $quiz = self::getDataGenerator()->get_plugin_generator('mod_quiz')->create_instance(['course' => $course1]); @@ -463,7 +476,7 @@ final class question_bank_helper_test extends \advanced_testcase { // Make sure that we only store bank views for plugins that support FEATURE_PUBLISHES_QUESTIONS. foreach ($recentlyviewed as $record) { - $this->assertNotEquals($quiz->cmid, $record->modid); + $this->assertNotEquals($quiz->cmid, $record->cminfo->id); } // Now delete one of the viewed bank modules and get the records again. @@ -472,10 +485,10 @@ final class question_bank_helper_test extends \advanced_testcase { $this->assertCount(4, $recentlyviewed); // Check the order was retained. - $this->assertEquals($banks[5]->cmid, $recentlyviewed[0]->modid); - $this->assertEquals($banks[4]->cmid, $recentlyviewed[1]->modid); - $this->assertEquals($banks[3]->cmid, $recentlyviewed[2]->modid); - $this->assertEquals($banks[1]->cmid, $recentlyviewed[3]->modid); + $this->assertEquals($banks[5]->cmid, $recentlyviewed[0]->cminfo->id); + $this->assertEquals($banks[4]->cmid, $recentlyviewed[1]->cminfo->id); + $this->assertEquals($banks[3]->cmid, $recentlyviewed[2]->cminfo->id); + $this->assertEquals($banks[1]->cmid, $recentlyviewed[3]->cminfo->id); } /**