From a02dd5741db3e178fda886bbb991a259bbb5eb8f Mon Sep 17 00:00:00 2001 From: Mark Johnson Date: Mon, 2 Jun 2025 13:49:28 +0100 Subject: [PATCH] MDL-85069 qbank_bulkmove: Load banks and categories dynamically The bulk move question dialogue presents 2 autocomplete fields for selection of the target bank and category to move questions to. This was loading all banks and categories the user could access, and filtering categories based on the selected bank. On large sites, this was not usable as there were too many banks and categories to load leading to timeouts. This refactors the dialogue to load just the current bank and cagtegory initially, then load other options on demand via AJAX. --- mod/quiz/tests/behat/random_question.feature | 2 +- .../build/modal_question_bank_bulkmove.min.js | 2 +- .../modal_question_bank_bulkmove.min.js.map | 2 +- .../amd/src/modal_question_bank_bulkmove.js | 141 ++++++++---------- .../bulkmove/classes/output/bulk_move.php | 74 +++------ .../bulkmove/templates/bulk_move.mustache | 96 +++++------- .../bulkmove/tests/behat/bulk_move.feature | 42 +++++- .../question_categories_idnumber.feature | 2 +- 8 files changed, 152 insertions(+), 209 deletions(-) diff --git a/mod/quiz/tests/behat/random_question.feature b/mod/quiz/tests/behat/random_question.feature index e99abc3abf1..0f2ed508e78 100644 --- a/mod/quiz/tests/behat/random_question.feature +++ b/mod/quiz/tests/behat/random_question.feature @@ -49,7 +49,7 @@ Feature: Moving a question to another category should not affect random question And I click on "With selected" "button" And I click on question bulk action "move" And I open the autocomplete suggestions list in the ".search-categories" "css_element" - And I click on "Subcategory" item in the autocomplete list + And I click on "Subcategory" "list_item" in the "Move the selected questions to..." "dialogue" And I click on "Move questions" "button" And I should see "Are you sure you want to move these questions?" And I click on "Confirm" "button" diff --git a/question/bank/bulkmove/amd/build/modal_question_bank_bulkmove.min.js b/question/bank/bulkmove/amd/build/modal_question_bank_bulkmove.min.js index 67d6c775fec..848ff31809b 100644 --- a/question/bank/bulkmove/amd/build/modal_question_bank_bulkmove.min.js +++ b/question/bank/bulkmove/amd/build/modal_question_bank_bulkmove.min.js @@ -1,3 +1,3 @@ -define("qbank_bulkmove/modal_question_bank_bulkmove",["exports","core/modal","core/fragment","core/str","core/form-autocomplete","core_question/repository","core/templates","core/notification"],(function(_exports,_modal,Fragment,_str,_formAutocomplete,_repository,_templates,_notification){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}}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,_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),_templates=_interopRequireDefault(_templates),_notification=_interopRequireDefault(_notification);class ModalQuestionBankBulkmove extends _modal.default{static init(contextId,categoryId){document.addEventListener("click",(e=>{const trigger=e.target;trigger.classList.contains("dropdown-item")&&"move"===trigger.getAttribute("name")&&(e.preventDefault(),ModalQuestionBankBulkmove.create({contextId:contextId,title:(0,_str.getString)("bulkmoveheader","qbank_bulkmove"),show:!0,categoryId:categoryId}))}))}configure(modalConfig){this.contextId=modalConfig.contextId,this.targetBankContextId=modalConfig.contextId,this.initSelectedCategoryId(modalConfig.categoryId),modalConfig.removeOnClose=!0,super.configure(modalConfig)}initSelectedCategoryId(categoryId){const filter=new URLSearchParams(window.location.href).get("filter");if(filter){var _JSON$parse;const filteredCategoryId=null===(_JSON$parse=JSON.parse(filter))||void 0===_JSON$parse?void 0:_JSON$parse.category.values[0];return this.currentCategoryId=filteredCategoryId>0?filteredCategoryId:null,void(this.targetCategoryId=filteredCategoryId)}this.currentCategoryId=categoryId,this.targetCategoryId=categoryId}show(){return this.display(this.contextId,this.currentCategoryId),super.show()}async display(currentBankContextId,currentCategoryId){this.bodyPromise=await Fragment.loadFragment("qbank_bulkmove","bulk_move",currentBankContextId,{categoryid:currentCategoryId}),await this.setBody(this.bodyPromise),await this.enhanceSelects(document.querySelectorAll(ModalQuestionBankBulkmove.SELECTORS.ORIGINAL_SELECTS)),this.registerEnhancedEventListeners(),this.mapCategoryContextIds(),this.updateSaveButtonState()}registerEnhancedEventListeners(){document.querySelector(ModalQuestionBankBulkmove.SELECTORS.SEARCH_CATEGORY).addEventListener("change",(e=>{const targetCategoryId=e.currentTarget.value;this.targetCategoryId=targetCategoryId,this.rebuildOptions(this.targetBankContextId,targetCategoryId),this.updateSaveButtonState()})),document.querySelector(ModalQuestionBankBulkmove.SELECTORS.SEARCH_BANK).addEventListener("change",(e=>{const selectedBankContextId=e.currentTarget.value;this.targetBankContextId=selectedBankContextId,this.rebuildOptions(selectedBankContextId,this.targetCategoryId)})),this.getModal().on("click",ModalQuestionBankBulkmove.SELECTORS.SAVE_BUTTON,(e=>{e.preventDefault(),this.displayConfirmMove()}))}mapCategoryContextIds(){const customSelectCategoryOptions=document.querySelectorAll(ModalQuestionBankBulkmove.SELECTORS.CATEGORY_OPTIONS);if(0===customSelectCategoryOptions.length)return;const categoryContextIds=[];customSelectCategoryOptions.forEach((option=>{categoryContextIds[option.value]=option.dataset.bankContextid})),this.categoryContextIds=categoryContextIds}async displayConfirmMove(){this.setTitle((0,_str.getString)("confirm","core")),this.setBody((0,_str.getString)("confirmmove","qbank_bulkmove")),this.hasFooterContent()?this.showFooter():(this.setFooter(_templates.default.render("qbank_bulkmove/bulk_move_footer",{})),await this.getFooterPromise(),document.querySelector(ModalQuestionBankBulkmove.SELECTORS.CONFIRM_BUTTON).addEventListener("click",(e=>{e.preventDefault(),this.moveQuestionsAfterConfirm(this.targetBankContextId,this.targetCategoryId)})),document.querySelector(ModalQuestionBankBulkmove.SELECTORS.CANCEL_BUTTON).addEventListener("click",(e=>{e.preventDefault(),this.setTitle((0,_str.getString)("bulkmoveheader","qbank_bulkmove")),this.setBodyContent(_templates.default.renderForPromise("core/loading",{})),this.hideFooter(),this.display(this.targetBankContextId,this.targetCategoryId)})))}rebuildOptions(selectedBankContextId,selectedCategoryId){const categoryContextIds=this.categoryContextIds,customSelectCategoryOptions=document.querySelectorAll(ModalQuestionBankBulkmove.SELECTORS.CATEGORY_OPTIONS);if(selectedBankContextId?(customSelectCategoryOptions.forEach((option=>{option.dataset.bankContextid!=selectedBankContextId?option.dataset.enabled="disabled":option.dataset.enabled="enabled"})),this.updateCategorySelectorState(!0)):this.updateCategorySelectorState(!1),selectedCategoryId&&selectedBankContextId&&categoryContextIds[selectedCategoryId]!=selectedBankContextId){document.querySelector('.search-categories span[role="option"][data-value="'+selectedCategoryId+'"]').click()}}updateCategorySelectorState(toEnable){const warning=document.querySelector(ModalQuestionBankBulkmove.SELECTORS.CATEGORY_WARNING),enhancedInput=document.querySelector(ModalQuestionBankBulkmove.SELECTORS.CATEGORY_ENHANCED_INPUT),suggestionButton=document.querySelector(ModalQuestionBankBulkmove.SELECTORS.CATEGORY_SUGGESTION);toEnable?(warning.classList.add("d-none"),enhancedInput.removeAttribute("disabled"),suggestionButton.classList.remove("d-none")):(warning.classList.remove("d-none"),enhancedInput.setAttribute("disabled","disabled"),suggestionButton.classList.add("d-none"))}updateSaveButtonState(){const saveButton=document.querySelector(ModalQuestionBankBulkmove.SELECTORS.SAVE_BUTTON),targetCategoryId=this.targetCategoryId;targetCategoryId&&targetCategoryId!=this.currentCategoryId?saveButton.removeAttribute("disabled"):saveButton.setAttribute("disabled","disabled")}async moveQuestionsAfterConfirm(targetContextId,targetCategoryId){await this.setBody(_templates.default.render("core/loading",{}));const qelements=document.querySelectorAll(ModalQuestionBankBulkmove.SELECTORS.SELECTED_QUESTIONS),questionids=[];qelements.forEach((element=>{if(element.checked){const name=element.getAttribute("name");questionids.push(name.substr(1,name.length))}})),0===questionids.length&&await _notification.default.exception("No questions selected");try{window.location.href=await(0,_repository.moveQuestions)(targetContextId,targetCategoryId,questionids.join(),window.location.href)}catch(error){await _notification.default.exception(error)}}async enhanceSelects(selects){const placeholder=await(0,_str.getString)("searchbyname","mod_quiz"),enhanced=[];if(selects.length>0){for(let i=0;i{const trigger=e.target;trigger.classList.contains("dropdown-item")&&"move"===trigger.getAttribute("name")&&(e.preventDefault(),ModalQuestionBankBulkmove.create({contextId:contextId,title:(0,_str.getString)("bulkmoveheader","qbank_bulkmove"),show:!0,categoryId:categoryId}))}))}configure(modalConfig){this.contextId=modalConfig.contextId,this.targetBankContextId=modalConfig.contextId,this.initSelectedCategoryId(modalConfig.categoryId),modalConfig.removeOnClose=!0,super.configure(modalConfig)}initSelectedCategoryId(categoryId){const filter=new URLSearchParams(window.location.href).get("filter");if(filter){var _JSON$parse;const filteredCategoryId=null===(_JSON$parse=JSON.parse(filter))||void 0===_JSON$parse?void 0:_JSON$parse.category.values[0];return this.currentCategoryId=filteredCategoryId>0?filteredCategoryId:null,void(this.targetCategoryId=filteredCategoryId)}this.currentCategoryId=categoryId,this.targetCategoryId=categoryId}show(){return this.display(this.contextId,this.currentCategoryId),super.show()}async display(currentBankContextId,currentCategoryId){const displayPending=new _pending.default("qbank_bulkmove/bulk_move_modal");this.bodyPromise=await Fragment.loadFragment("qbank_bulkmove","bulk_move",currentBankContextId,{categoryid:currentCategoryId}),await this.setBody(this.bodyPromise),await this.enhanceSelects(),this.registerEnhancedEventListeners(),this.updateSaveButtonState(),displayPending.resolve()}registerEnhancedEventListeners(){document.querySelector(ModalQuestionBankBulkmove.SELECTORS.SEARCH_CATEGORY).addEventListener("change",(()=>{this.updateSaveButtonState()})),document.querySelector(ModalQuestionBankBulkmove.SELECTORS.SEARCH_BANK).addEventListener("change",(async e=>{await this.updateCategorySelector(e.currentTarget.value),this.updateSaveButtonState()})),this.getModal().on("click",ModalQuestionBankBulkmove.SELECTORS.SAVE_BUTTON,(e=>{e.preventDefault(),this.displayConfirmMove()}))}async displayConfirmMove(){this.setTitle((0,_str.getString)("confirm","core")),this.setBody((0,_str.getString)("confirmmove","qbank_bulkmove")),this.hasFooterContent()?this.showFooter():(this.setFooter(_templates.default.render("qbank_bulkmove/bulk_move_footer",{})),await this.getFooterPromise(),document.querySelector(ModalQuestionBankBulkmove.SELECTORS.CONFIRM_BUTTON).addEventListener("click",(e=>{e.preventDefault(),this.moveQuestionsAfterConfirm(this.targetBankContextId,this.targetCategoryId)})),document.querySelector(ModalQuestionBankBulkmove.SELECTORS.CANCEL_BUTTON).addEventListener("click",(e=>{e.preventDefault(),this.setTitle((0,_str.getString)("bulkmoveheader","qbank_bulkmove")),this.setBodyContent(_templates.default.renderForPromise("core/loading",{})),this.hideFooter(),this.display(this.targetBankContextId,this.targetCategoryId)})))}updateCategorySelector(selectedBankCmId){return selectedBankCmId?Fragment.loadFragment("core_question","category_selector",this.contextId,{bankcmid:selectedBankCmId}).then(((html,js)=>{const categorySelector=document.querySelector(ModalQuestionBankBulkmove.SELECTORS.QUESTION_CATEGORY_SELECTOR);return _templates.default.replaceNode(categorySelector,html,js)})).then((()=>(document.querySelector(ModalQuestionBankBulkmove.SELECTORS.CATEGORY_WARNING).classList.add("d-none"),this.enhanceSelects()))).catch(_notification.default.exception):(this.updateCategorySelectorState(!1),Promise.resolve())}updateCategorySelectorState(toEnable){const warning=document.querySelector(ModalQuestionBankBulkmove.SELECTORS.CATEGORY_WARNING),enhancedInput=document.querySelector(ModalQuestionBankBulkmove.SELECTORS.CATEGORY_ENHANCED_INPUT),suggestionButton=document.querySelector(ModalQuestionBankBulkmove.SELECTORS.CATEGORY_SUGGESTION),selection=document.querySelector(ModalQuestionBankBulkmove.SELECTORS.CATEGORY_SELECTION);toEnable?(warning.classList.add("d-none"),enhancedInput.removeAttribute("disabled"),suggestionButton.classList.remove("d-none")):(warning.classList.remove("d-none"),enhancedInput.setAttribute("disabled","disabled"),suggestionButton.classList.add("d-none"),selection.click())}updateSaveButtonState(){const saveButton=document.querySelector(ModalQuestionBankBulkmove.SELECTORS.SAVE_BUTTON),categorySelector=document.querySelector(ModalQuestionBankBulkmove.SELECTORS.SEARCH_CATEGORY);[this.targetCategoryId,this.targetBankContextId]=categorySelector.value.split(","),this.targetCategoryId&&this.targetCategoryId!==this.currentCategoryId?saveButton.removeAttribute("disabled"):saveButton.setAttribute("disabled","disabled")}async moveQuestionsAfterConfirm(targetContextId,targetCategoryId){await this.setBody(_templates.default.render("core/loading",{}));const qelements=document.querySelectorAll(ModalQuestionBankBulkmove.SELECTORS.SELECTED_QUESTIONS),questionids=[];qelements.forEach((element=>{if(element.checked){const name=element.getAttribute("name");questionids.push(name.substr(1,name.length))}})),0===questionids.length&&await _notification.default.exception("No questions selected");try{window.location.href=await(0,_repository.moveQuestions)(targetContextId,targetCategoryId,questionids.join(),window.location.href)}catch(error){await _notification.default.exception(error)}}async enhanceSelects(){const placeholder=await(0,_str.getString)("searchbyname","mod_quiz");await _formAutocomplete.default.enhance(ModalQuestionBankBulkmove.SELECTORS.SEARCH_BANK,!1,"core_question/question_banks_datasource",placeholder,!1,!0,"",!0),await _formAutocomplete.default.enhance(ModalQuestionBankBulkmove.SELECTORS.SEARCH_CATEGORY,!1,null,placeholder,!1,!0,"",!0)}}return _exports.default=ModalQuestionBankBulkmove,_defineProperty(ModalQuestionBankBulkmove,"TYPE","qbank_bulkmove/bulkmove"),_defineProperty(ModalQuestionBankBulkmove,"SELECTORS",{SAVE_BUTTON:'[data-action="bulkmovesave"]',SELECTED_QUESTIONS:'table#categoryquestions input[id^="checkq"]',SEARCH_BANK:"#searchbanks",SEARCH_CATEGORY:".selectcategory",QUESTION_CATEGORY_SELECTOR:".question_category_selector",CATEGORY_OPTIONS:".selectcategory option",BANK_OPTIONS:"#searchbanks option",CATEGORY_ENHANCED_INPUT:".search-categories input",ORIGINAL_SELECTS:"select.bulk-move",CATEGORY_WARNING:"#searchcatwarning",CATEGORY_SUGGESTION:".search-categories span.form-autocomplete-downarrow",CATEGORY_SELECTION:'.search-categories span[role="option"][data-active-selection="true"]',CONFIRM_BUTTON:'.bulk-move-footer button[data-action="save"]',CANCEL_BUTTON:'.bulk-move-footer button[data-action="cancel"]'}),_exports.default})); //# sourceMappingURL=modal_question_bank_bulkmove.min.js.map \ No newline at end of file diff --git a/question/bank/bulkmove/amd/build/modal_question_bank_bulkmove.min.js.map b/question/bank/bulkmove/amd/build/modal_question_bank_bulkmove.min.js.map index 7b194d981f2..e7e76da3dca 100644 --- a/question/bank/bulkmove/amd/build/modal_question_bank_bulkmove.min.js.map +++ b/question/bank/bulkmove/amd/build/modal_question_bank_bulkmove.min.js.map @@ -1 +1 @@ -{"version":3,"file":"modal_question_bank_bulkmove.min.js","sources":["../src/modal_question_bank_bulkmove.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 bulkmove questions modal.\n *\n * @module qbank_bulkmove/modal_question_bank_bulkmove\n * @copyright 2024 onwards Catalyst IT EU {@link https://catalyst-eu.net}\n * @author Simon Adams \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';\nimport {moveQuestions} from 'core_question/repository';\nimport Templates from 'core/templates';\nimport Notification from 'core/notification';\n\n\nexport default class ModalQuestionBankBulkmove extends Modal {\n static TYPE = 'qbank_bulkmove/bulkmove';\n\n static SELECTORS = {\n SAVE_BUTTON: '[data-action=\"bulkmovesave\"]',\n SELECTED_QUESTIONS: 'table#categoryquestions input[id^=\"checkq\"]',\n SEARCH_BANK: '#searchbanks',\n SEARCH_CATEGORY: '#searchcategories',\n CATEGORY_OPTIONS: '#searchcategories option',\n BANK_OPTIONS: '#searchbanks option',\n CATEGORY_ENHANCED_INPUT: '.search-categories input',\n ORIGINAL_SELECTS: 'select.bulk-move',\n CATEGORY_WARNING: '#searchcatwarning',\n CATEGORY_SUGGESTION: '.search-categories span.form-autocomplete-downarrow',\n CONFIRM_BUTTON: '.bulk-move-footer button[data-action=\"save\"]',\n CANCEL_BUTTON: '.bulk-move-footer button[data-action=\"cancel\"]'\n };\n\n /**\n * @param {integer} contextId The current bank context id.\n * @param {integer} categoryId The current question category id.\n */\n static init(contextId, categoryId) {\n document.addEventListener('click', (e) => {\n const trigger = e.target;\n if (trigger.classList.contains('dropdown-item') && trigger.getAttribute('name') === 'move') {\n e.preventDefault();\n ModalQuestionBankBulkmove.create({\n contextId,\n title: getString('bulkmoveheader', 'qbank_bulkmove'),\n show: true,\n categoryId: categoryId,\n });\n }\n });\n }\n\n /**\n * Set the initialised config on the class.\n *\n * @param {Object} modalConfig\n */\n configure(modalConfig) {\n this.contextId = modalConfig.contextId;\n this.targetBankContextId = modalConfig.contextId;\n this.initSelectedCategoryId(modalConfig.categoryId);\n modalConfig.removeOnClose = true;\n super.configure(modalConfig);\n }\n\n /**\n * Initialise the category select based on the data passed to the JS or if a filter is applied in the url.\n * @param {integer} categoryId\n */\n initSelectedCategoryId(categoryId) {\n const filter = new URLSearchParams(window.location.href).get('filter');\n if (filter) {\n const filteredCategoryId = JSON.parse(filter)?.category.values[0];\n this.currentCategoryId = filteredCategoryId > 0 ? filteredCategoryId : null;\n this.targetCategoryId = filteredCategoryId;\n return;\n }\n this.currentCategoryId = categoryId;\n this.targetCategoryId = categoryId;\n }\n\n /**\n * Render the modal contents.\n * @return {Promise}\n */\n show() {\n void this.display(this.contextId, this.currentCategoryId);\n return super.show();\n }\n\n /**\n * Get the content to display and enhance the selects into auto complete fields.\n * @param {integer} currentBankContextId\n * @param {integer} currentCategoryId\n */\n async display(currentBankContextId, currentCategoryId) {\n this.bodyPromise = await Fragment.loadFragment(\n 'qbank_bulkmove',\n 'bulk_move',\n currentBankContextId,\n {\n 'categoryid': currentCategoryId,\n }\n );\n\n await this.setBody(this.bodyPromise);\n await this.enhanceSelects(document.querySelectorAll(ModalQuestionBankBulkmove.SELECTORS.ORIGINAL_SELECTS));\n this.registerEnhancedEventListeners();\n this.mapCategoryContextIds();\n this.updateSaveButtonState();\n }\n\n /**\n * Register event listeners on the enhanced selects. Must be done after they have been enhanced.\n */\n registerEnhancedEventListeners() {\n document.querySelector(ModalQuestionBankBulkmove.SELECTORS.SEARCH_CATEGORY).addEventListener(\"change\", (e) => {\n const targetCategoryId = e.currentTarget.value;\n this.targetCategoryId = targetCategoryId;\n this.rebuildOptions(this.targetBankContextId, targetCategoryId);\n this.updateSaveButtonState();\n });\n\n document.querySelector(ModalQuestionBankBulkmove.SELECTORS.SEARCH_BANK).addEventListener(\"change\", (e) => {\n const selectedBankContextId = e.currentTarget.value;\n this.targetBankContextId = selectedBankContextId;\n this.rebuildOptions(selectedBankContextId, this.targetCategoryId);\n });\n\n this.getModal().on(\"click\", ModalQuestionBankBulkmove.SELECTORS.SAVE_BUTTON, (e) => {\n e.preventDefault();\n void this.displayConfirmMove();\n });\n }\n\n /**\n * Set a map, so we can determine which bank belongs to which category.\n */\n mapCategoryContextIds() {\n const customSelectCategoryOptions = document.querySelectorAll(ModalQuestionBankBulkmove.SELECTORS.CATEGORY_OPTIONS);\n\n if (customSelectCategoryOptions.length === 0) {\n return;\n }\n\n const categoryContextIds = [];\n\n customSelectCategoryOptions.forEach((option) => {\n categoryContextIds[option.value] = option.dataset.bankContextid;\n });\n\n this.categoryContextIds = categoryContextIds;\n }\n\n /**\n * Update the body with a confirmation prompt and set confirm cancel buttons in the footer.\n * @return {Promise}\n */\n async displayConfirmMove() {\n this.setTitle(getString('confirm', 'core'));\n this.setBody(getString('confirmmove', 'qbank_bulkmove'));\n if (!this.hasFooterContent()) {\n // We don't have the footer yet so go grab it and register event listeners on the buttons.\n this.setFooter(Templates.render('qbank_bulkmove/bulk_move_footer', {}));\n await this.getFooterPromise();\n\n document.querySelector(ModalQuestionBankBulkmove.SELECTORS.CONFIRM_BUTTON).addEventListener(\"click\", (e) => {\n e.preventDefault();\n this.moveQuestionsAfterConfirm(this.targetBankContextId, this.targetCategoryId);\n });\n\n document.querySelector(ModalQuestionBankBulkmove.SELECTORS.CANCEL_BUTTON).addEventListener(\"click\", (e) => {\n e.preventDefault();\n this.setTitle(getString('bulkmoveheader', 'qbank_bulkmove'));\n this.setBodyContent(Templates.renderForPromise('core/loading', {}));\n this.hideFooter();\n this.display(this.targetBankContextId, this.targetCategoryId);\n });\n } else {\n // We already have a footer so just show it.\n this.showFooter();\n }\n }\n\n /**\n * Dynamically update all enhanced selects options based on what is selected.\n *\n * @param {integer} selectedBankContextId\n * @param {integer} selectedCategoryId\n */\n rebuildOptions(selectedBankContextId, selectedCategoryId) {\n const categoryContextIds = this.categoryContextIds;\n const customSelectCategoryOptions = document.querySelectorAll(ModalQuestionBankBulkmove.SELECTORS.CATEGORY_OPTIONS);\n\n // Disable the category selector if no bank selected.\n if (!selectedBankContextId) {\n this.updateCategorySelectorState(false);\n } else {\n // Mark to be disabled all the categories not belonging to the selected bank.\n // This will then be handled by the enhanced selects event handlers.\n customSelectCategoryOptions.forEach((option) => {\n if (option.dataset.bankContextid != selectedBankContextId) {\n option.dataset.enabled = 'disabled';\n } else {\n option.dataset.enabled = 'enabled';\n }\n });\n this.updateCategorySelectorState(true);\n }\n\n // De-select the selected category if it does not belong to the selected bank.\n if (selectedCategoryId && selectedBankContextId && categoryContextIds[selectedCategoryId] != selectedBankContextId) {\n const selectedCategoryElement = document.querySelector(\n '.search-categories span[role=\"option\"][data-value=\"' + selectedCategoryId + '\"]'\n );\n selectedCategoryElement.click();\n }\n }\n\n /**\n * Disable/enable the enhanced category selector field.\n * @param {boolean} toEnable True to enable, false to disable the field.\n */\n updateCategorySelectorState(toEnable) {\n const warning = document.querySelector(ModalQuestionBankBulkmove.SELECTORS.CATEGORY_WARNING);\n const enhancedInput = document.querySelector(ModalQuestionBankBulkmove.SELECTORS.CATEGORY_ENHANCED_INPUT);\n const suggestionButton = document.querySelector(ModalQuestionBankBulkmove.SELECTORS.CATEGORY_SUGGESTION);\n\n if (toEnable) {\n warning.classList.add('d-none');\n enhancedInput.removeAttribute('disabled');\n suggestionButton.classList.remove('d-none');\n } else {\n warning.classList.remove('d-none');\n enhancedInput.setAttribute('disabled', 'disabled');\n suggestionButton.classList.add('d-none');\n }\n }\n\n /**\n * Disable the button if the selected category is the same as the one the questions already belong to. Enable it otherwise.\n */\n updateSaveButtonState() {\n const saveButton = document.querySelector(ModalQuestionBankBulkmove.SELECTORS.SAVE_BUTTON);\n const targetCategoryId = this.targetCategoryId;\n\n if (targetCategoryId && targetCategoryId != this.currentCategoryId) {\n saveButton.removeAttribute('disabled');\n } else {\n saveButton.setAttribute('disabled', 'disabled');\n }\n }\n\n /**\n * Move the selected questions to their new target category.\n * @param {integer} targetContextId the target bank context id.\n * @param {integer} targetCategoryId the target question category id.\n * @return {Promise}\n */\n async moveQuestionsAfterConfirm(targetContextId, targetCategoryId) {\n await this.setBody(Templates.render('core/loading', {}));\n const qelements = document.querySelectorAll(ModalQuestionBankBulkmove.SELECTORS.SELECTED_QUESTIONS);\n const questionids = [];\n qelements.forEach((element) => {\n if (element.checked) {\n const name = element.getAttribute('name');\n questionids.push(name.substr(1, name.length));\n }\n });\n if (questionids.length === 0) {\n await Notification.exception('No questions selected');\n }\n\n try {\n window.location.href = await moveQuestions(\n targetContextId,\n targetCategoryId,\n questionids.join(),\n window.location.href\n );\n } catch (error) {\n await Notification.exception(error);\n }\n }\n\n /**\n * Take the provided select options and enhance them into auto-complete fields.\n * @param {NodeList} selects Custom select elements to enhance.\n * @return {Promise}\n */\n async enhanceSelects(selects) {\n const placeholder = await getString('searchbyname', 'mod_quiz');\n const enhanced = [];\n\n if (selects.length > 0) {\n for (let i = 0; i < selects.length; i++) {\n enhanced.push(AutoComplete.enhance(\n selects.item(i),\n false,\n '',\n placeholder,\n false,\n true,\n '',\n true\n )\n );\n }\n\n return Promise.all(enhanced);\n }\n\n return Promise.reject('No selects to enhance');\n }\n}\n"],"names":["ModalQuestionBankBulkmove","Modal","contextId","categoryId","document","addEventListener","e","trigger","target","classList","contains","getAttribute","preventDefault","create","title","show","configure","modalConfig","targetBankContextId","initSelectedCategoryId","removeOnClose","filter","URLSearchParams","window","location","href","get","filteredCategoryId","JSON","parse","_JSON$parse","category","values","currentCategoryId","targetCategoryId","this","display","super","currentBankContextId","bodyPromise","Fragment","loadFragment","setBody","enhanceSelects","querySelectorAll","SELECTORS","ORIGINAL_SELECTS","registerEnhancedEventListeners","mapCategoryContextIds","updateSaveButtonState","querySelector","SEARCH_CATEGORY","currentTarget","value","rebuildOptions","SEARCH_BANK","selectedBankContextId","getModal","on","SAVE_BUTTON","displayConfirmMove","customSelectCategoryOptions","CATEGORY_OPTIONS","length","categoryContextIds","forEach","option","dataset","bankContextid","setTitle","hasFooterContent","showFooter","setFooter","Templates","render","getFooterPromise","CONFIRM_BUTTON","moveQuestionsAfterConfirm","CANCEL_BUTTON","setBodyContent","renderForPromise","hideFooter","selectedCategoryId","enabled","updateCategorySelectorState","click","toEnable","warning","CATEGORY_WARNING","enhancedInput","CATEGORY_ENHANCED_INPUT","suggestionButton","CATEGORY_SUGGESTION","add","removeAttribute","remove","setAttribute","saveButton","targetContextId","qelements","SELECTED_QUESTIONS","questionids","element","checked","name","push","substr","Notification","exception","join","error","selects","placeholder","enhanced","i","AutoComplete","enhance","item","Promise","all","reject","BANK_OPTIONS"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"modal_question_bank_bulkmove.min.js","sources":["../src/modal_question_bank_bulkmove.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 bulkmove questions modal.\n *\n * @module qbank_bulkmove/modal_question_bank_bulkmove\n * @copyright 2024 onwards Catalyst IT EU {@link https://catalyst-eu.net}\n * @author Simon Adams \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';\nimport {moveQuestions} from 'core_question/repository';\nimport Templates from 'core/templates';\nimport Notification from 'core/notification';\nimport Pending from 'core/pending';\n\n\nexport default class ModalQuestionBankBulkmove extends Modal {\n static TYPE = 'qbank_bulkmove/bulkmove';\n\n static SELECTORS = {\n SAVE_BUTTON: '[data-action=\"bulkmovesave\"]',\n SELECTED_QUESTIONS: 'table#categoryquestions input[id^=\"checkq\"]',\n SEARCH_BANK: '#searchbanks',\n SEARCH_CATEGORY: '.selectcategory',\n QUESTION_CATEGORY_SELECTOR: '.question_category_selector',\n CATEGORY_OPTIONS: '.selectcategory option',\n BANK_OPTIONS: '#searchbanks option',\n CATEGORY_ENHANCED_INPUT: '.search-categories input',\n ORIGINAL_SELECTS: 'select.bulk-move',\n CATEGORY_WARNING: '#searchcatwarning',\n CATEGORY_SUGGESTION: '.search-categories span.form-autocomplete-downarrow',\n CATEGORY_SELECTION: '.search-categories span[role=\"option\"][data-active-selection=\"true\"]',\n CONFIRM_BUTTON: '.bulk-move-footer button[data-action=\"save\"]',\n CANCEL_BUTTON: '.bulk-move-footer button[data-action=\"cancel\"]'\n };\n\n /**\n * @param {integer} contextId The current bank context id.\n * @param {integer} categoryId The current question category id.\n */\n static init(contextId, categoryId) {\n document.addEventListener('click', (e) => {\n const trigger = e.target;\n if (trigger.classList.contains('dropdown-item') && trigger.getAttribute('name') === 'move') {\n e.preventDefault();\n ModalQuestionBankBulkmove.create({\n contextId,\n title: getString('bulkmoveheader', 'qbank_bulkmove'),\n show: true,\n categoryId: categoryId,\n });\n }\n });\n }\n\n /**\n * Set the initialised config on the class.\n *\n * @param {Object} modalConfig\n */\n configure(modalConfig) {\n this.contextId = modalConfig.contextId;\n this.targetBankContextId = modalConfig.contextId;\n this.initSelectedCategoryId(modalConfig.categoryId);\n modalConfig.removeOnClose = true;\n super.configure(modalConfig);\n }\n\n /**\n * Initialise the category select based on the data passed to the JS or if a filter is applied in the url.\n * @param {integer} categoryId\n */\n initSelectedCategoryId(categoryId) {\n const filter = new URLSearchParams(window.location.href).get('filter');\n if (filter) {\n const filteredCategoryId = JSON.parse(filter)?.category.values[0];\n this.currentCategoryId = filteredCategoryId > 0 ? filteredCategoryId : null;\n this.targetCategoryId = filteredCategoryId;\n return;\n }\n this.currentCategoryId = categoryId;\n this.targetCategoryId = categoryId;\n }\n\n /**\n * Render the modal contents.\n * @return {Promise}\n */\n show() {\n void this.display(this.contextId, this.currentCategoryId);\n return super.show();\n }\n\n /**\n * Get the content to display and enhance the selects into auto complete fields.\n * @param {integer} currentBankContextId\n * @param {integer} currentCategoryId\n */\n async display(currentBankContextId, currentCategoryId) {\n const displayPending = new Pending('qbank_bulkmove/bulk_move_modal');\n this.bodyPromise = await Fragment.loadFragment(\n 'qbank_bulkmove',\n 'bulk_move',\n currentBankContextId,\n {\n 'categoryid': currentCategoryId,\n }\n );\n\n await this.setBody(this.bodyPromise);\n await this.enhanceSelects();\n this.registerEnhancedEventListeners();\n this.updateSaveButtonState();\n displayPending.resolve();\n }\n\n /**\n * Register event listeners on the enhanced selects. Must be done after they have been enhanced.\n */\n registerEnhancedEventListeners() {\n document.querySelector(ModalQuestionBankBulkmove.SELECTORS.SEARCH_CATEGORY).addEventListener(\"change\", () => {\n this.updateSaveButtonState();\n });\n\n document.querySelector(ModalQuestionBankBulkmove.SELECTORS.SEARCH_BANK).addEventListener(\"change\", async(e) => {\n await this.updateCategorySelector(e.currentTarget.value);\n this.updateSaveButtonState();\n });\n\n this.getModal().on(\"click\", ModalQuestionBankBulkmove.SELECTORS.SAVE_BUTTON, (e) => {\n e.preventDefault();\n void this.displayConfirmMove();\n });\n }\n\n /**\n * Update the body with a confirmation prompt and set confirm cancel buttons in the footer.\n * @return {Promise}\n */\n async displayConfirmMove() {\n this.setTitle(getString('confirm', 'core'));\n this.setBody(getString('confirmmove', 'qbank_bulkmove'));\n if (!this.hasFooterContent()) {\n // We don't have the footer yet so go grab it and register event listeners on the buttons.\n this.setFooter(Templates.render('qbank_bulkmove/bulk_move_footer', {}));\n await this.getFooterPromise();\n\n document.querySelector(ModalQuestionBankBulkmove.SELECTORS.CONFIRM_BUTTON).addEventListener(\"click\", (e) => {\n e.preventDefault();\n this.moveQuestionsAfterConfirm(this.targetBankContextId, this.targetCategoryId);\n });\n\n document.querySelector(ModalQuestionBankBulkmove.SELECTORS.CANCEL_BUTTON).addEventListener(\"click\", (e) => {\n e.preventDefault();\n this.setTitle(getString('bulkmoveheader', 'qbank_bulkmove'));\n this.setBodyContent(Templates.renderForPromise('core/loading', {}));\n this.hideFooter();\n this.display(this.targetBankContextId, this.targetCategoryId);\n });\n } else {\n // We already have a footer so just show it.\n this.showFooter();\n }\n }\n\n /**\n * Update the category selector based on the selected question bank.\n *\n * @param {Number} selectedBankCmId\n * @return {Promise} Resolved when the update is complete.\n */\n updateCategorySelector(selectedBankCmId) {\n if (!selectedBankCmId) {\n this.updateCategorySelectorState(false);\n return Promise.resolve();\n } else {\n return Fragment.loadFragment(\n 'core_question',\n 'category_selector',\n this.contextId,\n {\n 'bankcmid': selectedBankCmId,\n }\n )\n .then((html, js) => {\n const categorySelector = document.querySelector(ModalQuestionBankBulkmove.SELECTORS.QUESTION_CATEGORY_SELECTOR);\n return Templates.replaceNode(categorySelector, html, js);\n })\n .then(() => {\n document.querySelector(ModalQuestionBankBulkmove.SELECTORS.CATEGORY_WARNING).classList.add('d-none');\n return this.enhanceSelects();\n })\n .catch(Notification.exception);\n }\n }\n\n /**\n * Disable/enable the enhanced category selector field.\n * @param {boolean} toEnable True to enable, false to disable the field.\n */\n updateCategorySelectorState(toEnable) {\n const warning = document.querySelector(ModalQuestionBankBulkmove.SELECTORS.CATEGORY_WARNING);\n const enhancedInput = document.querySelector(ModalQuestionBankBulkmove.SELECTORS.CATEGORY_ENHANCED_INPUT);\n const suggestionButton = document.querySelector(ModalQuestionBankBulkmove.SELECTORS.CATEGORY_SUGGESTION);\n const selection = document.querySelector(ModalQuestionBankBulkmove.SELECTORS.CATEGORY_SELECTION);\n\n if (toEnable) {\n warning.classList.add('d-none');\n enhancedInput.removeAttribute('disabled');\n suggestionButton.classList.remove('d-none');\n } else {\n warning.classList.remove('d-none');\n enhancedInput.setAttribute('disabled', 'disabled');\n suggestionButton.classList.add('d-none');\n selection.click(); // Clear selected category.\n }\n }\n\n /**\n * Disable the button if the selected category is the same as the one the questions already belong to. Enable it otherwise.\n */\n updateSaveButtonState() {\n const saveButton = document.querySelector(ModalQuestionBankBulkmove.SELECTORS.SAVE_BUTTON);\n const categorySelector = document.querySelector(ModalQuestionBankBulkmove.SELECTORS.SEARCH_CATEGORY);\n [this.targetCategoryId, this.targetBankContextId] = categorySelector.value.split(',');\n\n if (this.targetCategoryId && this.targetCategoryId !== this.currentCategoryId) {\n saveButton.removeAttribute('disabled');\n } else {\n saveButton.setAttribute('disabled', 'disabled');\n }\n }\n\n /**\n * Move the selected questions to their new target category.\n * @param {integer} targetContextId the target bank context id.\n * @param {integer} targetCategoryId the target question category id.\n * @return {Promise}\n */\n async moveQuestionsAfterConfirm(targetContextId, targetCategoryId) {\n await this.setBody(Templates.render('core/loading', {}));\n const qelements = document.querySelectorAll(ModalQuestionBankBulkmove.SELECTORS.SELECTED_QUESTIONS);\n const questionids = [];\n qelements.forEach((element) => {\n if (element.checked) {\n const name = element.getAttribute('name');\n questionids.push(name.substr(1, name.length));\n }\n });\n if (questionids.length === 0) {\n await Notification.exception('No questions selected');\n }\n\n try {\n window.location.href = await moveQuestions(\n targetContextId,\n targetCategoryId,\n questionids.join(),\n window.location.href\n );\n } catch (error) {\n await Notification.exception(error);\n }\n }\n\n /**\n * Take the provided select options and enhance them into auto-complete fields.\n *\n * @return {Promise}\n */\n async enhanceSelects() {\n const placeholder = await getString('searchbyname', 'mod_quiz');\n\n await AutoComplete.enhance(\n ModalQuestionBankBulkmove.SELECTORS.SEARCH_BANK,\n false,\n 'core_question/question_banks_datasource',\n placeholder,\n false,\n true,\n '',\n true,\n );\n\n await AutoComplete.enhance(\n ModalQuestionBankBulkmove.SELECTORS.SEARCH_CATEGORY,\n false,\n null,\n placeholder,\n false,\n true,\n '',\n true,\n );\n }\n}\n"],"names":["ModalQuestionBankBulkmove","Modal","contextId","categoryId","document","addEventListener","e","trigger","target","classList","contains","getAttribute","preventDefault","create","title","show","configure","modalConfig","targetBankContextId","initSelectedCategoryId","removeOnClose","filter","URLSearchParams","window","location","href","get","filteredCategoryId","JSON","parse","_JSON$parse","category","values","currentCategoryId","targetCategoryId","this","display","super","currentBankContextId","displayPending","Pending","bodyPromise","Fragment","loadFragment","setBody","enhanceSelects","registerEnhancedEventListeners","updateSaveButtonState","resolve","querySelector","SELECTORS","SEARCH_CATEGORY","SEARCH_BANK","async","updateCategorySelector","currentTarget","value","getModal","on","SAVE_BUTTON","displayConfirmMove","setTitle","hasFooterContent","showFooter","setFooter","Templates","render","getFooterPromise","CONFIRM_BUTTON","moveQuestionsAfterConfirm","CANCEL_BUTTON","setBodyContent","renderForPromise","hideFooter","selectedBankCmId","then","html","js","categorySelector","QUESTION_CATEGORY_SELECTOR","replaceNode","CATEGORY_WARNING","add","catch","Notification","exception","updateCategorySelectorState","Promise","toEnable","warning","enhancedInput","CATEGORY_ENHANCED_INPUT","suggestionButton","CATEGORY_SUGGESTION","selection","CATEGORY_SELECTION","removeAttribute","remove","setAttribute","click","saveButton","split","targetContextId","qelements","querySelectorAll","SELECTED_QUESTIONS","questionids","forEach","element","checked","name","push","substr","length","join","error","placeholder","AutoComplete","enhance","CATEGORY_OPTIONS","BANK_OPTIONS","ORIGINAL_SELECTS"],"mappings":"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"} \ No newline at end of file diff --git a/question/bank/bulkmove/amd/src/modal_question_bank_bulkmove.js b/question/bank/bulkmove/amd/src/modal_question_bank_bulkmove.js index 2e4bcb8e1ce..9d398bd8230 100644 --- a/question/bank/bulkmove/amd/src/modal_question_bank_bulkmove.js +++ b/question/bank/bulkmove/amd/src/modal_question_bank_bulkmove.js @@ -29,6 +29,7 @@ import AutoComplete from 'core/form-autocomplete'; import {moveQuestions} from 'core_question/repository'; import Templates from 'core/templates'; import Notification from 'core/notification'; +import Pending from 'core/pending'; export default class ModalQuestionBankBulkmove extends Modal { @@ -38,13 +39,15 @@ export default class ModalQuestionBankBulkmove extends Modal { SAVE_BUTTON: '[data-action="bulkmovesave"]', SELECTED_QUESTIONS: 'table#categoryquestions input[id^="checkq"]', SEARCH_BANK: '#searchbanks', - SEARCH_CATEGORY: '#searchcategories', - CATEGORY_OPTIONS: '#searchcategories option', + SEARCH_CATEGORY: '.selectcategory', + QUESTION_CATEGORY_SELECTOR: '.question_category_selector', + CATEGORY_OPTIONS: '.selectcategory option', BANK_OPTIONS: '#searchbanks option', CATEGORY_ENHANCED_INPUT: '.search-categories input', ORIGINAL_SELECTS: 'select.bulk-move', CATEGORY_WARNING: '#searchcatwarning', CATEGORY_SUGGESTION: '.search-categories span.form-autocomplete-downarrow', + CATEGORY_SELECTION: '.search-categories span[role="option"][data-active-selection="true"]', CONFIRM_BUTTON: '.bulk-move-footer button[data-action="save"]', CANCEL_BUTTON: '.bulk-move-footer button[data-action="cancel"]' }; @@ -112,6 +115,7 @@ export default class ModalQuestionBankBulkmove extends Modal { * @param {integer} currentCategoryId */ async display(currentBankContextId, currentCategoryId) { + const displayPending = new Pending('qbank_bulkmove/bulk_move_modal'); this.bodyPromise = await Fragment.loadFragment( 'qbank_bulkmove', 'bulk_move', @@ -122,27 +126,23 @@ export default class ModalQuestionBankBulkmove extends Modal { ); await this.setBody(this.bodyPromise); - await this.enhanceSelects(document.querySelectorAll(ModalQuestionBankBulkmove.SELECTORS.ORIGINAL_SELECTS)); + await this.enhanceSelects(); this.registerEnhancedEventListeners(); - this.mapCategoryContextIds(); this.updateSaveButtonState(); + displayPending.resolve(); } /** * Register event listeners on the enhanced selects. Must be done after they have been enhanced. */ registerEnhancedEventListeners() { - document.querySelector(ModalQuestionBankBulkmove.SELECTORS.SEARCH_CATEGORY).addEventListener("change", (e) => { - const targetCategoryId = e.currentTarget.value; - this.targetCategoryId = targetCategoryId; - this.rebuildOptions(this.targetBankContextId, targetCategoryId); + document.querySelector(ModalQuestionBankBulkmove.SELECTORS.SEARCH_CATEGORY).addEventListener("change", () => { this.updateSaveButtonState(); }); - document.querySelector(ModalQuestionBankBulkmove.SELECTORS.SEARCH_BANK).addEventListener("change", (e) => { - const selectedBankContextId = e.currentTarget.value; - this.targetBankContextId = selectedBankContextId; - this.rebuildOptions(selectedBankContextId, this.targetCategoryId); + document.querySelector(ModalQuestionBankBulkmove.SELECTORS.SEARCH_BANK).addEventListener("change", async(e) => { + await this.updateCategorySelector(e.currentTarget.value); + this.updateSaveButtonState(); }); this.getModal().on("click", ModalQuestionBankBulkmove.SELECTORS.SAVE_BUTTON, (e) => { @@ -151,25 +151,6 @@ export default class ModalQuestionBankBulkmove extends Modal { }); } - /** - * Set a map, so we can determine which bank belongs to which category. - */ - mapCategoryContextIds() { - const customSelectCategoryOptions = document.querySelectorAll(ModalQuestionBankBulkmove.SELECTORS.CATEGORY_OPTIONS); - - if (customSelectCategoryOptions.length === 0) { - return; - } - - const categoryContextIds = []; - - customSelectCategoryOptions.forEach((option) => { - categoryContextIds[option.value] = option.dataset.bankContextid; - }); - - this.categoryContextIds = categoryContextIds; - } - /** * Update the body with a confirmation prompt and set confirm cancel buttons in the footer. * @return {Promise} @@ -201,37 +182,33 @@ export default class ModalQuestionBankBulkmove extends Modal { } /** - * Dynamically update all enhanced selects options based on what is selected. + * Update the category selector based on the selected question bank. * - * @param {integer} selectedBankContextId - * @param {integer} selectedCategoryId + * @param {Number} selectedBankCmId + * @return {Promise} Resolved when the update is complete. */ - rebuildOptions(selectedBankContextId, selectedCategoryId) { - const categoryContextIds = this.categoryContextIds; - const customSelectCategoryOptions = document.querySelectorAll(ModalQuestionBankBulkmove.SELECTORS.CATEGORY_OPTIONS); - - // Disable the category selector if no bank selected. - if (!selectedBankContextId) { + updateCategorySelector(selectedBankCmId) { + if (!selectedBankCmId) { this.updateCategorySelectorState(false); + return Promise.resolve(); } else { - // Mark to be disabled all the categories not belonging to the selected bank. - // This will then be handled by the enhanced selects event handlers. - customSelectCategoryOptions.forEach((option) => { - if (option.dataset.bankContextid != selectedBankContextId) { - option.dataset.enabled = 'disabled'; - } else { - option.dataset.enabled = 'enabled'; + return Fragment.loadFragment( + 'core_question', + 'category_selector', + this.contextId, + { + 'bankcmid': selectedBankCmId, } - }); - this.updateCategorySelectorState(true); - } - - // De-select the selected category if it does not belong to the selected bank. - if (selectedCategoryId && selectedBankContextId && categoryContextIds[selectedCategoryId] != selectedBankContextId) { - const selectedCategoryElement = document.querySelector( - '.search-categories span[role="option"][data-value="' + selectedCategoryId + '"]' - ); - selectedCategoryElement.click(); + ) + .then((html, js) => { + const categorySelector = document.querySelector(ModalQuestionBankBulkmove.SELECTORS.QUESTION_CATEGORY_SELECTOR); + return Templates.replaceNode(categorySelector, html, js); + }) + .then(() => { + document.querySelector(ModalQuestionBankBulkmove.SELECTORS.CATEGORY_WARNING).classList.add('d-none'); + return this.enhanceSelects(); + }) + .catch(Notification.exception); } } @@ -243,6 +220,7 @@ export default class ModalQuestionBankBulkmove extends Modal { const warning = document.querySelector(ModalQuestionBankBulkmove.SELECTORS.CATEGORY_WARNING); const enhancedInput = document.querySelector(ModalQuestionBankBulkmove.SELECTORS.CATEGORY_ENHANCED_INPUT); const suggestionButton = document.querySelector(ModalQuestionBankBulkmove.SELECTORS.CATEGORY_SUGGESTION); + const selection = document.querySelector(ModalQuestionBankBulkmove.SELECTORS.CATEGORY_SELECTION); if (toEnable) { warning.classList.add('d-none'); @@ -252,6 +230,7 @@ export default class ModalQuestionBankBulkmove extends Modal { warning.classList.remove('d-none'); enhancedInput.setAttribute('disabled', 'disabled'); suggestionButton.classList.add('d-none'); + selection.click(); // Clear selected category. } } @@ -260,9 +239,10 @@ export default class ModalQuestionBankBulkmove extends Modal { */ updateSaveButtonState() { const saveButton = document.querySelector(ModalQuestionBankBulkmove.SELECTORS.SAVE_BUTTON); - const targetCategoryId = this.targetCategoryId; + const categorySelector = document.querySelector(ModalQuestionBankBulkmove.SELECTORS.SEARCH_CATEGORY); + [this.targetCategoryId, this.targetBankContextId] = categorySelector.value.split(','); - if (targetCategoryId && targetCategoryId != this.currentCategoryId) { + if (this.targetCategoryId && this.targetCategoryId !== this.currentCategoryId) { saveButton.removeAttribute('disabled'); } else { saveButton.setAttribute('disabled', 'disabled'); @@ -303,31 +283,32 @@ export default class ModalQuestionBankBulkmove extends Modal { /** * Take the provided select options and enhance them into auto-complete fields. - * @param {NodeList} selects Custom select elements to enhance. + * * @return {Promise} */ - async enhanceSelects(selects) { + async enhanceSelects() { const placeholder = await getString('searchbyname', 'mod_quiz'); - const enhanced = []; - if (selects.length > 0) { - for (let i = 0; i < selects.length; i++) { - enhanced.push(AutoComplete.enhance( - selects.item(i), - false, - '', - placeholder, - false, - true, - '', - true - ) - ); - } + await AutoComplete.enhance( + ModalQuestionBankBulkmove.SELECTORS.SEARCH_BANK, + false, + 'core_question/question_banks_datasource', + placeholder, + false, + true, + '', + true, + ); - return Promise.all(enhanced); - } - - return Promise.reject('No selects to enhance'); + await AutoComplete.enhance( + ModalQuestionBankBulkmove.SELECTORS.SEARCH_CATEGORY, + false, + null, + placeholder, + false, + true, + '', + true, + ); } } diff --git a/question/bank/bulkmove/classes/output/bulk_move.php b/question/bank/bulkmove/classes/output/bulk_move.php index d7b59154320..0ffc7ca4f40 100644 --- a/question/bank/bulkmove/classes/output/bulk_move.php +++ b/question/bank/bulkmove/classes/output/bulk_move.php @@ -27,6 +27,7 @@ namespace qbank_bulkmove\output; use cm_info; use core_question\local\bank\question_bank_helper; +use core_question\output\question_category_selector; use moodle_url; use renderer_base; use single_button; @@ -62,44 +63,26 @@ class bulk_move implements \renderable, \templatable { public function export_for_template(renderer_base $output) { [, $cmrec] = get_module_from_cmid($this->currentbankid); - $currentbank = cm_info::create($cmrec); + $currentbankcm = cm_info::create($cmrec); - // Get all shared banks and categories and make the current bank/category pre-selected, i.e. ordered first in the list. - $bankstorender = question_bank_helper::get_activity_instances_with_shareable_questions( - [], - [], - ['moodle/question:add'], - true, - $this->currentbankid - ); - - $allcategories = array_map(function($bank) { - if ($bank->modid == $this->currentbankid) { - // If this is the current bank then sort the categories so that our current categoryid is first in the list. - $this->sort_categories($bank->questioncategories, $this->currentcategoryid); - } - return $bank->questioncategories; - }, $bankstorender); - - // The current bank is not a shared bank, but grab the category records anyway so that we can at least allow them - // to be moved to another local category in the bank. - if (!plugin_supports('mod', $currentbank->modname, FEATURE_PUBLISHES_QUESTIONS, false)) { - $currentbank = question_bank_helper::get_activity_instances_with_private_questions( - incourseids: [$currentbank->course], - getcategories: true, + // Get the current bank and its categories. All other banks and categories will be loaded dynamically. + if (plugin_supports('mod', $currentbankcm->modname, FEATURE_PUBLISHES_QUESTIONS, false)) { + $banktorender = question_bank_helper::get_activity_instances_with_shareable_questions( + havingcap: ['moodle/question:add'], currentbankid: $this->currentbankid, + filtercontext: $currentbankcm->context, + limit: 1, + )[0]; + } 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]; - $currentbankcats = $currentbank->questioncategories; - // Move the current category to the top of the list. - $this->sort_categories($currentbankcats, $this->currentcategoryid); - // Add the current bank categories to the front of the categories list. - array_unshift($allcategories, $currentbankcats); - // Add the current bank to the front of the banks list. - array_unshift($bankstorender, $currentbank); } - // Flatten all the categories into a 2D array. - $allcategories = array_merge(...array_values($allcategories)); + $categoryselector = new question_category_selector([$currentbankcm->context], autocomplete: true); $savebutton = new single_button( new moodle_url('#'), @@ -113,29 +96,10 @@ class bulk_move implements \renderable, \templatable { ); return [ - 'allsharedbanks' => $bankstorender, - 'allcategories' => $allcategories, + 'bank' => $banktorender, + 'categories' => $categoryselector->export_for_template($output), 'save' => $savebutton->export_for_template($output), + 'contextid' => $currentbankcm->context->id, ]; } - - /** - * Wrapped usort to move the currentcategoryid to the top of the list of question categories. - * - * @param array $categories categories to sort - * @param int $currentcategoryid the category to be sorted to the top of the list - * @return void - */ - protected function sort_categories(array &$categories, int $currentcategoryid): void { - usort($categories, static function($categorya, $categoryb) use ($currentcategoryid) { - if ($categorya->id != $currentcategoryid && $categoryb->id == $currentcategoryid) { - return 1; - } - if ($categorya->id == $currentcategoryid && $categoryb->id != $currentcategoryid) { - return -1; - } - - return $categoryb->id <=> $categorya->id; - }); - } } diff --git a/question/bank/bulkmove/templates/bulk_move.mustache b/question/bank/bulkmove/templates/bulk_move.mustache index 7ffce4d05da..0a021484856 100644 --- a/question/bank/bulkmove/templates/bulk_move.mustache +++ b/question/bank/bulkmove/templates/bulk_move.mustache @@ -21,12 +21,7 @@ Example context (json): { - "allsharedbanks": [ - { - "name": "Quiz 1", - "contextid": 1, - "coursenamebankname": "c1 - Quiz 1" - }, + "bank": [ { "name": "Question bank 1", "modid": "2", @@ -34,55 +29,27 @@ "coursenamebankname": "c1 - Question bank 1", "cminfo": {}, "questioncategories": [ - { - "id": "22", - "name": "Default for Question bank 1", - "contextid": "22", - "enabled": "disabled" - } - ] - }, - { - "name": "Question bank 2", - "modid": "3", - "contextid": 33, - "coursenamebankname": "c2 - Question bank 2", - "cminfo": {}, - "questioncategories": [ - { - "id": "23", - "name": "Default for Question bank 2", - "contextid": "23", - "enabled": "disabled" - } ] } ], - "allcategories": [ - { - "id": "22", - "name": "Default for Question bank 1", - "contextid": "22" - }, - { - "id": "23", - "name": "Default for Question bank 2", - "contextid": "23", - "enabled": "disabled" - }, - { - "id": "24", - "name": "Default for Question bank 3", - "contextid": "24", - "enabled": "disabled" - }, - { - "id": "25", - "name": "Default for Question bank 4", - "contextid": "25", - "enabled": "disabled" - } - ], + "categories": { + "categories": [ + { + "value": "0", + "disabled": true, + "label": "Bank name 1" + }, + { + "value": "123,456", + "selected": true, + "label": "Category 1" + }, + { + "value": "124,456", + "label": "Category 2" + } + ] + }, "save": { "id": "single_button669e368d496707", "formid": null, @@ -111,25 +78,30 @@ ], "actions": [], "hasactions": false - } + }, + "contextid": 1000 } }}
{{#str}}questionbank, question{{/str}}
- + {{#bank}} + + {{/bank}}
{{#str}}questioncategories, question{{/str}}
- + {{#categories}} + {{> core_question/question_category_selector }} + {{/categories}}
{{#str}}warning, qbank_bulkmove{{/str}}
diff --git a/question/bank/bulkmove/tests/behat/bulk_move.feature b/question/bank/bulkmove/tests/behat/bulk_move.feature index ce047a2fe72..428284704b3 100644 --- a/question/bank/bulkmove/tests/behat/bulk_move.feature +++ b/question/bank/bulkmove/tests/behat/bulk_move.feature @@ -78,8 +78,8 @@ Feature: Use the qbank plugin manager page for bulkmove Then I should not see "C3 - Question bank 3" in the ".search-banks" "css_element" And I click on "C1 - Question bank 1" item in the autocomplete list Then I should not see "Test questions 1" in the ".search-categories .form-autocomplete-selection" "css_element" + And the field "selectcategory" matches value "Test questions 2 (1)" And I open the autocomplete suggestions list in the ".search-categories" "css_element" - And "Test questions 2" "autocomplete_suggestions" should exist And "Test questions 3" "autocomplete_suggestions" should not exist And "Test questions 4" "autocomplete_suggestions" should not exist And "Test questions 5" "autocomplete_suggestions" should exist @@ -92,7 +92,7 @@ Feature: Use the qbank plugin manager page for bulkmove And I click on "With selected" "button" And I click on "move" "button" And I open the autocomplete suggestions list in the ".search-categories" "css_element" - And I click on "Test questions 6" item in the autocomplete list + And I click on "Test questions 6 (1)" item in the autocomplete list And I click on "Move questions" "button" Then I should see "Are you sure you want to move these questions?" And I click on "Confirm" "button" @@ -136,7 +136,30 @@ Feature: Use the qbank plugin manager page for bulkmove And I should not see question bulk action "move" @javascript - Scenario: Questions can be moved to a different bank + Scenario: Questions can be moved to a different bank, if the user has the correct capability + Given the following "role" exists: + | name | Question adder | + | shortname | adder | + And the following "role capability" exists: + | role | adder | + | moodle/question:add | allow | + And the following "course" exists: + | fullname | Course 4 | + | shortname | C4 | + | category | 0 | + And the following "course enrolment" exists: + | course | C4 | + | user | teacher1 | + | role | adder | + And the following "activity" exists: + | activity | qbank | + | course | C4 | + | name | Question bank 4 | + | idnumber | qbank4 | + And the following "question category" exists: + | contextlevel | Activity module | + | reference | qbank4 | + | name | Test questions 7 | Given I am on the "Test quiz" "mod_quiz > question bank" page logged in as "teacher1" And I press "Create a new question ..." And I set the field "item_qtype_truefalse" to "1" @@ -151,11 +174,14 @@ Feature: Use the qbank plugin manager page for bulkmove And I click on "With selected" "button" And I click on "move" "button" And the field "searchbanks" matches value "C1 - Test quiz" - And the field "searchcategories" matches value "Test questions 1" + And the field "selectcategory" matches value "Test questions 1 (2)" And I open the autocomplete suggestions list in the ".search-banks" "css_element" + And I should see "C1 - Question bank 1" in the ".search-banks .form-autocomplete-suggestions" "css_element" + And I should see "C2 - Question bank 2" in the ".search-banks .form-autocomplete-suggestions" "css_element" + And I should see "C4 - Question bank 4" in the ".search-banks .form-autocomplete-suggestions" "css_element" + And I should not see "C3 - Question bank 3" in the ".search-banks .form-autocomplete-suggestions" "css_element" And I click on "C1 - Question bank 1" item in the autocomplete list - And I open the autocomplete suggestions list in the ".search-categories" "css_element" - And I click on "Test questions 2" item in the autocomplete list + And the field "selectcategory" matches value "Test questions 2 (1)" And I click on "Move questions" "button" And I should see "Are you sure you want to move these questions?" When I click on "Confirm" "button" @@ -165,6 +191,6 @@ Feature: Use the qbank plugin manager page for bulkmove And I click on "With selected" "button" And I click on "move" "button" And the field "searchbanks" matches value "C1 - Question bank 1" - And the field "searchcategories" matches value "Test questions 2" + And the field "selectcategory" matches value "Test questions 2 (2)" # The moved question should be highlighted - And the "class" attribute of "Seventh question" "table_row" should contain "highlight" \ No newline at end of file + And the "class" attribute of "Seventh question" "table_row" should contain "highlight" diff --git a/question/tests/behat/question_categories_idnumber.feature b/question/tests/behat/question_categories_idnumber.feature index e57c931683c..c557c0cb2de 100644 --- a/question/tests/behat/question_categories_idnumber.feature +++ b/question/tests/behat/question_categories_idnumber.feature @@ -83,7 +83,7 @@ Feature: A teacher can put questions with idnumbers in categories in the questio And I click on "With selected" "button" And I click on question bulk action "move" And I open the autocomplete suggestions list in the ".search-categories" "css_element" - And I click on "Used category" item in the autocomplete list + And I click on "Used category" "list_item" in the "Move the selected questions to..." "dialogue" And I click on "Move questions" "button" And I should see "Are you sure you want to move these questions?" And I click on "Confirm" "button"