From 37b55c085cd239376f2edbd3f1e66c8c844a8f34 Mon Sep 17 00:00:00 2001 From: Mark Johnson Date: Mon, 24 Nov 2025 10:12:02 +0000 Subject: [PATCH] MDL-87264 qbank_managecategories: Move categories between banks This restores the ability to move an entire category to a different context, which now means a different activity module. The feature is accessed via the Move option in the category's action menu, which now has a "Switch bank" option like the "add from question bank" UI in the quiz. When a category is moved to a different context, it will disappear from the current page, and a link to the destination bank is displayed. --- .../amd/build/category.min.js | 2 +- .../amd/build/category.min.js.map | 2 +- .../amd/build/categorymanager.min.js | 2 +- .../amd/build/categorymanager.min.js.map | 2 +- .../bank/managecategories/amd/src/category.js | 24 ++-- .../amd/src/categorymanager.js | 2 + .../classes/external/move_category.php | 9 +- .../lang/en/qbank_managecategories.php | 1 + .../templates/categories.mustache | 12 +- .../templates/move_category_list.mustache | 118 ++++++++++++------ .../templates/move_context_list.mustache | 3 +- .../behat/move_question_categories.feature | 31 +++++ .../tests/external/move_category_test.php | 11 +- public/question/classes/route/api/bank.php | 4 + 14 files changed, 160 insertions(+), 63 deletions(-) diff --git a/public/question/bank/managecategories/amd/build/category.min.js b/public/question/bank/managecategories/amd/build/category.min.js index 00f460f7c4c..f35716ce81f 100644 --- a/public/question/bank/managecategories/amd/build/category.min.js +++ b/public/question/bank/managecategories/amd/build/category.min.js @@ -1,3 +1,3 @@ -define("qbank_managecategories/category",["exports","core/reactive","qbank_managecategories/categorymanager","core/templates","core/modal","core/str","core_question/bank_switcher","core/fetch","core/notification","core/url","core/local/inplace_editable/events"],(function(_exports,_reactive,_categorymanager,_templates,_modal,_str,_bank_switcher,_fetch,_notification,CoreUrl,_events){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}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_templates=_interopRequireDefault(_templates),_modal=_interopRequireDefault(_modal),_bank_switcher=_interopRequireDefault(_bank_switcher),_fetch=_interopRequireDefault(_fetch),_notification=_interopRequireDefault(_notification),CoreUrl=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}(CoreUrl);class _default extends _reactive.BaseComponent{create(descriptor){this.name=descriptor.element.id,this.selectors={CATEGORY_LIST:".qbank_managecategories-categorylist",CATEGORY_ITEM:".qbank_managecategories-item[data-categoryid]",CATEGORY_CONTENTS:".qbank_managecategories-item > .container",EDIT_BUTTON:'[data-action="addeditcategory"]',EDITABLE_CATEGORY_NAME:'.inplaceeditable[data-itemtype="categoryname"]',MOVE_BUTTON:'[role="menuitem"][data-actiontype="move"]',CONTEXT:".qbank_managecategories-categorylist[data-contextid]",MODAL_CATEGORY_ITEM:".modal_category_item[data-movingcategoryid]",CONTENT_AREA:".qbank_managecategories-details",CATEGORY_ID:id=>"#category-".concat(id),CONTENT_CONTAINER:id=>"#category-".concat(id," .qbank_managecategories-childlistcontainer"),CHILD_LIST:id=>'ul[data-categoryid="'.concat(id,'"]'),PREVIOUS_SIBLING:sortorder=>':scope > [data-sortorder="'.concat(sortorder,'"]'),SWITCH_QUESTION_BANK:'[data-action="switch-question-bank"]',MOVE_BANK_HEADER:".bank-header"},this.classes={NO_BOTTOM_PADDING:"pb-0",DRAGHANDLE:"draghandle",DROPTARGET:"qbank_managecategories-droptarget-before"},this.ids={CATEGORY:id=>"category-".concat(id)}}stateReady(){this.initDragDrop(),this.addEventListener(this.getElement(this.selectors.EDIT_BUTTON),"click",_categorymanager.categorymanager.showEditModal);const moveButton=this.getElement(this.selectors.MOVE_BUTTON);this.addEventListener(moveButton,"click",this.showMoveModal),this.addEventListener(this.getElement(),_events.eventTypes.elementUpdated,(e=>{const editable=e.target.closest(this.selectors.EDITABLE_CATEGORY_NAME);_categorymanager.categorymanager.updateCategoryName(editable.dataset.itemid,editable.dataset.value)}))}destroy(){this.deInitDragDrop()}initDragDrop(){this.deInitDragDrop(),this.element.classList.contains(this.classes.DRAGHANDLE)&&(this.getDraggableData=this._getDraggableData),this.dragdrop=new _reactive.DragDrop(this)}deInitDragDrop(){void 0!==this.dragdrop&&(void 0!==this.getDraggableData&&(this.dragdrop.setDraggable(!1),this.getDraggableData=void 0),this.dragdrop.unregister(),this.dragdrop=void 0)}static init(target,selectors){return new this({element:document.querySelector(target),selectors:selectors,reactive:_categorymanager.categorymanager})}_getDraggableData(){return{id:this.getElement().dataset.categoryid}}validateDropData(){return!0}showDropZone(dropData){return!this.getElement().closest(this.selectors.CATEGORY_ID(dropData.id))&&(this.getElement().classList.add(this.classes.DROPTARGET),!0)}hideDropZone(){this.getElement().classList.remove(this.classes.DROPTARGET)}drop(dropData,event){var _precedingSibling;const dropTarget=event.target.closest(this.selectors.CATEGORY_ITEM);if(!dropTarget)return;if(dropTarget.closest(this.selectors.CATEGORY_ID(dropData.id)))return;if(!document.getElementById(this.ids.CATEGORY(dropData.id)))return;const targetParentId=dropTarget.dataset.parent,parentList=dropTarget.closest(this.selectors.CATEGORY_LIST);let precedingSibling;precedingSibling=dropTarget===parentList.firstElementChild?null:dropTarget.previousElementSibling,_categorymanager.categorymanager.moveCategory(dropData.id,targetParentId,null===(_precedingSibling=precedingSibling)||void 0===_precedingSibling?void 0:_precedingSibling.dataset.categoryid)}getWatchers(){return[{watch:"categories[".concat(this.element.dataset.categoryid,"]:updated"),handler:this.updatePosition},{watch:"categories[".concat(this.element.dataset.categoryid,"].templatecontext:created"),handler:this.rerender},{watch:"categories[".concat(this.element.dataset.categoryid,"].templatecontext:updated"),handler:this.rerender},{watch:"categories[".concat(this.element.dataset.categoryid,"].name:updated"),handler:this.updateName},{watch:"categories:created",handler:this.checkChildList}]}async rerender(_ref){let{element:element}=_ref;const{html:html,js:js}=await _templates.default.renderForPromise("qbank_managecategories/category_details",element.templatecontext);return _templates.default.replaceNodeContents(this.getElement(this.selectors.CONTENT_AREA),html,js)}async createChildList(context){const{html:html,js:js}=await _templates.default.renderForPromise("qbank_managecategories/childlist",context),parentContainer=document.querySelector(this.selectors.CONTENT_CONTAINER(context.categoryid));await _templates.default.appendNodeContents(parentContainer,html,js);const childList=document.querySelector(this.selectors.CHILD_LIST(context.categoryid));return childList.closest(this.selectors.CATEGORY_CONTENTS).classList.add(this.classes.NO_BOTTOM_PADDING),childList}async updatePosition(_ref2){let newParent,{element:element}=_ref2;const originParent=document.querySelector(this.selectors.CHILD_LIST(this.getElement().dataset.parent));if(element.contextid===_categorymanager.categorymanager.state.page.contextid){let previousSibling,nextSibling;var _previousSibling;if(parseInt(this.getElement().dataset.parent)!==element.parent?(newParent=document.querySelector(this.selectors.CHILD_LIST(element.parent)),newParent||(newParent=await this.createChildList({categoryid:element.parent})),this.getElement().dataset.parent=element.parent):newParent=this.getElement().parentElement,newParent.firstElementChild&&parseInt(element.sortorder)<=parseInt(newParent.firstElementChild.dataset.sortorder))nextSibling=newParent.firstElementChild;else previousSibling=newParent.querySelector(this.selectors.PREVIOUS_SIBLING(element.sortorder-1)),nextSibling=null===(_previousSibling=previousSibling)||void 0===_previousSibling?void 0:_previousSibling.nextElementSibling;(newParent!==this.getElement().parentElement||nextSibling!==this.getElement())&&(nextSibling?newParent.insertBefore(this.getElement(),nextSibling):newParent.appendChild(this.getElement()))}else this.getElement().remove();originParent!==newParent&&this.reactive.stateManager.processUpdates([{name:"categoryLists",action:"put",fields:{id:originParent.dataset.categoryid,childCount:originParent.querySelectorAll(this.selectors.CATEGORY_ITEM).length}},{name:"categoryLists",action:"put",fields:{id:newParent.dataset.categoryid,childCount:newParent.querySelectorAll(this.selectors.CATEGORY_ITEM).length}}]),this.element.dataset.sortorder=element.sortorder;const isDraggable=this.element.classList.contains(this.classes.DRAGHANDLE);isDraggable&&!element.draghandle?(this.element.classList.remove(this.classes.DRAGHANDLE),this.initDragDrop()):!isDraggable&&element.draghandle&&(this.element.classList.add(this.classes.DRAGHANDLE),this.initDragDrop())}getCategoryDataFromElements(element){const categories=[];return element.children&&element.children.forEach((category=>{let child={categoryId:category.dataset.categoryid,parentId:category.dataset.parent,categoryName:category.dataset.categoryname,children:null};const childList=category.querySelector(this.selectors.CATEGORY_LIST);childList&&(child.children=this.getCategoryDataFromElements(childList)),categories.push(child)})),categories}getCategoryDataFromRecords(record){const categories=[];if(record.children)for(const childId in record.children){const category=record.children[childId];let child={categoryId:parseInt(category.id),parentId:parseInt(category.parent),categoryName:category.name,children:null};category.children&&(child.children=this.getCategoryDataFromRecords(category)),categories.push(child)}return categories}createMoveCategoryList(categoryData,movingCategoryId){const categories=[];if(categoryData){let precedingSibling=null;categoryData.forEach((category=>{var _precedingSibling$cat,_precedingSibling2;if(category.categoryId===movingCategoryId)return;let child={categoryid:category.categoryId,movingcategoryid:movingCategoryId,precedingsiblingid:null!==(_precedingSibling$cat=null===(_precedingSibling2=precedingSibling)||void 0===_precedingSibling2?void 0:_precedingSibling2.categoryId)&&void 0!==_precedingSibling$cat?_precedingSibling$cat:0,parent:category.parentId,categoryname:category.categoryName,categories:null,current:category.categoryId===movingCategoryId};category.children?child.categories=this.createMoveCategoryList(category.children,movingCategoryId):child.categories=[{movingcategoryid:movingCategoryId,precedingsiblingid:0,parent:category.categoryId,categoryname:category.categoryName,categories:null,newchild:!0}],categories.push(child),precedingSibling=category})),precedingSibling&&precedingSibling.categoryId!==movingCategoryId&&categories.push({movingcategoryid:movingCategoryId,precedingsiblingid:precedingSibling.categoryId,parent:precedingSibling.parentId,categoryname:precedingSibling.categoryName,categories:null,lastchild:!0})}return categories}async showMoveModal(e){const item=e.target.closest(this.selectors.MOVE_BUTTON);if(!item)return;if("true"===item.getAttribute("aria-disabled"))return;item.setAttribute("aria-disabled",!0);const contextElement=document.querySelector(this.selectors.CONTEXT),categoryData=this.getCategoryDataFromElements(contextElement),moveContext={contextname:contextElement.dataset.contextname,contextid:contextElement.dataset.contextid,cmid:_categorymanager.categorymanager.state.page.cmid,categories:[],hascategories:!1},movingCategoryId=parseInt(item.dataset.categoryid);moveContext.categories=this.createMoveCategoryList(categoryData,movingCategoryId),moveContext.hascategories=moveContext.categories.length>0;const moveCategory=(0,_str.get_string)("movecategory","qbank_managecategories",item.dataset.categoryname),modal=await _modal.default.create({title:moveCategory,body:_templates.default.render("qbank_managecategories/move_context_list",moveContext),footer:"",show:!0,large:!0}),switcher=new _bank_switcher.default;modal.getBody()[0].addEventListener("click",(async e=>{const categoryItem=e.target.closest(this.selectors.MODAL_CATEGORY_ITEM),moveHeader=e.currentTarget.querySelector(this.selectors.MOVE_BANK_HEADER);if(categoryItem){if(_categorymanager.categorymanager.moveCategory(categoryItem.dataset.movingcategoryid,categoryItem.dataset.parent,categoryItem.dataset.precedingsiblingid),moveHeader.dataset.cmid!==_categorymanager.categorymanager.state.page.cmid){const url=CoreUrl.relativeUrl("/question/bank/managecategories/category.php",{cmid:moveHeader.dataset.cmid}),message=await(0,_str.get_string)("categorymovedto","qbank_managecategories",{url:url,name:moveHeader.textContent});_notification.default.addNotification({message:message,type:"info"})}return void modal.destroy()}if(e.target.closest(this.selectors.SWITCH_QUESTION_BANK)){const pageState=_categorymanager.categorymanager.state.page;try{const contextId=parseInt(contextElement.dataset.contextid);await switcher.show(modal,pageState.courseid,contextId,parseInt(moveHeader.dataset.cmid),pageState.cmid)}catch(ex){_notification.default.exception(ex)}}})),modal.getModal()[0].addEventListener("bankSwitched",(async e=>{try{const params={coursemodule:e.detail.cmid},categoriesResponse=await _fetch.default.performGet("core_question","categories",{params:params}),{context:context,categories:categories}=await categoriesResponse.json();for(const id in categories){const category=categories[id];if(category.parent>0){const parentitem=categories[category.parent];parentitem.hasOwnProperty("children")||(parentitem.children={}),categories[category.parent].children[category.id]=category}}let topCategory;for(const id in categories)0===parseInt(categories[id].parent)&&(topCategory=categories[id]);const categoryData=this.getCategoryDataFromRecords(topCategory),moveContext={contextname:context.prefixedname,contextid:context.prefixedname,cmid:e.detail.cmid,categories:[],hascategories:!1};moveContext.categories=this.createMoveCategoryList(categoryData,movingCategoryId),moveContext.hascategories=moveContext.categories.length>0,modal.setBody(_templates.default.render("qbank_managecategories/move_context_list",moveContext)),await modal.getBodyPromise(),modal.setTitle(moveCategory),modal.setFooter("")}catch(ex){_notification.default.alert((0,_str.get_string)("error","error"),ex)}})),item.setAttribute("aria-disabled",!1)}async checkChildList(_ref3){let{element:element}=_ref3;return element.parent!==this.getElement().dataset.categoryid||this.getElement(this.selectors.CATEGORY_LIST)?null:this.createChildList({categoryid:element.parent,children:[element.templatecontext]})}updateName(_ref4){let{element:element}=_ref4;this.getElement().dataset.categoryname=element.name}}return _exports.default=_default,_exports.default})); +define("qbank_managecategories/category",["exports","core/reactive","qbank_managecategories/categorymanager","core/templates","core/modal","core/str","core_question/bank_switcher","core/fetch","core/notification","core/url","core/local/inplace_editable/events"],(function(_exports,_reactive,_categorymanager,_templates,_modal,_str,_bank_switcher,_fetch,_notification,CoreUrl,_events){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}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_templates=_interopRequireDefault(_templates),_modal=_interopRequireDefault(_modal),_bank_switcher=_interopRequireDefault(_bank_switcher),_fetch=_interopRequireDefault(_fetch),_notification=_interopRequireDefault(_notification),CoreUrl=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}(CoreUrl);class _default extends _reactive.BaseComponent{create(descriptor){this.name=descriptor.element.id,this.selectors={CATEGORY_LIST:".qbank_managecategories-categorylist",CATEGORY_ITEM:".qbank_managecategories-item[data-categoryid]",CATEGORY_CONTENTS:".qbank_managecategories-item > .container",EDIT_BUTTON:'[data-action="addeditcategory"]',EDITABLE_CATEGORY_NAME:'.inplaceeditable[data-itemtype="categoryname"]',MOVE_BUTTON:'[role="menuitem"][data-actiontype="move"]',CONTEXT:".qbank_managecategories-categorylist[data-contextid]",MODAL_CATEGORY_ITEM:".modal_category_item[data-movingcategoryid]",CONTENT_AREA:".qbank_managecategories-details",CATEGORY_ID:id=>"#category-".concat(id),CONTENT_CONTAINER:id=>"#category-".concat(id," .qbank_managecategories-childlistcontainer"),CHILD_LIST:id=>'ul[data-categoryid="'.concat(id,'"]'),PREVIOUS_SIBLING:sortorder=>':scope > [data-sortorder="'.concat(sortorder,'"]'),SWITCH_QUESTION_BANK:'[data-action="switch-question-bank"]',MOVE_BANK_HEADER:".bank-header"},this.classes={NO_BOTTOM_PADDING:"pb-0",DRAGHANDLE:"draghandle",DROPTARGET:"qbank_managecategories-droptarget-before"},this.ids={CATEGORY:id=>"category-".concat(id)}}stateReady(){this.initDragDrop(),this.addEventListener(this.getElement(this.selectors.EDIT_BUTTON),"click",_categorymanager.categorymanager.showEditModal);const moveButton=this.getElement(this.selectors.MOVE_BUTTON);this.addEventListener(moveButton,"click",this.showMoveModal),this.addEventListener(this.getElement(),_events.eventTypes.elementUpdated,(e=>{const editable=e.target.closest(this.selectors.EDITABLE_CATEGORY_NAME);_categorymanager.categorymanager.updateCategoryName(editable.dataset.itemid,editable.dataset.value)}))}destroy(){this.deInitDragDrop()}initDragDrop(){this.deInitDragDrop(),this.element.classList.contains(this.classes.DRAGHANDLE)&&(this.getDraggableData=this._getDraggableData),this.dragdrop=new _reactive.DragDrop(this)}deInitDragDrop(){void 0!==this.dragdrop&&(void 0!==this.getDraggableData&&(this.dragdrop.setDraggable(!1),this.getDraggableData=void 0),this.dragdrop.unregister(),this.dragdrop=void 0)}static init(target,selectors){return new this({element:document.querySelector(target),selectors:selectors,reactive:_categorymanager.categorymanager})}_getDraggableData(){return{id:this.getElement().dataset.categoryid}}validateDropData(){return!0}showDropZone(dropData){return!this.getElement().closest(this.selectors.CATEGORY_ID(dropData.id))&&(this.getElement().classList.add(this.classes.DROPTARGET),!0)}hideDropZone(){this.getElement().classList.remove(this.classes.DROPTARGET)}drop(dropData,event){var _precedingSibling;const dropTarget=event.target.closest(this.selectors.CATEGORY_ITEM);if(!dropTarget)return;if(dropTarget.closest(this.selectors.CATEGORY_ID(dropData.id)))return;if(!document.getElementById(this.ids.CATEGORY(dropData.id)))return;const targetParentId=dropTarget.dataset.parent,parentList=dropTarget.closest(this.selectors.CATEGORY_LIST);let precedingSibling;precedingSibling=dropTarget===parentList.firstElementChild?null:dropTarget.previousElementSibling,_categorymanager.categorymanager.moveCategory(dropData.id,targetParentId,null===(_precedingSibling=precedingSibling)||void 0===_precedingSibling?void 0:_precedingSibling.dataset.categoryid)}getWatchers(){return[{watch:"categories[".concat(this.element.dataset.categoryid,"]:updated"),handler:this.updatePosition},{watch:"categories[".concat(this.element.dataset.categoryid,"].templatecontext:created"),handler:this.rerender},{watch:"categories[".concat(this.element.dataset.categoryid,"].templatecontext:updated"),handler:this.rerender},{watch:"categories[".concat(this.element.dataset.categoryid,"].name:updated"),handler:this.updateName},{watch:"categories:created",handler:this.checkChildList}]}async rerender(_ref){let{element:element}=_ref;const{html:html,js:js}=await _templates.default.renderForPromise("qbank_managecategories/category_details",element.templatecontext);return _templates.default.replaceNodeContents(this.getElement(this.selectors.CONTENT_AREA),html,js)}async createChildList(context){const{html:html,js:js}=await _templates.default.renderForPromise("qbank_managecategories/childlist",context),parentContainer=document.querySelector(this.selectors.CONTENT_CONTAINER(context.categoryid));await _templates.default.appendNodeContents(parentContainer,html,js);const childList=document.querySelector(this.selectors.CHILD_LIST(context.categoryid));return childList.closest(this.selectors.CATEGORY_CONTENTS).classList.add(this.classes.NO_BOTTOM_PADDING),childList}async updatePosition(_ref2){let newParent,{element:element}=_ref2;const originParent=document.querySelector(this.selectors.CHILD_LIST(this.getElement().dataset.parent));if(element.contextid===_categorymanager.categorymanager.state.page.contextid){let previousSibling,nextSibling;var _previousSibling;if(parseInt(this.getElement().dataset.parent)!==element.parent?(newParent=document.querySelector(this.selectors.CHILD_LIST(element.parent)),newParent||(newParent=await this.createChildList({categoryid:element.parent})),this.getElement().dataset.parent=element.parent):newParent=this.getElement().parentElement,newParent.firstElementChild&&parseInt(element.sortorder)<=parseInt(newParent.firstElementChild.dataset.sortorder))nextSibling=newParent.firstElementChild;else previousSibling=newParent.querySelector(this.selectors.PREVIOUS_SIBLING(element.sortorder-1)),nextSibling=null===(_previousSibling=previousSibling)||void 0===_previousSibling?void 0:_previousSibling.nextElementSibling;(newParent!==this.getElement().parentElement||nextSibling!==this.getElement())&&(nextSibling?newParent.insertBefore(this.getElement(),nextSibling):newParent.appendChild(this.getElement()))}else this.getElement().remove();originParent!==newParent&&this.reactive.stateManager.processUpdates([{name:"categoryLists",action:"put",fields:{id:originParent.dataset.categoryid,childCount:originParent.querySelectorAll(this.selectors.CATEGORY_ITEM).length}},{name:"categoryLists",action:"put",fields:{id:newParent.dataset.categoryid,childCount:newParent.querySelectorAll(this.selectors.CATEGORY_ITEM).length}}]),this.element.dataset.sortorder=element.sortorder;const isDraggable=this.element.classList.contains(this.classes.DRAGHANDLE);isDraggable&&!element.draghandle?(this.element.classList.remove(this.classes.DRAGHANDLE),this.initDragDrop()):!isDraggable&&element.draghandle&&(this.element.classList.add(this.classes.DRAGHANDLE),this.initDragDrop())}getCategoryDataFromElements(element){const categories=[];return element.children&&element.children.forEach((category=>{let child={categoryId:category.dataset.categoryid,parentId:category.dataset.parent,categoryName:category.dataset.categoryname,children:null};const childList=category.querySelector(this.selectors.CATEGORY_LIST);childList&&(child.children=this.getCategoryDataFromElements(childList)),categories.push(child)})),categories}getCategoryDataFromRecords(record){const categories=[];if(record.children)for(const childId in record.children){const category=record.children[childId];let child={categoryId:parseInt(category.id),parentId:parseInt(category.parent),categoryName:category.name,children:null};category.children&&(child.children=this.getCategoryDataFromRecords(category)),categories.push(child)}return categories}createMoveCategoryList(categoryData,movingCategoryId){const categories=[];if(categoryData){let precedingSibling=null;categoryData.forEach((category=>{var _precedingSibling$cat,_precedingSibling2;if(parseInt(category.categoryId)===movingCategoryId)return;let child={movingcategoryid:movingCategoryId,precedingsiblingid:null!==(_precedingSibling$cat=null===(_precedingSibling2=precedingSibling)||void 0===_precedingSibling2?void 0:_precedingSibling2.categoryId)&&void 0!==_precedingSibling$cat?_precedingSibling$cat:0,parent:category.parentId,categoryname:category.categoryName,categories:null};category.children?child.categories=this.createMoveCategoryList(category.children,movingCategoryId):child.categories=[{movingcategoryid:movingCategoryId,precedingsiblingid:0,parent:category.categoryId,categoryname:category.categoryName,categories:null,newchild:!0}],categories.push(child),precedingSibling=category})),precedingSibling&&precedingSibling.categoryId!==movingCategoryId&&categories.push({movingcategoryid:movingCategoryId,precedingsiblingid:precedingSibling.categoryId,parent:precedingSibling.parentId,categoryname:precedingSibling.categoryName,categories:null,lastchild:!0})}return categories}async showMoveModal(e){const item=e.target.closest(this.selectors.MOVE_BUTTON);if(!item)return;if("true"===item.getAttribute("aria-disabled"))return;item.setAttribute("aria-disabled",!0);const contextElement=document.querySelector(this.selectors.CONTEXT),categoryData=this.getCategoryDataFromElements(contextElement),moveContext={contextname:contextElement.dataset.contextname,contextid:contextElement.dataset.contextid,cmid:_categorymanager.categorymanager.state.page.cmid,categories:[],hascategories:!1},movingCategoryId=parseInt(item.dataset.categoryid);moveContext.categories=this.createMoveCategoryList(categoryData,movingCategoryId),moveContext.hascategories=moveContext.categories.length>0;const moveCategory=(0,_str.get_string)("movecategory","qbank_managecategories",item.dataset.categoryname),modal=await _modal.default.create({title:moveCategory,body:_templates.default.render("qbank_managecategories/move_context_list",moveContext),footer:"",show:!0,large:!0}),switcher=new _bank_switcher.default;modal.getBody()[0].addEventListener("click",(async e=>{const categoryItem=e.target.closest(this.selectors.MODAL_CATEGORY_ITEM),moveHeader=e.currentTarget.querySelector(this.selectors.MOVE_BANK_HEADER);if(categoryItem){if(_categorymanager.categorymanager.moveCategory(categoryItem.dataset.movingcategoryid,categoryItem.dataset.parent,categoryItem.dataset.precedingsiblingid),moveHeader.dataset.cmid!==_categorymanager.categorymanager.state.page.cmid){const url=CoreUrl.relativeUrl("/question/bank/managecategories/category.php",{cmid:moveHeader.dataset.cmid}),message=await(0,_str.get_string)("categorymovedto","qbank_managecategories",{url:url,name:moveHeader.textContent});_notification.default.addNotification({message:message,type:"info"})}return void modal.destroy()}if(e.target.closest(this.selectors.SWITCH_QUESTION_BANK)){const pageState=_categorymanager.categorymanager.state.page;try{const contextId=parseInt(contextElement.dataset.contextid);await switcher.show(modal,pageState.courseid,contextId,parseInt(moveHeader.dataset.cmid),pageState.cmid)}catch(ex){_notification.default.exception(ex)}}})),modal.getModal()[0].addEventListener("bankSwitched",(async e=>{try{const params={coursemodule:e.detail.cmid},categoriesResponse=await _fetch.default.performGet("core_question","categories",{params:params}),{context:context,categories:categories}=await categoriesResponse.json();for(const id in categories){const category=categories[id];if(category.parent>0){const parentitem=categories[category.parent];parentitem.hasOwnProperty("children")||(parentitem.children={}),categories[category.parent].children[category.id]=category}}let topCategory;for(const id in categories)0===parseInt(categories[id].parent)&&(topCategory=categories[id]);const categoryData=this.getCategoryDataFromRecords(topCategory),moveContext={contextname:context.prefixedname,contextid:context.prefixedname,cmid:e.detail.cmid,categories:[],hascategories:!1};moveContext.categories=this.createMoveCategoryList(categoryData,movingCategoryId),moveContext.hascategories=moveContext.categories.length>0,modal.setBody(_templates.default.render("qbank_managecategories/move_context_list",moveContext)),await modal.getBodyPromise(),modal.setTitle(moveCategory),modal.setFooter("")}catch(ex){_notification.default.alert((0,_str.get_string)("error","error"),ex)}})),item.setAttribute("aria-disabled",!1)}async checkChildList(_ref3){let{element:element}=_ref3;return element.parent!==this.getElement().dataset.categoryid||this.getElement(this.selectors.CATEGORY_LIST)?null:this.createChildList({categoryid:element.parent,children:[element.templatecontext]})}updateName(_ref4){let{element:element}=_ref4;this.getElement().dataset.categoryname=element.name}}return _exports.default=_default,_exports.default})); //# sourceMappingURL=category.min.js.map \ No newline at end of file diff --git a/public/question/bank/managecategories/amd/build/category.min.js.map b/public/question/bank/managecategories/amd/build/category.min.js.map index af630c260ca..d272472f08c 100644 --- a/public/question/bank/managecategories/amd/build/category.min.js.map +++ b/public/question/bank/managecategories/amd/build/category.min.js.map @@ -1 +1 @@ -{"version":3,"file":"category.min.js","sources":["../src/category.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 * The category component.\n *\n * @module qbank_managecategories/category\n * @class qbank_managecategories/category\n */\n\nimport {BaseComponent, DragDrop} from 'core/reactive';\nimport {categorymanager} from 'qbank_managecategories/categorymanager';\nimport Templates from 'core/templates';\nimport Modal from \"core/modal\";\nimport {get_string as getString} from \"core/str\";\nimport BankSwitcher from 'core_question/bank_switcher';\nimport Fetch from 'core/fetch';\nimport Notification from 'core/notification';\nimport * as CoreUrl from 'core/url';\nimport {eventTypes as inplaceEditableEventTypes} from 'core/local/inplace_editable/events';\n\nexport default class extends BaseComponent {\n\n create(descriptor) {\n this.name = descriptor.element.id;\n this.selectors = {\n CATEGORY_LIST: '.qbank_managecategories-categorylist',\n CATEGORY_ITEM: '.qbank_managecategories-item[data-categoryid]',\n CATEGORY_CONTENTS: '.qbank_managecategories-item > .container',\n EDIT_BUTTON: '[data-action=\"addeditcategory\"]',\n EDITABLE_CATEGORY_NAME: '.inplaceeditable[data-itemtype=\"categoryname\"]',\n MOVE_BUTTON: '[role=\"menuitem\"][data-actiontype=\"move\"]',\n CONTEXT: '.qbank_managecategories-categorylist[data-contextid]',\n MODAL_CATEGORY_ITEM: '.modal_category_item[data-movingcategoryid]',\n CONTENT_AREA: '.qbank_managecategories-details',\n CATEGORY_ID: id => `#category-${id}`,\n CONTENT_CONTAINER: id => `#category-${id} .qbank_managecategories-childlistcontainer`,\n CHILD_LIST: id => `ul[data-categoryid=\"${id}\"]`,\n PREVIOUS_SIBLING: sortorder => `:scope > [data-sortorder=\"${sortorder}\"]`,\n SWITCH_QUESTION_BANK: '[data-action=\"switch-question-bank\"]',\n MOVE_BANK_HEADER: '.bank-header',\n };\n this.classes = {\n NO_BOTTOM_PADDING: 'pb-0',\n DRAGHANDLE: 'draghandle',\n DROPTARGET: 'qbank_managecategories-droptarget-before',\n };\n this.ids = {\n CATEGORY: id => `category-${id}`,\n };\n }\n\n stateReady() {\n this.initDragDrop();\n this.addEventListener(this.getElement(this.selectors.EDIT_BUTTON), 'click', categorymanager.showEditModal);\n const moveButton = this.getElement(this.selectors.MOVE_BUTTON);\n this.addEventListener(moveButton, 'click', this.showMoveModal);\n this.addEventListener(this.getElement(), inplaceEditableEventTypes.elementUpdated, (e) => {\n const editable = e.target.closest(this.selectors.EDITABLE_CATEGORY_NAME);\n categorymanager.updateCategoryName(editable.dataset.itemid, editable.dataset.value);\n });\n }\n\n destroy() {\n // The draggable element must be unregistered.\n this.deInitDragDrop();\n }\n\n /**\n * Remove any existing DragDrop component, and create a new one.\n */\n initDragDrop() {\n this.deInitDragDrop();\n // If the element is currently draggable, register the getDraggableData method.\n if (this.element.classList.contains(this.classes.DRAGHANDLE)) {\n this.getDraggableData = this._getDraggableData;\n }\n this.dragdrop = new DragDrop(this);\n }\n\n /**\n * If the DragDrop component is currently registered, unregister it.\n */\n deInitDragDrop() {\n if (this.dragdrop !== undefined) {\n if (this.getDraggableData !== undefined) {\n this.dragdrop.setDraggable(false);\n this.getDraggableData = undefined;\n }\n this.dragdrop.unregister();\n this.dragdrop = undefined;\n }\n }\n\n /**\n * Static method to create a component instance.\n *\n * @param {string} target the DOM main element or its ID\n * @param {object} selectors optional css selector overrides\n * @return {Component}\n */\n static init(target, selectors) {\n return new this({\n element: document.querySelector(target),\n selectors,\n reactive: categorymanager,\n });\n }\n\n /**\n * Return the category ID from the component's element.\n *\n * This method is referenced as getDraggableData when the component can be dragged.\n *\n * @return {{id: string}}\n * @private\n */\n _getDraggableData() {\n return {\n id: this.getElement().dataset.categoryid\n };\n }\n\n validateDropData() {\n return true;\n }\n\n /**\n * Highlight the top border of the category item.\n *\n * @param {Object} dropData\n */\n showDropZone(dropData) {\n if (this.getElement().closest(this.selectors.CATEGORY_ID(dropData.id))) {\n // Can't drop onto itself or its own child.\n return false;\n }\n this.getElement().classList.add(this.classes.DROPTARGET);\n return true;\n }\n\n /**\n * Remove highlighting.\n */\n hideDropZone() {\n this.getElement().classList.remove(this.classes.DROPTARGET);\n }\n\n /**\n * Find the new position of the dropped category, and trigger the move.\n *\n * @param {Object} dropData The category being moved.\n * @param {Event} event The drop event.\n */\n drop(dropData, event) {\n const dropTarget = event.target.closest(this.selectors.CATEGORY_ITEM);\n\n if (!dropTarget) {\n return;\n }\n\n if (dropTarget.closest(this.selectors.CATEGORY_ID(dropData.id))) {\n // Can't drop onto your own child.\n return;\n }\n\n const source = document.getElementById(this.ids.CATEGORY(dropData.id));\n\n if (!source) {\n return;\n }\n\n const targetParentId = dropTarget.dataset.parent;\n const parentList = dropTarget.closest(this.selectors.CATEGORY_LIST);\n let precedingSibling;\n\n if (dropTarget === parentList.firstElementChild) {\n // Dropped at the top of the list.\n precedingSibling = null;\n } else {\n precedingSibling = dropTarget.previousElementSibling;\n }\n\n // Insert the category after the target category\n categorymanager.moveCategory(dropData.id, targetParentId, precedingSibling?.dataset.categoryid);\n }\n\n getWatchers() {\n return [\n // After any update to this category, move it to the new position.\n {watch: `categories[${this.element.dataset.categoryid}]:updated`, handler: this.updatePosition},\n // When the template context is added or updated, re-render the content.\n {watch: `categories[${this.element.dataset.categoryid}].templatecontext:created`, handler: this.rerender},\n {watch: `categories[${this.element.dataset.categoryid}].templatecontext:updated`, handler: this.rerender},\n // When the name is updated, update it in the element data set.\n {watch: `categories[${this.element.dataset.categoryid}].name:updated`, handler: this.updateName},\n // When a new category is created, check whether we need to add a child list to this category.\n {watch: `categories:created`, handler: this.checkChildList},\n ];\n }\n\n /**\n * Re-render the category content.\n *\n * @param {Object} args\n * @param {Element} args.element\n * @return {Promise}\n */\n async rerender({element}) {\n const {html, js} = await Templates.renderForPromise(\n 'qbank_managecategories/category_details',\n element.templatecontext\n );\n return Templates.replaceNodeContents(this.getElement(this.selectors.CONTENT_AREA), html, js);\n }\n\n /**\n * Render and append a new child list.\n *\n * @param {Object} context Template context, must include at least categoryid.\n * @return {Promise}\n */\n async createChildList(context) {\n const {html, js} = await Templates.renderForPromise(\n 'qbank_managecategories/childlist',\n context,\n );\n const parentContainer = document.querySelector(this.selectors.CONTENT_CONTAINER(context.categoryid));\n await Templates.appendNodeContents(parentContainer, html, js);\n const childList = document.querySelector(this.selectors.CHILD_LIST(context.categoryid));\n childList.closest(this.selectors.CATEGORY_CONTENTS).classList.add(this.classes.NO_BOTTOM_PADDING);\n return childList;\n }\n\n /**\n * Move a category to its new position.\n *\n * A category may change its parent, sortorder and draghandle independently or at the same time. This method will resolve those\n * changes and move the element to the new position. If the parent doesn't already have a child list, one will be created.\n *\n * If the parent has changed, this will also update the state with the new child count of the old and new parents.\n *\n * @param {Object} args\n * @param {Object} args.element\n * @return {Promise}\n */\n async updatePosition({element}) {\n // Move to a new parent category.\n let newParent;\n const originParent = document.querySelector(this.selectors.CHILD_LIST(this.getElement().dataset.parent));\n if (element.contextid === categorymanager.state.page.contextid) {\n if (parseInt(this.getElement().dataset.parent) !== element.parent) {\n newParent = document.querySelector(this.selectors.CHILD_LIST(element.parent));\n if (!newParent) {\n // The target category doesn't have a child list yet. We'd better create one.\n newParent = await this.createChildList({categoryid: element.parent});\n }\n this.getElement().dataset.parent = element.parent;\n } else {\n newParent = this.getElement().parentElement;\n }\n\n // Move to a new position within the parent.\n let previousSibling;\n let nextSibling;\n if (\n newParent.firstElementChild &&\n parseInt(element.sortorder) <= parseInt(newParent.firstElementChild.dataset.sortorder)\n ) {\n // Move to the top of the list.\n nextSibling = newParent.firstElementChild;\n } else {\n // Move later in the list.\n previousSibling = newParent.querySelector(this.selectors.PREVIOUS_SIBLING(element.sortorder - 1));\n nextSibling = previousSibling?.nextElementSibling;\n }\n\n // Check if this has actually moved, or if it's just having its sortorder updated due to another element moving.\n const moved = (newParent !== this.getElement().parentElement || nextSibling !== this.getElement());\n\n if (moved) {\n if (nextSibling) {\n // Move to the specified position in the list.\n newParent.insertBefore(this.getElement(), nextSibling);\n } else {\n // Move to the end of the list (may also be the top of the list is empty).\n newParent.appendChild(this.getElement());\n }\n }\n } else {\n // The category was moved to a different context, it should no longer appear on this page.\n this.getElement().remove();\n }\n\n if (originParent !== newParent) {\n // Update child count of old and new parent.\n this.reactive.stateManager.processUpdates([\n {\n name: 'categoryLists',\n action: 'put',\n fields: {\n id: originParent.dataset.categoryid,\n childCount: originParent.querySelectorAll(this.selectors.CATEGORY_ITEM).length\n }\n },\n {\n name: 'categoryLists',\n action: 'put',\n fields: {\n id: newParent.dataset.categoryid,\n childCount: newParent.querySelectorAll(this.selectors.CATEGORY_ITEM).length\n }\n }\n ]);\n }\n\n this.element.dataset.sortorder = element.sortorder;\n\n // Enable/disable dragging.\n const isDraggable = this.element.classList.contains(this.classes.DRAGHANDLE);\n if (isDraggable && !element.draghandle) {\n this.element.classList.remove(this.classes.DRAGHANDLE);\n this.initDragDrop();\n } else if (!isDraggable && element.draghandle) {\n this.element.classList.add(this.classes.DRAGHANDLE);\n this.initDragDrop();\n }\n }\n\n /**\n * Get the abstract category data from the elements on the page.\n *\n * @param {Element} element The category element from the page.\n * @return {Array} List of categories containing categoryId, parentId, categoryName, and a nested array of children.\n */\n getCategoryDataFromElements(element) {\n const categories = [];\n if (element.children) {\n element.children.forEach(category => {\n // Add this category to the list.\n let child = {\n categoryId: category.dataset.categoryid,\n parentId: category.dataset.parent,\n categoryName: category.dataset.categoryname,\n children: null,\n };\n const childList = category.querySelector(this.selectors.CATEGORY_LIST);\n if (childList) {\n // If the child has its own children, recursively make a list of those.\n child.children = this.getCategoryDataFromElements(childList);\n }\n categories.push(child);\n });\n }\n return categories;\n }\n\n /**\n * Get the abstract category data from the records retrieved from the web service.\n *\n * @param {Object} record The category record.\n * @return {Array} List of categories containing categoryId, parentId, categoryName, and a nested array of children.\n */\n getCategoryDataFromRecords(record) {\n const categories = [];\n if (record.children) {\n for (const childId in record.children) {\n const category = record.children[childId];\n // Add this category to the list.\n let child = {\n categoryId: parseInt(category.id),\n parentId: parseInt(category.parent),\n categoryName: category.name,\n children: null,\n };\n if (category.children) {\n // If the child has its own children, recursively make a list of those.\n child.children = this.getCategoryDataFromRecords(category);\n }\n categories.push(child);\n }\n }\n return categories;\n }\n\n /**\n * Recursively create a list of all valid destinations for a current category within a parent category.\n *\n * @param {Object} categoryData\n * @param {Number} movingCategoryId\n * @return {Array}\n */\n createMoveCategoryList(categoryData, movingCategoryId) {\n const categories = [];\n if (categoryData) {\n let precedingSibling = null;\n categoryData.forEach(category => {\n // Don't create a target for the category that's moving.\n if (category.categoryId === movingCategoryId) {\n return;\n }\n // Create a target to move before this child.\n let child = {\n categoryid: category.categoryId,\n movingcategoryid: movingCategoryId,\n precedingsiblingid: precedingSibling?.categoryId ?? 0,\n parent: category.parentId,\n categoryname: category.categoryName,\n categories: null,\n current: category.categoryId === movingCategoryId,\n };\n if (category.children) {\n // If the child has its own children, recursively make a list of those.\n child.categories = this.createMoveCategoryList(category.children, movingCategoryId);\n } else {\n // Otherwise, create a target to move as a new child of this one.\n child.categories = [\n {\n movingcategoryid: movingCategoryId,\n precedingsiblingid: 0,\n parent: category.categoryId,\n categoryname: category.categoryName,\n categories: null,\n newchild: true,\n }\n ];\n }\n categories.push(child);\n precedingSibling = category;\n });\n if (precedingSibling) {\n if (precedingSibling.categoryId !== movingCategoryId) {\n // If this is the last child of its parent, also create a target to move the category after this one.\n categories.push({\n movingcategoryid: movingCategoryId,\n precedingsiblingid: precedingSibling.categoryId,\n parent: precedingSibling.parentId,\n categoryname: precedingSibling.categoryName,\n categories: null,\n lastchild: true,\n });\n }\n }\n }\n return categories;\n }\n\n /**\n * Displays a modal containing links to move the category to a new location.\n *\n * @param {Event} e Button click event.\n */\n async showMoveModal(e) {\n // Return if it is not menu item.\n const item = e.target.closest(this.selectors.MOVE_BUTTON);\n if (!item) {\n return;\n }\n // Return if it is disabled.\n if (item.getAttribute('aria-disabled') === 'true') {\n return;\n }\n\n // Prevent addition click on the item.\n item.setAttribute('aria-disabled', true);\n\n // Build the list of move links.\n const contextElement = document.querySelector(this.selectors.CONTEXT);\n const categoryData = this.getCategoryDataFromElements(contextElement);\n\n const moveContext = {\n contextname: contextElement.dataset.contextname,\n contextid: contextElement.dataset.contextid,\n cmid: categorymanager.state.page.cmid,\n categories: [],\n hascategories: false,\n };\n const movingCategoryId = parseInt(item.dataset.categoryid);\n moveContext.categories = this.createMoveCategoryList(categoryData, movingCategoryId);\n moveContext.hascategories = moveContext.categories.length > 0;\n\n const moveCategory = getString('movecategory', 'qbank_managecategories', item.dataset.categoryname);\n const modal = await Modal.create({\n title: moveCategory,\n body: Templates.render('qbank_managecategories/move_context_list', moveContext),\n footer: '',\n show: true,\n large: true,\n });\n const switcher = new BankSwitcher();\n // Show modal and add click event for list items and bank switcher.\n modal.getBody()[0].addEventListener('click', async(e) => {\n const categoryItem = e.target.closest(this.selectors.MODAL_CATEGORY_ITEM);\n const moveHeader = e.currentTarget.querySelector(this.selectors.MOVE_BANK_HEADER);\n if (categoryItem) {\n categorymanager.moveCategory(\n categoryItem.dataset.movingcategoryid,\n categoryItem.dataset.parent,\n categoryItem.dataset.precedingsiblingid,\n );\n if (moveHeader.dataset.cmid !== categorymanager.state.page.cmid) {\n const url = CoreUrl.relativeUrl(\n '/question/bank/managecategories/category.php',\n {cmid: moveHeader.dataset.cmid}\n );\n const message = await getString(\n 'categorymovedto',\n 'qbank_managecategories',\n {url, name: moveHeader.textContent},\n );\n Notification.addNotification({message: message, type: 'info'});\n }\n modal.destroy();\n return;\n }\n const switchButton = e.target.closest(this.selectors.SWITCH_QUESTION_BANK);\n if (switchButton) {\n const pageState = categorymanager.state.page;\n try {\n const contextId = parseInt(contextElement.dataset.contextid);\n await switcher.show(modal, pageState.courseid, contextId, parseInt(moveHeader.dataset.cmid), pageState.cmid);\n } catch (ex) {\n Notification.exception(ex);\n }\n }\n });\n modal.getModal()[0].addEventListener('bankSwitched', async(e) => {\n try {\n const params = {coursemodule: e.detail.cmid};\n const categoriesResponse = await Fetch.performGet(\n 'core_question',\n 'categories',\n {params},\n );\n const {context, categories} = await categoriesResponse.json();\n // Convert the list of categories into a nested tree.\n for (const id in categories) {\n const category = categories[id];\n if (category.parent > 0) {\n const parentitem = categories[category.parent];\n if (!parentitem.hasOwnProperty('children')) {\n parentitem.children = {};\n }\n categories[category.parent].children[category.id] = category;\n }\n }\n // Get the top category with all the others nested below.\n let topCategory;\n for (const id in categories) {\n if (parseInt(categories[id].parent) === 0) {\n topCategory = categories[id];\n }\n }\n const categoryData = this.getCategoryDataFromRecords(topCategory);\n const moveContext = {\n contextname: context.prefixedname,\n contextid: context.prefixedname,\n cmid: e.detail.cmid,\n categories: [],\n hascategories: false,\n };\n moveContext.categories = this.createMoveCategoryList(categoryData, movingCategoryId);\n moveContext.hascategories = moveContext.categories.length > 0;\n modal.setBody(\n Templates.render('qbank_managecategories/move_context_list', moveContext),\n );\n await modal.getBodyPromise();\n modal.setTitle(moveCategory);\n modal.setFooter('');\n } catch (ex) {\n Notification.alert(getString('error', 'error'), ex);\n }\n });\n item.setAttribute('aria-disabled', false);\n }\n\n /**\n * Check and add a child list if needed.\n *\n * Check whether the category that has just been added has this category as its parent. If it does,\n * check that this category has a child list, and if not, add one.\n *\n * @param {Object} args\n * @param {Element} args.element The new category.\n * @return {Promise}\n */\n async checkChildList({element}) {\n if (element.parent !== this.getElement().dataset.categoryid) {\n return null; // Not for me.\n }\n let childList = this.getElement(this.selectors.CATEGORY_LIST);\n if (childList) {\n return null; // List already exists, it will handle adding the new category.\n }\n // Render and add a new child list containing the new category.\n return this.createChildList({\n categoryid: element.parent,\n children: [\n element.templatecontext,\n ]\n });\n }\n\n /**\n * Update the name in the category item element's data, for building move targets.\n *\n * @param {Object} args\n * @param {Object} args.element\n */\n updateName({element}) {\n this.getElement().dataset.categoryname = element.name;\n }\n}\n"],"names":["BaseComponent","create","descriptor","name","element","id","selectors","CATEGORY_LIST","CATEGORY_ITEM","CATEGORY_CONTENTS","EDIT_BUTTON","EDITABLE_CATEGORY_NAME","MOVE_BUTTON","CONTEXT","MODAL_CATEGORY_ITEM","CONTENT_AREA","CATEGORY_ID","CONTENT_CONTAINER","CHILD_LIST","PREVIOUS_SIBLING","sortorder","SWITCH_QUESTION_BANK","MOVE_BANK_HEADER","classes","NO_BOTTOM_PADDING","DRAGHANDLE","DROPTARGET","ids","CATEGORY","stateReady","initDragDrop","addEventListener","this","getElement","categorymanager","showEditModal","moveButton","showMoveModal","inplaceEditableEventTypes","elementUpdated","e","editable","target","closest","updateCategoryName","dataset","itemid","value","destroy","deInitDragDrop","classList","contains","getDraggableData","_getDraggableData","dragdrop","DragDrop","undefined","setDraggable","unregister","document","querySelector","reactive","categoryid","validateDropData","showDropZone","dropData","add","hideDropZone","remove","drop","event","dropTarget","getElementById","targetParentId","parent","parentList","precedingSibling","firstElementChild","previousElementSibling","moveCategory","_precedingSibling","getWatchers","watch","handler","updatePosition","rerender","updateName","checkChildList","html","js","Templates","renderForPromise","templatecontext","replaceNodeContents","context","parentContainer","appendNodeContents","childList","newParent","originParent","contextid","state","page","previousSibling","nextSibling","parseInt","createChildList","parentElement","_previousSibling","nextElementSibling","insertBefore","appendChild","stateManager","processUpdates","action","fields","childCount","querySelectorAll","length","isDraggable","draghandle","getCategoryDataFromElements","categories","children","forEach","category","child","categoryId","parentId","categoryName","categoryname","push","getCategoryDataFromRecords","record","childId","createMoveCategoryList","categoryData","movingCategoryId","movingcategoryid","precedingsiblingid","_precedingSibling2","current","newchild","lastchild","item","getAttribute","setAttribute","contextElement","moveContext","contextname","cmid","hascategories","modal","Modal","title","body","render","footer","show","large","switcher","BankSwitcher","getBody","async","categoryItem","moveHeader","currentTarget","url","CoreUrl","relativeUrl","message","textContent","addNotification","type","pageState","contextId","courseid","ex","exception","getModal","params","coursemodule","detail","categoriesResponse","Fetch","performGet","json","parentitem","hasOwnProperty","topCategory","prefixedname","setBody","getBodyPromise","setTitle","setFooter","alert"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"category.min.js","sources":["../src/category.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 * The category component.\n *\n * @module qbank_managecategories/category\n * @class qbank_managecategories/category\n */\n\nimport {BaseComponent, DragDrop} from 'core/reactive';\nimport {categorymanager} from 'qbank_managecategories/categorymanager';\nimport Templates from 'core/templates';\nimport Modal from \"core/modal\";\nimport {get_string as getString} from \"core/str\";\nimport BankSwitcher from 'core_question/bank_switcher';\nimport Fetch from 'core/fetch';\nimport Notification from 'core/notification';\nimport * as CoreUrl from 'core/url';\nimport {eventTypes as inplaceEditableEventTypes} from 'core/local/inplace_editable/events';\n\nexport default class extends BaseComponent {\n\n create(descriptor) {\n this.name = descriptor.element.id;\n this.selectors = {\n CATEGORY_LIST: '.qbank_managecategories-categorylist',\n CATEGORY_ITEM: '.qbank_managecategories-item[data-categoryid]',\n CATEGORY_CONTENTS: '.qbank_managecategories-item > .container',\n EDIT_BUTTON: '[data-action=\"addeditcategory\"]',\n EDITABLE_CATEGORY_NAME: '.inplaceeditable[data-itemtype=\"categoryname\"]',\n MOVE_BUTTON: '[role=\"menuitem\"][data-actiontype=\"move\"]',\n CONTEXT: '.qbank_managecategories-categorylist[data-contextid]',\n MODAL_CATEGORY_ITEM: '.modal_category_item[data-movingcategoryid]',\n CONTENT_AREA: '.qbank_managecategories-details',\n CATEGORY_ID: id => `#category-${id}`,\n CONTENT_CONTAINER: id => `#category-${id} .qbank_managecategories-childlistcontainer`,\n CHILD_LIST: id => `ul[data-categoryid=\"${id}\"]`,\n PREVIOUS_SIBLING: sortorder => `:scope > [data-sortorder=\"${sortorder}\"]`,\n SWITCH_QUESTION_BANK: '[data-action=\"switch-question-bank\"]',\n MOVE_BANK_HEADER: '.bank-header',\n };\n this.classes = {\n NO_BOTTOM_PADDING: 'pb-0',\n DRAGHANDLE: 'draghandle',\n DROPTARGET: 'qbank_managecategories-droptarget-before',\n };\n this.ids = {\n CATEGORY: id => `category-${id}`,\n };\n }\n\n stateReady() {\n this.initDragDrop();\n this.addEventListener(this.getElement(this.selectors.EDIT_BUTTON), 'click', categorymanager.showEditModal);\n const moveButton = this.getElement(this.selectors.MOVE_BUTTON);\n this.addEventListener(moveButton, 'click', this.showMoveModal);\n this.addEventListener(this.getElement(), inplaceEditableEventTypes.elementUpdated, (e) => {\n const editable = e.target.closest(this.selectors.EDITABLE_CATEGORY_NAME);\n categorymanager.updateCategoryName(editable.dataset.itemid, editable.dataset.value);\n });\n }\n\n destroy() {\n // The draggable element must be unregistered.\n this.deInitDragDrop();\n }\n\n /**\n * Remove any existing DragDrop component, and create a new one.\n */\n initDragDrop() {\n this.deInitDragDrop();\n // If the element is currently draggable, register the getDraggableData method.\n if (this.element.classList.contains(this.classes.DRAGHANDLE)) {\n this.getDraggableData = this._getDraggableData;\n }\n this.dragdrop = new DragDrop(this);\n }\n\n /**\n * If the DragDrop component is currently registered, unregister it.\n */\n deInitDragDrop() {\n if (this.dragdrop !== undefined) {\n if (this.getDraggableData !== undefined) {\n this.dragdrop.setDraggable(false);\n this.getDraggableData = undefined;\n }\n this.dragdrop.unregister();\n this.dragdrop = undefined;\n }\n }\n\n /**\n * Static method to create a component instance.\n *\n * @param {string} target the DOM main element or its ID\n * @param {object} selectors optional css selector overrides\n * @return {Component}\n */\n static init(target, selectors) {\n return new this({\n element: document.querySelector(target),\n selectors,\n reactive: categorymanager,\n });\n }\n\n /**\n * Return the category ID from the component's element.\n *\n * This method is referenced as getDraggableData when the component can be dragged.\n *\n * @return {{id: string}}\n * @private\n */\n _getDraggableData() {\n return {\n id: this.getElement().dataset.categoryid\n };\n }\n\n validateDropData() {\n return true;\n }\n\n /**\n * Highlight the top border of the category item.\n *\n * @param {Object} dropData\n */\n showDropZone(dropData) {\n if (this.getElement().closest(this.selectors.CATEGORY_ID(dropData.id))) {\n // Can't drop onto itself or its own child.\n return false;\n }\n this.getElement().classList.add(this.classes.DROPTARGET);\n return true;\n }\n\n /**\n * Remove highlighting.\n */\n hideDropZone() {\n this.getElement().classList.remove(this.classes.DROPTARGET);\n }\n\n /**\n * Find the new position of the dropped category, and trigger the move.\n *\n * @param {Object} dropData The category being moved.\n * @param {Event} event The drop event.\n */\n drop(dropData, event) {\n const dropTarget = event.target.closest(this.selectors.CATEGORY_ITEM);\n\n if (!dropTarget) {\n return;\n }\n\n if (dropTarget.closest(this.selectors.CATEGORY_ID(dropData.id))) {\n // Can't drop onto your own child.\n return;\n }\n\n const source = document.getElementById(this.ids.CATEGORY(dropData.id));\n\n if (!source) {\n return;\n }\n\n const targetParentId = dropTarget.dataset.parent;\n const parentList = dropTarget.closest(this.selectors.CATEGORY_LIST);\n let precedingSibling;\n\n if (dropTarget === parentList.firstElementChild) {\n // Dropped at the top of the list.\n precedingSibling = null;\n } else {\n precedingSibling = dropTarget.previousElementSibling;\n }\n\n // Insert the category after the target category\n categorymanager.moveCategory(dropData.id, targetParentId, precedingSibling?.dataset.categoryid);\n }\n\n getWatchers() {\n return [\n // After any update to this category, move it to the new position.\n {watch: `categories[${this.element.dataset.categoryid}]:updated`, handler: this.updatePosition},\n // When the template context is added or updated, re-render the content.\n {watch: `categories[${this.element.dataset.categoryid}].templatecontext:created`, handler: this.rerender},\n {watch: `categories[${this.element.dataset.categoryid}].templatecontext:updated`, handler: this.rerender},\n // When the name is updated, update it in the element data set.\n {watch: `categories[${this.element.dataset.categoryid}].name:updated`, handler: this.updateName},\n // When a new category is created, check whether we need to add a child list to this category.\n {watch: `categories:created`, handler: this.checkChildList},\n ];\n }\n\n /**\n * Re-render the category content.\n *\n * @param {Object} args\n * @param {Element} args.element\n * @return {Promise}\n */\n async rerender({element}) {\n const {html, js} = await Templates.renderForPromise(\n 'qbank_managecategories/category_details',\n element.templatecontext\n );\n return Templates.replaceNodeContents(this.getElement(this.selectors.CONTENT_AREA), html, js);\n }\n\n /**\n * Render and append a new child list.\n *\n * @param {Object} context Template context, must include at least categoryid.\n * @return {Promise}\n */\n async createChildList(context) {\n const {html, js} = await Templates.renderForPromise(\n 'qbank_managecategories/childlist',\n context,\n );\n const parentContainer = document.querySelector(this.selectors.CONTENT_CONTAINER(context.categoryid));\n await Templates.appendNodeContents(parentContainer, html, js);\n const childList = document.querySelector(this.selectors.CHILD_LIST(context.categoryid));\n childList.closest(this.selectors.CATEGORY_CONTENTS).classList.add(this.classes.NO_BOTTOM_PADDING);\n return childList;\n }\n\n /**\n * Move a category to its new position.\n *\n * A category may change its parent, sortorder and draghandle independently or at the same time. This method will resolve those\n * changes and move the element to the new position. If the parent doesn't already have a child list, one will be created.\n *\n * If the parent has changed, this will also update the state with the new child count of the old and new parents.\n *\n * @param {Object} args\n * @param {Object} args.element\n * @return {Promise}\n */\n async updatePosition({element}) {\n // Move to a new parent category.\n let newParent;\n const originParent = document.querySelector(this.selectors.CHILD_LIST(this.getElement().dataset.parent));\n if (element.contextid === categorymanager.state.page.contextid) {\n if (parseInt(this.getElement().dataset.parent) !== element.parent) {\n newParent = document.querySelector(this.selectors.CHILD_LIST(element.parent));\n if (!newParent) {\n // The target category doesn't have a child list yet. We'd better create one.\n newParent = await this.createChildList({categoryid: element.parent});\n }\n this.getElement().dataset.parent = element.parent;\n } else {\n newParent = this.getElement().parentElement;\n }\n\n // Move to a new position within the parent.\n let previousSibling;\n let nextSibling;\n if (\n newParent.firstElementChild &&\n parseInt(element.sortorder) <= parseInt(newParent.firstElementChild.dataset.sortorder)\n ) {\n // Move to the top of the list.\n nextSibling = newParent.firstElementChild;\n } else {\n // Move later in the list.\n previousSibling = newParent.querySelector(this.selectors.PREVIOUS_SIBLING(element.sortorder - 1));\n nextSibling = previousSibling?.nextElementSibling;\n }\n\n // Check if this has actually moved, or if it's just having its sortorder updated due to another element moving.\n const moved = (newParent !== this.getElement().parentElement || nextSibling !== this.getElement());\n\n if (moved) {\n if (nextSibling) {\n // Move to the specified position in the list.\n newParent.insertBefore(this.getElement(), nextSibling);\n } else {\n // Move to the end of the list (may also be the top of the list is empty).\n newParent.appendChild(this.getElement());\n }\n }\n } else {\n // The category was moved to a different context, it should no longer appear on this page.\n this.getElement().remove();\n }\n\n if (originParent !== newParent) {\n // Update child count of old and new parent.\n this.reactive.stateManager.processUpdates([\n {\n name: 'categoryLists',\n action: 'put',\n fields: {\n id: originParent.dataset.categoryid,\n childCount: originParent.querySelectorAll(this.selectors.CATEGORY_ITEM).length\n }\n },\n {\n name: 'categoryLists',\n action: 'put',\n fields: {\n id: newParent.dataset.categoryid,\n childCount: newParent.querySelectorAll(this.selectors.CATEGORY_ITEM).length\n }\n }\n ]);\n }\n\n this.element.dataset.sortorder = element.sortorder;\n\n // Enable/disable dragging.\n const isDraggable = this.element.classList.contains(this.classes.DRAGHANDLE);\n if (isDraggable && !element.draghandle) {\n this.element.classList.remove(this.classes.DRAGHANDLE);\n this.initDragDrop();\n } else if (!isDraggable && element.draghandle) {\n this.element.classList.add(this.classes.DRAGHANDLE);\n this.initDragDrop();\n }\n }\n\n /**\n * Create a list of category data from the elements on the page.\n *\n * This will find the category list item elements on the page and extract the category ID, parent ID, and name from the dataset\n * of each element, with any child categories nested underneath. This list can then be passed to createMoveCategoryList to\n * create a tree of move targets.\n *\n * @param {Element} element The category element from the page.\n * @return {Array} List of categories containing categoryId, parentId, categoryName, and a nested array of children.\n */\n getCategoryDataFromElements(element) {\n const categories = [];\n if (element.children) {\n element.children.forEach(category => {\n // Add this category to the list.\n let child = {\n categoryId: category.dataset.categoryid,\n parentId: category.dataset.parent,\n categoryName: category.dataset.categoryname,\n children: null,\n };\n const childList = category.querySelector(this.selectors.CATEGORY_LIST);\n if (childList) {\n // If the child has its own children, recursively make a list of those.\n child.children = this.getCategoryDataFromElements(childList);\n }\n categories.push(child);\n });\n }\n return categories;\n }\n\n /**\n * Get the category data from the records retrieved from the web service.\n *\n * This will process the list of category records and extract the category ID, parent ID, and name from each,\n * with any child categories nested underneath. This list can then be passed to createMoveCategoryList to\n * create a tree of move targets.\n *\n * @param {Object} record The category record.\n * @return {Array} List of categories containing categoryId, parentId, categoryName, and a nested array of children.\n */\n getCategoryDataFromRecords(record) {\n const categories = [];\n if (record.children) {\n for (const childId in record.children) {\n const category = record.children[childId];\n // Add this category to the list.\n let child = {\n categoryId: parseInt(category.id),\n parentId: parseInt(category.parent),\n categoryName: category.name,\n children: null,\n };\n if (category.children) {\n // If the child has its own children, recursively make a list of those.\n child.children = this.getCategoryDataFromRecords(category);\n }\n categories.push(child);\n }\n }\n return categories;\n }\n\n\n /**\n * Recursively create a list of all valid destinations for a current category within a parent category.\n *\n * Each entry in the list represents moving the category \"before\" another category by default, but may also represent\n * moving \"after\" another category, or \"as a new child of\" a parent category.\n *\n * @param {Object} categoryData A list of category data from getCategoryDataFromElements() or getCategoryDataFromRecords().\n * @param {Number} movingCategoryId The ID of the category currently being moved.\n * @return {Array} A list of objects representing valid move targets for the category. Each object has:\n * movingcategoryid - The ID of the category we are moving.\n * precedingsiblingid - The ID of the previous category under the same parent as this target. 0 if this is the first child.\n * parent - The ID of the target category's parent category. 0 if this is the top category.\n * categoryname - The name of the target category, to display as part of the destination.\n * categories - An array of child category targets. If there are no children, this must be null\n * to prevent infinite recursion in the template.\n * newchild - If true, this destination is \"as a new child of the parent\".\n * lastchild - If true, this destination is after the target category, rather than before.\n */\n createMoveCategoryList(categoryData, movingCategoryId) {\n const categories = [];\n if (categoryData) {\n let precedingSibling = null;\n categoryData.forEach(category => {\n // Don't create a target for the category that's moving.\n if (parseInt(category.categoryId) === movingCategoryId) {\n return;\n }\n // Create a target to move before this child.\n let child = {\n movingcategoryid: movingCategoryId,\n precedingsiblingid: precedingSibling?.categoryId ?? 0,\n parent: category.parentId,\n categoryname: category.categoryName,\n categories: null, // Prevent infinite recursion in the template.\n };\n if (category.children) {\n // If the child has its own children, recursively make a list of those.\n child.categories = this.createMoveCategoryList(category.children, movingCategoryId);\n } else {\n // Otherwise, create a target to move as a new child of this one.\n child.categories = [\n {\n movingcategoryid: movingCategoryId,\n precedingsiblingid: 0,\n parent: category.categoryId,\n categoryname: category.categoryName,\n categories: null, // Prevent infinite recursion in the template.\n newchild: true,\n }\n ];\n }\n categories.push(child);\n precedingSibling = category;\n });\n if (precedingSibling) {\n if (precedingSibling.categoryId !== movingCategoryId) {\n // If this is the last child of its parent, also create a target to move the category after this one.\n categories.push({\n movingcategoryid: movingCategoryId,\n precedingsiblingid: precedingSibling.categoryId,\n parent: precedingSibling.parentId,\n categoryname: precedingSibling.categoryName,\n categories: null, // Prevent infinite recursion in the template.\n lastchild: true,\n });\n }\n }\n }\n return categories;\n }\n\n /**\n * Displays a modal containing links to move the category to a new location.\n *\n * @param {Event} e Button click event.\n */\n async showMoveModal(e) {\n // Return if it is not menu item.\n const item = e.target.closest(this.selectors.MOVE_BUTTON);\n if (!item) {\n return;\n }\n // Return if it is disabled.\n if (item.getAttribute('aria-disabled') === 'true') {\n return;\n }\n\n // Prevent addition click on the item.\n item.setAttribute('aria-disabled', true);\n\n // Build the list of move links.\n const contextElement = document.querySelector(this.selectors.CONTEXT);\n const categoryData = this.getCategoryDataFromElements(contextElement);\n\n const moveContext = {\n contextname: contextElement.dataset.contextname,\n contextid: contextElement.dataset.contextid,\n cmid: categorymanager.state.page.cmid,\n categories: [],\n hascategories: false,\n };\n const movingCategoryId = parseInt(item.dataset.categoryid);\n moveContext.categories = this.createMoveCategoryList(categoryData, movingCategoryId);\n moveContext.hascategories = moveContext.categories.length > 0;\n\n const moveCategory = getString('movecategory', 'qbank_managecategories', item.dataset.categoryname);\n const modal = await Modal.create({\n title: moveCategory,\n body: Templates.render('qbank_managecategories/move_context_list', moveContext),\n footer: '',\n show: true,\n large: true,\n });\n const switcher = new BankSwitcher();\n // Show modal and add click event for list items and bank switcher.\n modal.getBody()[0].addEventListener('click', async(e) => {\n const categoryItem = e.target.closest(this.selectors.MODAL_CATEGORY_ITEM);\n const moveHeader = e.currentTarget.querySelector(this.selectors.MOVE_BANK_HEADER);\n if (categoryItem) {\n categorymanager.moveCategory(\n categoryItem.dataset.movingcategoryid,\n categoryItem.dataset.parent,\n categoryItem.dataset.precedingsiblingid,\n );\n if (moveHeader.dataset.cmid !== categorymanager.state.page.cmid) {\n const url = CoreUrl.relativeUrl(\n '/question/bank/managecategories/category.php',\n {cmid: moveHeader.dataset.cmid}\n );\n const message = await getString(\n 'categorymovedto',\n 'qbank_managecategories',\n {url, name: moveHeader.textContent},\n );\n Notification.addNotification({message: message, type: 'info'});\n }\n modal.destroy();\n return;\n }\n const switchButton = e.target.closest(this.selectors.SWITCH_QUESTION_BANK);\n if (switchButton) {\n const pageState = categorymanager.state.page;\n try {\n const contextId = parseInt(contextElement.dataset.contextid);\n await switcher.show(modal, pageState.courseid, contextId, parseInt(moveHeader.dataset.cmid), pageState.cmid);\n } catch (ex) {\n Notification.exception(ex);\n }\n }\n });\n modal.getModal()[0].addEventListener('bankSwitched', async(e) => {\n try {\n const params = {coursemodule: e.detail.cmid};\n const categoriesResponse = await Fetch.performGet(\n 'core_question',\n 'categories',\n {params},\n );\n const {context, categories} = await categoriesResponse.json();\n // Convert the list of categories into a nested tree.\n for (const id in categories) {\n const category = categories[id];\n if (category.parent > 0) {\n const parentitem = categories[category.parent];\n if (!parentitem.hasOwnProperty('children')) {\n parentitem.children = {};\n }\n categories[category.parent].children[category.id] = category;\n }\n }\n // Get the top category with all the others nested below.\n let topCategory;\n for (const id in categories) {\n if (parseInt(categories[id].parent) === 0) {\n topCategory = categories[id];\n }\n }\n const categoryData = this.getCategoryDataFromRecords(topCategory);\n const moveContext = {\n contextname: context.prefixedname,\n contextid: context.prefixedname,\n cmid: e.detail.cmid,\n categories: [],\n hascategories: false,\n };\n moveContext.categories = this.createMoveCategoryList(categoryData, movingCategoryId);\n moveContext.hascategories = moveContext.categories.length > 0;\n modal.setBody(\n Templates.render('qbank_managecategories/move_context_list', moveContext),\n );\n await modal.getBodyPromise();\n modal.setTitle(moveCategory);\n modal.setFooter('');\n } catch (ex) {\n Notification.alert(getString('error', 'error'), ex);\n }\n });\n item.setAttribute('aria-disabled', false);\n }\n\n /**\n * Check and add a child list if needed.\n *\n * Check whether the category that has just been added has this category as its parent. If it does,\n * check that this category has a child list, and if not, add one.\n *\n * @param {Object} args\n * @param {Element} args.element The new category.\n * @return {Promise}\n */\n async checkChildList({element}) {\n if (element.parent !== this.getElement().dataset.categoryid) {\n return null; // Not for me.\n }\n let childList = this.getElement(this.selectors.CATEGORY_LIST);\n if (childList) {\n return null; // List already exists, it will handle adding the new category.\n }\n // Render and add a new child list containing the new category.\n return this.createChildList({\n categoryid: element.parent,\n children: [\n element.templatecontext,\n ]\n });\n }\n\n /**\n * Update the name in the category item element's data, for building move targets.\n *\n * @param {Object} args\n * @param {Object} args.element\n */\n updateName({element}) {\n this.getElement().dataset.categoryname = element.name;\n }\n}\n"],"names":["BaseComponent","create","descriptor","name","element","id","selectors","CATEGORY_LIST","CATEGORY_ITEM","CATEGORY_CONTENTS","EDIT_BUTTON","EDITABLE_CATEGORY_NAME","MOVE_BUTTON","CONTEXT","MODAL_CATEGORY_ITEM","CONTENT_AREA","CATEGORY_ID","CONTENT_CONTAINER","CHILD_LIST","PREVIOUS_SIBLING","sortorder","SWITCH_QUESTION_BANK","MOVE_BANK_HEADER","classes","NO_BOTTOM_PADDING","DRAGHANDLE","DROPTARGET","ids","CATEGORY","stateReady","initDragDrop","addEventListener","this","getElement","categorymanager","showEditModal","moveButton","showMoveModal","inplaceEditableEventTypes","elementUpdated","e","editable","target","closest","updateCategoryName","dataset","itemid","value","destroy","deInitDragDrop","classList","contains","getDraggableData","_getDraggableData","dragdrop","DragDrop","undefined","setDraggable","unregister","document","querySelector","reactive","categoryid","validateDropData","showDropZone","dropData","add","hideDropZone","remove","drop","event","dropTarget","getElementById","targetParentId","parent","parentList","precedingSibling","firstElementChild","previousElementSibling","moveCategory","_precedingSibling","getWatchers","watch","handler","updatePosition","rerender","updateName","checkChildList","html","js","Templates","renderForPromise","templatecontext","replaceNodeContents","context","parentContainer","appendNodeContents","childList","newParent","originParent","contextid","state","page","previousSibling","nextSibling","parseInt","createChildList","parentElement","_previousSibling","nextElementSibling","insertBefore","appendChild","stateManager","processUpdates","action","fields","childCount","querySelectorAll","length","isDraggable","draghandle","getCategoryDataFromElements","categories","children","forEach","category","child","categoryId","parentId","categoryName","categoryname","push","getCategoryDataFromRecords","record","childId","createMoveCategoryList","categoryData","movingCategoryId","movingcategoryid","precedingsiblingid","_precedingSibling2","newchild","lastchild","item","getAttribute","setAttribute","contextElement","moveContext","contextname","cmid","hascategories","modal","Modal","title","body","render","footer","show","large","switcher","BankSwitcher","getBody","async","categoryItem","moveHeader","currentTarget","url","CoreUrl","relativeUrl","message","textContent","addNotification","type","pageState","contextId","courseid","ex","exception","getModal","params","coursemodule","detail","categoriesResponse","Fetch","performGet","json","parentitem","hasOwnProperty","topCategory","prefixedname","setBody","getBodyPromise","setTitle","setFooter","alert"],"mappings":"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"} \ No newline at end of file diff --git a/public/question/bank/managecategories/amd/build/categorymanager.min.js b/public/question/bank/managecategories/amd/build/categorymanager.min.js index 8d2609c59f4..f095f31e6e0 100644 --- a/public/question/bank/managecategories/amd/build/categorymanager.min.js +++ b/public/question/bank/managecategories/amd/build/categorymanager.min.js @@ -1,3 +1,3 @@ -define("qbank_managecategories/categorymanager",["exports","core/reactive","core/str","qbank_managecategories/mutations","qbank_managecategories/events","core/ajax","core/notification","core_form/modalform"],(function(_exports,_reactive,_str,_mutations,_events,_ajax,_notification,_modalform){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=_exports.categorymanager=void 0,_ajax=_interopRequireDefault(_ajax),_notification=_interopRequireDefault(_notification),_modalform=_interopRequireDefault(_modalform);const SELECTORS_CATEGORY_LIST=".qbank_managecategories-categorylist",SELECTORS_CONTEXT=".qbank_managecategories-categorylist[data-contextid]",SELECTORS_CATEGORY_ITEM=".qbank_managecategories-item[data-categoryid]",SELECTORS_CATEGORY_ROOT="#categoryroot",SELECTORS_SHOWDESCRIPTIONS_TOGGLE="#showdescriptions-toggle",SELECTORS_ADD_EDIT_BUTTON='[data-action="addeditcategory"]',CLASSES_DRAGHANDLE="draghandle",CLASSES_DANGER="alert-danger";class CategoryManager extends _reactive.Reactive{moveCategory(categoryId,targetParentId){let precedingSiblingId=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;const call={methodname:"qbank_managecategories_move_category",args:{pagecontextid:this.state.page.contextid,categoryid:categoryId,targetparentid:targetParentId,precedingsiblingid:precedingSiblingId}};_ajax.default.call([call])[0].then((stateUpdates=>(this.stateManager.processUpdates(stateUpdates),stateUpdates))).catch((error=>{var _document$getElements;_notification.default.addNotification({message:error.message,type:"error"}),null===(_document$getElements=document.getElementsByClassName(CLASSES_DANGER)[0])||void 0===_document$getElements||_document$getElements.scrollIntoView()}))}getTitle(isEdit){return(0,_str.get_string)(isEdit?"editcategory":"addcategory","question")}getSave(isEdit){return isEdit?(0,_str.get_string)("savechanges","core"):(0,_str.get_string)("addcategory","question")}showEditModal(e){const addEditButton=e.target.closest(SELECTORS_ADD_EDIT_BUTTON);if(!addEditButton)return;if(!addEditButton.dataset.actiontype)return;e.preventDefault();const title=categorymanager.getTitle("edit"===addEditButton.dataset.actiontype),save=categorymanager.getSave("edit"===addEditButton.dataset.actiontype),cmid=addEditButton.dataset.cmid,courseid=addEditButton.dataset.courseid,questioncount=addEditButton.dataset.questioncount;let contextid=addEditButton.dataset.contextid,categoryid=null,sortorder=null,parent=null;const categoryItem=e.target.closest(SELECTORS_CATEGORY_ITEM);if(categoryItem){contextid=categoryItem.dataset.contextid,categoryid=categoryItem.dataset.categoryid,sortorder=categoryItem.dataset.sortorder;const parentContext=categoryItem.closest(SELECTORS_CONTEXT);parent=categoryItem.dataset.parent+","+parentContext.dataset.contextid}const modalForm=new _modalform.default({formClass:"qbank_managecategories\\form\\question_category_edit_form",args:{cmid:cmid,courseid:courseid,questioncount:questioncount,contextid:contextid,categoryid:categoryid,sortorder:sortorder,parent:parent},modalConfig:{title:title,large:!0},saveButtonText:save,returnFocus:addEditButton});modalForm.addEventListener(modalForm.events.FORM_SUBMITTED,(response=>{categorymanager.stateManager.processUpdates(response.detail)})),modalForm.show()}updateCategoryName(categoryId,newName){categorymanager.stateManager.setReadOnly(!1),categorymanager.stateManager.processUpdate("categories","put",{id:categoryId,name:newName}),categorymanager.stateManager.setReadOnly(!0)}}const categorymanager=new CategoryManager({name:"qtype_managecategories_categorymanager",eventName:_events.eventTypes.qbankManagecategoriesStateUpdated,eventDispatch:_events.notifyQbankManagecategoriesStateUpdated,mutations:_mutations.mutations});_exports.categorymanager=categorymanager;_exports.init=()=>{(async reactive=>{const stateData={page:{contextid:document.querySelector(SELECTORS_CATEGORY_ROOT).dataset.contextid,showdescriptions:document.querySelector(SELECTORS_SHOWDESCRIPTIONS_TOGGLE).checked},categories:[],categoryLists:[]};document.querySelectorAll(SELECTORS_CATEGORY_ITEM).forEach((item=>{stateData.categories.push({id:item.dataset.categoryid,name:item.dataset.categoryname,parent:item.dataset.parent,contextid:item.dataset.contextid,sortorder:item.dataset.sortorder,draghandle:item.classList.contains(CLASSES_DRAGHANDLE)})})),document.querySelectorAll(SELECTORS_CATEGORY_LIST).forEach((categoryList=>{stateData.categoryLists.push({id:categoryList.dataset.categoryid,childCount:categoryList.querySelectorAll(SELECTORS_CATEGORY_ITEM).length})})),reactive.setInitialState(stateData)})(categorymanager)}})); +define("qbank_managecategories/categorymanager",["exports","core/reactive","core/str","qbank_managecategories/mutations","qbank_managecategories/events","core/ajax","core/notification","core_form/modalform"],(function(_exports,_reactive,_str,_mutations,_events,_ajax,_notification,_modalform){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=_exports.categorymanager=void 0,_ajax=_interopRequireDefault(_ajax),_notification=_interopRequireDefault(_notification),_modalform=_interopRequireDefault(_modalform);const SELECTORS_CATEGORY_LIST=".qbank_managecategories-categorylist",SELECTORS_CONTEXT=".qbank_managecategories-categorylist[data-contextid]",SELECTORS_CATEGORY_ITEM=".qbank_managecategories-item[data-categoryid]",SELECTORS_CATEGORY_ROOT="#categoryroot",SELECTORS_SHOWDESCRIPTIONS_TOGGLE="#showdescriptions-toggle",SELECTORS_ADD_EDIT_BUTTON='[data-action="addeditcategory"]',CLASSES_DRAGHANDLE="draghandle",CLASSES_DANGER="alert-danger";class CategoryManager extends _reactive.Reactive{moveCategory(categoryId,targetParentId){let precedingSiblingId=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;const call={methodname:"qbank_managecategories_move_category",args:{pagecontextid:this.state.page.contextid,categoryid:categoryId,targetparentid:targetParentId,precedingsiblingid:precedingSiblingId}};_ajax.default.call([call])[0].then((stateUpdates=>(this.stateManager.processUpdates(stateUpdates),stateUpdates))).catch((error=>{var _document$getElements;_notification.default.addNotification({message:error.message,type:"error"}),null===(_document$getElements=document.getElementsByClassName(CLASSES_DANGER)[0])||void 0===_document$getElements||_document$getElements.scrollIntoView()}))}getTitle(isEdit){return(0,_str.get_string)(isEdit?"editcategory":"addcategory","question")}getSave(isEdit){return isEdit?(0,_str.get_string)("savechanges","core"):(0,_str.get_string)("addcategory","question")}showEditModal(e){const addEditButton=e.target.closest(SELECTORS_ADD_EDIT_BUTTON);if(!addEditButton)return;if(!addEditButton.dataset.actiontype)return;e.preventDefault();const title=categorymanager.getTitle("edit"===addEditButton.dataset.actiontype),save=categorymanager.getSave("edit"===addEditButton.dataset.actiontype),cmid=addEditButton.dataset.cmid,courseid=addEditButton.dataset.courseid,questioncount=addEditButton.dataset.questioncount;let contextid=addEditButton.dataset.contextid,categoryid=null,sortorder=null,parent=null;const categoryItem=e.target.closest(SELECTORS_CATEGORY_ITEM);if(categoryItem){contextid=categoryItem.dataset.contextid,categoryid=categoryItem.dataset.categoryid,sortorder=categoryItem.dataset.sortorder;const parentContext=categoryItem.closest(SELECTORS_CONTEXT);parent=categoryItem.dataset.parent+","+parentContext.dataset.contextid}const modalForm=new _modalform.default({formClass:"qbank_managecategories\\form\\question_category_edit_form",args:{cmid:cmid,courseid:courseid,questioncount:questioncount,contextid:contextid,categoryid:categoryid,sortorder:sortorder,parent:parent},modalConfig:{title:title,large:!0},saveButtonText:save,returnFocus:addEditButton});modalForm.addEventListener(modalForm.events.FORM_SUBMITTED,(response=>{categorymanager.stateManager.processUpdates(response.detail)})),modalForm.show()}updateCategoryName(categoryId,newName){categorymanager.stateManager.setReadOnly(!1),categorymanager.stateManager.processUpdate("categories","put",{id:categoryId,name:newName}),categorymanager.stateManager.setReadOnly(!0)}}const categorymanager=new CategoryManager({name:"qtype_managecategories_categorymanager",eventName:_events.eventTypes.qbankManagecategoriesStateUpdated,eventDispatch:_events.notifyQbankManagecategoriesStateUpdated,mutations:_mutations.mutations});_exports.categorymanager=categorymanager;_exports.init=()=>{(async reactive=>{const rootElement=document.querySelector(SELECTORS_CATEGORY_ROOT),stateData={page:{contextid:rootElement.dataset.contextid,courseid:rootElement.dataset.courseid,cmid:rootElement.dataset.cmid,showdescriptions:document.querySelector(SELECTORS_SHOWDESCRIPTIONS_TOGGLE).checked},categories:[],categoryLists:[]};document.querySelectorAll(SELECTORS_CATEGORY_ITEM).forEach((item=>{stateData.categories.push({id:item.dataset.categoryid,name:item.dataset.categoryname,parent:item.dataset.parent,contextid:item.dataset.contextid,sortorder:item.dataset.sortorder,draghandle:item.classList.contains(CLASSES_DRAGHANDLE)})})),document.querySelectorAll(SELECTORS_CATEGORY_LIST).forEach((categoryList=>{stateData.categoryLists.push({id:categoryList.dataset.categoryid,childCount:categoryList.querySelectorAll(SELECTORS_CATEGORY_ITEM).length})})),reactive.setInitialState(stateData)})(categorymanager)}})); //# sourceMappingURL=categorymanager.min.js.map \ No newline at end of file diff --git a/public/question/bank/managecategories/amd/build/categorymanager.min.js.map b/public/question/bank/managecategories/amd/build/categorymanager.min.js.map index 7ee9e843b87..4b4f0d35e62 100644 --- a/public/question/bank/managecategories/amd/build/categorymanager.min.js.map +++ b/public/question/bank/managecategories/amd/build/categorymanager.min.js.map @@ -1 +1 @@ -{"version":3,"file":"categorymanager.min.js","sources":["../src/categorymanager.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 * Reactive module for category manager\n *\n * @module qbank_managecategories/categorymanager\n */\n\nimport {Reactive} from 'core/reactive';\nimport {get_string as getString} from 'core/str';\nimport {mutations} from 'qbank_managecategories/mutations';\nimport {eventTypes, notifyQbankManagecategoriesStateUpdated} from 'qbank_managecategories/events';\nimport Ajax from \"core/ajax\";\nimport Notification from \"core/notification\";\nimport ModalForm from 'core_form/modalform';\n\nconst SELECTORS = {\n CATEGORY_LIST: '.qbank_managecategories-categorylist',\n CONTEXT: '.qbank_managecategories-categorylist[data-contextid]',\n CATEGORY_ITEM: '.qbank_managecategories-item[data-categoryid]',\n CATEGORY_ROOT: '#categoryroot',\n SHOWDESCRIPTIONS_TOGGLE: '#showdescriptions-toggle',\n ADD_EDIT_BUTTON: '[data-action=\"addeditcategory\"]',\n};\n\nconst CLASSES = {\n DRAGHANDLE: 'draghandle',\n DANGER: 'alert-danger',\n};\n\n/**\n * Load the initial state.\n *\n * This iterates over the initial tree of category items, and captures the data required for the state from each category.\n * It also captures a count of the number of children in each list.\n *\n * @param {Reactive} reactive\n * @return {Promise}\n */\nconst loadState = async(reactive) => {\n const rootElement = document.querySelector(SELECTORS.CATEGORY_ROOT);\n const stateData = {\n page: {\n contextid: rootElement.dataset.contextid,\n showdescriptions: document.querySelector(SELECTORS.SHOWDESCRIPTIONS_TOGGLE).checked,\n },\n categories: [],\n categoryLists: [],\n };\n const listItems = document.querySelectorAll(SELECTORS.CATEGORY_ITEM);\n listItems.forEach(item => {\n stateData.categories.push({\n id: item.dataset.categoryid,\n name: item.dataset.categoryname,\n parent: item.dataset.parent,\n contextid: item.dataset.contextid,\n sortorder: item.dataset.sortorder,\n draghandle: item.classList.contains(CLASSES.DRAGHANDLE),\n });\n });\n const categoryLists = document.querySelectorAll(SELECTORS.CATEGORY_LIST);\n categoryLists.forEach(categoryList => {\n stateData.categoryLists.push({\n id: categoryList.dataset.categoryid,\n childCount: categoryList.querySelectorAll(SELECTORS.CATEGORY_ITEM).length,\n });\n });\n reactive.setInitialState(stateData);\n};\n\n/**\n * Reactive instance for the category manager.\n */\nclass CategoryManager extends Reactive {\n /**\n * Move a category to a new position within the given parent.\n *\n * This will call the move_category web service function to re-order the categories, then update\n * the state with the returned updates.\n *\n * @param {Number} categoryId The ID of the category being moved.\n * @param {Number} targetParentId The ID of the destination parent category (this may not have changed).\n * @param {Number} precedingSiblingId The ID of the category to put the moved category after.\n * This may be null if moving to the top of a list.\n */\n moveCategory(\n categoryId,\n targetParentId,\n precedingSiblingId = null,\n ) {\n const call = {\n methodname: 'qbank_managecategories_move_category',\n args: {\n pagecontextid: this.state.page.contextid,\n categoryid: categoryId,\n targetparentid: targetParentId,\n precedingsiblingid: precedingSiblingId,\n }\n };\n Ajax.call([call])[0]\n .then((stateUpdates) => {\n this.stateManager.processUpdates(stateUpdates);\n return stateUpdates;\n })\n .catch(error => {\n Notification.addNotification({\n message: error.message,\n type: 'error',\n });\n document.getElementsByClassName(CLASSES.DANGER)[0]?.scrollIntoView();\n });\n }\n\n /**\n * Return title for the add/edit modal.\n *\n * @param {boolean} isEdit is 'add' or 'edit' form\n * @returns {String} title string\n */\n getTitle(isEdit) {\n return getString(isEdit ? 'editcategory' : 'addcategory', 'question');\n }\n\n /**\n * Return save button label for the add/edit modal.\n *\n * @param {boolean} isEdit is 'add' or 'edit' form\n * @returns {String} save string\n */\n getSave(isEdit) {\n return isEdit ? getString('savechanges', 'core') : getString('addcategory', 'question');\n }\n\n /**\n * Function handling display of modal form.\n *\n * @param {Event} e The click event triggering the modal.\n */\n showEditModal(e) {\n const addEditButton = e.target.closest(SELECTORS.ADD_EDIT_BUTTON);\n\n // Return if it is not 'addeditcategory' button.\n if (!addEditButton) {\n return;\n }\n\n // Return if the action type is not specified.\n if (!addEditButton.dataset.actiontype) {\n return;\n }\n\n e.preventDefault();\n // Data for the modal.\n const title = categorymanager.getTitle(addEditButton.dataset.actiontype === 'edit');\n const save = categorymanager.getSave(addEditButton.dataset.actiontype === 'edit');\n const cmid = addEditButton.dataset.cmid;\n const courseid = addEditButton.dataset.courseid;\n const questioncount = addEditButton.dataset.questioncount;\n let contextid = addEditButton.dataset.contextid;\n let categoryid = null;\n let sortorder = null;\n let parent = null;\n const categoryItem = e.target.closest(SELECTORS.CATEGORY_ITEM);\n if (categoryItem) {\n contextid = categoryItem.dataset.contextid;\n categoryid = categoryItem.dataset.categoryid;\n sortorder = categoryItem.dataset.sortorder;\n const parentContext = categoryItem.closest(SELECTORS.CONTEXT);\n parent = categoryItem.dataset.parent + ',' + parentContext.dataset.contextid;\n }\n\n // Call the modal.\n const modalForm = new ModalForm({\n formClass: \"qbank_managecategories\\\\form\\\\question_category_edit_form\",\n args: {\n cmid,\n courseid,\n questioncount,\n contextid,\n categoryid,\n sortorder,\n parent,\n },\n modalConfig: {\n title: title,\n large: true,\n },\n saveButtonText: save,\n returnFocus: addEditButton,\n });\n // Once the form has been submitted via the web service, update the state with the new or updated\n // category based on the web service response.\n modalForm.addEventListener(modalForm.events.FORM_SUBMITTED, (response) => {\n categorymanager.stateManager.processUpdates(response.detail);\n });\n // Show the form.\n modalForm.show();\n }\n\n updateCategoryName(categoryId, newName) {\n categorymanager.stateManager.setReadOnly(false);\n categorymanager.stateManager.processUpdate('categories', 'put', {id: categoryId, name: newName});\n categorymanager.stateManager.setReadOnly(true);\n }\n}\n\nexport const categorymanager = new CategoryManager({\n name: 'qtype_managecategories_categorymanager',\n eventName: eventTypes.qbankManagecategoriesStateUpdated,\n eventDispatch: notifyQbankManagecategoriesStateUpdated,\n mutations,\n});\n\n/**\n * Load the initial state.\n */\nexport const init = () => {\n loadState(categorymanager);\n};\n"],"names":["SELECTORS","CLASSES","CategoryManager","Reactive","moveCategory","categoryId","targetParentId","precedingSiblingId","call","methodname","args","pagecontextid","this","state","page","contextid","categoryid","targetparentid","precedingsiblingid","then","stateUpdates","stateManager","processUpdates","catch","error","addNotification","message","type","document","getElementsByClassName","scrollIntoView","getTitle","isEdit","getSave","showEditModal","e","addEditButton","target","closest","dataset","actiontype","preventDefault","title","categorymanager","save","cmid","courseid","questioncount","sortorder","parent","categoryItem","parentContext","modalForm","ModalForm","formClass","modalConfig","large","saveButtonText","returnFocus","addEventListener","events","FORM_SUBMITTED","response","detail","show","updateCategoryName","newName","setReadOnly","processUpdate","id","name","eventName","eventTypes","qbankManagecategoriesStateUpdated","eventDispatch","notifyQbankManagecategoriesStateUpdated","mutations","async","stateData","querySelector","showdescriptions","checked","categories","categoryLists","querySelectorAll","forEach","item","push","categoryname","draghandle","classList","contains","categoryList","childCount","length","reactive","setInitialState","loadState"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"categorymanager.min.js","sources":["../src/categorymanager.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 * Reactive module for category manager\n *\n * @module qbank_managecategories/categorymanager\n */\n\nimport {Reactive} from 'core/reactive';\nimport {get_string as getString} from 'core/str';\nimport {mutations} from 'qbank_managecategories/mutations';\nimport {eventTypes, notifyQbankManagecategoriesStateUpdated} from 'qbank_managecategories/events';\nimport Ajax from \"core/ajax\";\nimport Notification from \"core/notification\";\nimport ModalForm from 'core_form/modalform';\n\nconst SELECTORS = {\n CATEGORY_LIST: '.qbank_managecategories-categorylist',\n CONTEXT: '.qbank_managecategories-categorylist[data-contextid]',\n CATEGORY_ITEM: '.qbank_managecategories-item[data-categoryid]',\n CATEGORY_ROOT: '#categoryroot',\n SHOWDESCRIPTIONS_TOGGLE: '#showdescriptions-toggle',\n ADD_EDIT_BUTTON: '[data-action=\"addeditcategory\"]',\n};\n\nconst CLASSES = {\n DRAGHANDLE: 'draghandle',\n DANGER: 'alert-danger',\n};\n\n/**\n * Load the initial state.\n *\n * This iterates over the initial tree of category items, and captures the data required for the state from each category.\n * It also captures a count of the number of children in each list.\n *\n * @param {Reactive} reactive\n * @return {Promise}\n */\nconst loadState = async(reactive) => {\n const rootElement = document.querySelector(SELECTORS.CATEGORY_ROOT);\n const stateData = {\n page: {\n contextid: rootElement.dataset.contextid,\n courseid: rootElement.dataset.courseid,\n cmid: rootElement.dataset.cmid,\n showdescriptions: document.querySelector(SELECTORS.SHOWDESCRIPTIONS_TOGGLE).checked,\n },\n categories: [],\n categoryLists: [],\n };\n const listItems = document.querySelectorAll(SELECTORS.CATEGORY_ITEM);\n listItems.forEach(item => {\n stateData.categories.push({\n id: item.dataset.categoryid,\n name: item.dataset.categoryname,\n parent: item.dataset.parent,\n contextid: item.dataset.contextid,\n sortorder: item.dataset.sortorder,\n draghandle: item.classList.contains(CLASSES.DRAGHANDLE),\n });\n });\n const categoryLists = document.querySelectorAll(SELECTORS.CATEGORY_LIST);\n categoryLists.forEach(categoryList => {\n stateData.categoryLists.push({\n id: categoryList.dataset.categoryid,\n childCount: categoryList.querySelectorAll(SELECTORS.CATEGORY_ITEM).length,\n });\n });\n reactive.setInitialState(stateData);\n};\n\n/**\n * Reactive instance for the category manager.\n */\nclass CategoryManager extends Reactive {\n /**\n * Move a category to a new position within the given parent.\n *\n * This will call the move_category web service function to re-order the categories, then update\n * the state with the returned updates.\n *\n * @param {Number} categoryId The ID of the category being moved.\n * @param {Number} targetParentId The ID of the destination parent category (this may not have changed).\n * @param {Number} precedingSiblingId The ID of the category to put the moved category after.\n * This may be null if moving to the top of a list.\n */\n moveCategory(\n categoryId,\n targetParentId,\n precedingSiblingId = null,\n ) {\n const call = {\n methodname: 'qbank_managecategories_move_category',\n args: {\n pagecontextid: this.state.page.contextid,\n categoryid: categoryId,\n targetparentid: targetParentId,\n precedingsiblingid: precedingSiblingId,\n }\n };\n Ajax.call([call])[0]\n .then((stateUpdates) => {\n this.stateManager.processUpdates(stateUpdates);\n return stateUpdates;\n })\n .catch(error => {\n Notification.addNotification({\n message: error.message,\n type: 'error',\n });\n document.getElementsByClassName(CLASSES.DANGER)[0]?.scrollIntoView();\n });\n }\n\n /**\n * Return title for the add/edit modal.\n *\n * @param {boolean} isEdit is 'add' or 'edit' form\n * @returns {String} title string\n */\n getTitle(isEdit) {\n return getString(isEdit ? 'editcategory' : 'addcategory', 'question');\n }\n\n /**\n * Return save button label for the add/edit modal.\n *\n * @param {boolean} isEdit is 'add' or 'edit' form\n * @returns {String} save string\n */\n getSave(isEdit) {\n return isEdit ? getString('savechanges', 'core') : getString('addcategory', 'question');\n }\n\n /**\n * Function handling display of modal form.\n *\n * @param {Event} e The click event triggering the modal.\n */\n showEditModal(e) {\n const addEditButton = e.target.closest(SELECTORS.ADD_EDIT_BUTTON);\n\n // Return if it is not 'addeditcategory' button.\n if (!addEditButton) {\n return;\n }\n\n // Return if the action type is not specified.\n if (!addEditButton.dataset.actiontype) {\n return;\n }\n\n e.preventDefault();\n // Data for the modal.\n const title = categorymanager.getTitle(addEditButton.dataset.actiontype === 'edit');\n const save = categorymanager.getSave(addEditButton.dataset.actiontype === 'edit');\n const cmid = addEditButton.dataset.cmid;\n const courseid = addEditButton.dataset.courseid;\n const questioncount = addEditButton.dataset.questioncount;\n let contextid = addEditButton.dataset.contextid;\n let categoryid = null;\n let sortorder = null;\n let parent = null;\n const categoryItem = e.target.closest(SELECTORS.CATEGORY_ITEM);\n if (categoryItem) {\n contextid = categoryItem.dataset.contextid;\n categoryid = categoryItem.dataset.categoryid;\n sortorder = categoryItem.dataset.sortorder;\n const parentContext = categoryItem.closest(SELECTORS.CONTEXT);\n parent = categoryItem.dataset.parent + ',' + parentContext.dataset.contextid;\n }\n\n // Call the modal.\n const modalForm = new ModalForm({\n formClass: \"qbank_managecategories\\\\form\\\\question_category_edit_form\",\n args: {\n cmid,\n courseid,\n questioncount,\n contextid,\n categoryid,\n sortorder,\n parent,\n },\n modalConfig: {\n title: title,\n large: true,\n },\n saveButtonText: save,\n returnFocus: addEditButton,\n });\n // Once the form has been submitted via the web service, update the state with the new or updated\n // category based on the web service response.\n modalForm.addEventListener(modalForm.events.FORM_SUBMITTED, (response) => {\n categorymanager.stateManager.processUpdates(response.detail);\n });\n // Show the form.\n modalForm.show();\n }\n\n updateCategoryName(categoryId, newName) {\n categorymanager.stateManager.setReadOnly(false);\n categorymanager.stateManager.processUpdate('categories', 'put', {id: categoryId, name: newName});\n categorymanager.stateManager.setReadOnly(true);\n }\n}\n\nexport const categorymanager = new CategoryManager({\n name: 'qtype_managecategories_categorymanager',\n eventName: eventTypes.qbankManagecategoriesStateUpdated,\n eventDispatch: notifyQbankManagecategoriesStateUpdated,\n mutations,\n});\n\n/**\n * Load the initial state.\n */\nexport const init = () => {\n loadState(categorymanager);\n};\n"],"names":["SELECTORS","CLASSES","CategoryManager","Reactive","moveCategory","categoryId","targetParentId","precedingSiblingId","call","methodname","args","pagecontextid","this","state","page","contextid","categoryid","targetparentid","precedingsiblingid","then","stateUpdates","stateManager","processUpdates","catch","error","addNotification","message","type","document","getElementsByClassName","scrollIntoView","getTitle","isEdit","getSave","showEditModal","e","addEditButton","target","closest","dataset","actiontype","preventDefault","title","categorymanager","save","cmid","courseid","questioncount","sortorder","parent","categoryItem","parentContext","modalForm","ModalForm","formClass","modalConfig","large","saveButtonText","returnFocus","addEventListener","events","FORM_SUBMITTED","response","detail","show","updateCategoryName","newName","setReadOnly","processUpdate","id","name","eventName","eventTypes","qbankManagecategoriesStateUpdated","eventDispatch","notifyQbankManagecategoriesStateUpdated","mutations","async","rootElement","querySelector","stateData","showdescriptions","checked","categories","categoryLists","querySelectorAll","forEach","item","push","categoryname","draghandle","classList","contains","categoryList","childCount","length","reactive","setInitialState","loadState"],"mappings":"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"} \ No newline at end of file diff --git a/public/question/bank/managecategories/amd/src/category.js b/public/question/bank/managecategories/amd/src/category.js index a620347f2ce..1c2cb06f344 100644 --- a/public/question/bank/managecategories/amd/src/category.js +++ b/public/question/bank/managecategories/amd/src/category.js @@ -403,12 +403,24 @@ export default class extends BaseComponent { return categories; } + /** * Recursively create a list of all valid destinations for a current category within a parent category. * + * Each entry in the list represents moving the category "before" another category by default, but may also represent + * moving "after" another category, or "as a new child of" a parent category. + * * @param {Object} categoryData A list of category data from getCategoryDataFromElements() or getCategoryDataFromRecords(). * @param {Number} movingCategoryId The ID of the category currently being moved. - * @return {Array} + * @return {Array} A list of objects representing valid move targets for the category. Each object has: + * movingcategoryid - The ID of the category we are moving. + * precedingsiblingid - The ID of the previous category under the same parent as this target. 0 if this is the first child. + * parent - The ID of the target category's parent category. 0 if this is the top category. + * categoryname - The name of the target category, to display as part of the destination. + * categories - An array of child category targets. If there are no children, this must be null + * to prevent infinite recursion in the template. + * newchild - If true, this destination is "as a new child of the parent". + * lastchild - If true, this destination is after the target category, rather than before. */ createMoveCategoryList(categoryData, movingCategoryId) { const categories = []; @@ -416,18 +428,16 @@ export default class extends BaseComponent { let precedingSibling = null; categoryData.forEach(category => { // Don't create a target for the category that's moving. - if (category.categoryId === movingCategoryId) { + if (parseInt(category.categoryId) === movingCategoryId) { return; } // Create a target to move before this child. let child = { - categoryid: category.categoryId, movingcategoryid: movingCategoryId, precedingsiblingid: precedingSibling?.categoryId ?? 0, parent: category.parentId, categoryname: category.categoryName, - categories: null, - current: category.categoryId === movingCategoryId, + categories: null, // Prevent infinite recursion in the template. }; if (category.children) { // If the child has its own children, recursively make a list of those. @@ -440,7 +450,7 @@ export default class extends BaseComponent { precedingsiblingid: 0, parent: category.categoryId, categoryname: category.categoryName, - categories: null, + categories: null, // Prevent infinite recursion in the template. newchild: true, } ]; @@ -456,7 +466,7 @@ export default class extends BaseComponent { precedingsiblingid: precedingSibling.categoryId, parent: precedingSibling.parentId, categoryname: precedingSibling.categoryName, - categories: null, + categories: null, // Prevent infinite recursion in the template. lastchild: true, }); } diff --git a/public/question/bank/managecategories/amd/src/categorymanager.js b/public/question/bank/managecategories/amd/src/categorymanager.js index 5c0f4ad2983..3151238ca4e 100644 --- a/public/question/bank/managecategories/amd/src/categorymanager.js +++ b/public/question/bank/managecategories/amd/src/categorymanager.js @@ -55,6 +55,8 @@ const loadState = async(reactive) => { const stateData = { page: { contextid: rootElement.dataset.contextid, + courseid: rootElement.dataset.courseid, + cmid: rootElement.dataset.cmid, showdescriptions: document.querySelector(SELECTORS.SHOWDESCRIPTIONS_TOGGLE).checked, }, categories: [], diff --git a/public/question/bank/managecategories/classes/external/move_category.php b/public/question/bank/managecategories/classes/external/move_category.php index 93f4b15811e..398c088aa28 100644 --- a/public/question/bank/managecategories/classes/external/move_category.php +++ b/public/question/bank/managecategories/classes/external/move_category.php @@ -72,7 +72,7 @@ class move_category extends external_api { 'id' => new external_value(PARAM_INT, 'The ID of the category that was updated.'), 'sortorder' => new external_value(PARAM_INT, 'The new sortorder', VALUE_OPTIONAL), 'parent' => new external_value(PARAM_INT, 'The ID of the new parent category.', VALUE_OPTIONAL), - 'context' => new external_value(PARAM_INT, 'The ID of the new context.', VALUE_OPTIONAL), + 'contextid' => new external_value(PARAM_INT, 'The ID of the new context.', VALUE_OPTIONAL), 'draghandle' => new external_value( PARAM_BOOL, 'Should this category have a drag handle?', @@ -151,7 +151,7 @@ class move_category extends external_api { ) { $transaction->rollback(new moodle_exception('idnumberexists', 'qbank_managecategories')); } - $originstateupdate->fields->context = $targetparent->contextid; + $originstateupdate->fields->contextid = $targetparent->contextid; } // Update sort order. @@ -187,7 +187,10 @@ class move_category extends external_api { $toupdatesortorder = $DB->get_records_select('question_categories', $select, $params, $sort); foreach ($toupdatesortorder as $category) { $DB->set_field('question_categories', 'sortorder', ++$sortorder, ['id' => $category->id]); - $stateupdates[] = self::make_state_update($category->id, sortorder: $sortorder); + if ($targetparent->contextid == $pagecontextid) { + // If we're updating other categories in the current context, return state updates for those too. + $stateupdates[] = self::make_state_update($category->id, sortorder: $sortorder); + } } if (isset($originstateupdate->fields->parent)) { diff --git a/public/question/bank/managecategories/lang/en/qbank_managecategories.php b/public/question/bank/managecategories/lang/en/qbank_managecategories.php index dbc2ba4bd6e..00b849eeab4 100644 --- a/public/question/bank/managecategories/lang/en/qbank_managecategories.php +++ b/public/question/bank/managecategories/lang/en/qbank_managecategories.php @@ -27,6 +27,7 @@ $string['after'] = 'After {$a}'; $string['before'] = 'Before {$a}'; $string['categoryincategory'] = 'Cannot move category in same category.'; $string['categorymoved'] = 'The category has been moved successfully.'; +$string['categorymovedto'] = 'Category moved to {$a->name}.'; $string['confirmdelete'] = 'Delete category "{$a}"?. Any child categories will be moved to this category\'s parent.'; $string['descriptionnotdisplayed'] = 'Categories description not displayed'; $string['displaydescription'] = 'Categories description displayed'; diff --git a/public/question/bank/managecategories/templates/categories.mustache b/public/question/bank/managecategories/templates/categories.mustache index 0cbaa3a0b8d..0365af5e4a2 100644 --- a/public/question/bank/managecategories/templates/categories.mustache +++ b/public/question/bank/managecategories/templates/categories.mustache @@ -21,6 +21,8 @@ Context variables required for this template: * contextid - Context id for js init. + * courseid - The course the current module context sits within. + * cmid - The module the context belongs to. * categoriesrendered - Array representing categories data rendered. * items - Categories data. * heading - Category's heading for each context (ie: Course or Quiz). @@ -29,6 +31,8 @@ Example context (json): { "contextid": "18", + "courseid": "2", + "cmid": "10", "categoriesrendered": [{ "items": [{ "categoryid": "1", @@ -43,14 +47,16 @@ "iconright": "", "children": "" }], - "heading": "Question categories for 'Quiz: qz'", - "ctxlvl": "contextlevel70" + "heading": "Question categories for 'Quiz: qz'" }] } }}
+ data-contextid="{{{contextid}}}" + data-courseid="{{courseid}}" + data-cmid="{{cmid}}" + > {{#categoriesrendered}}

{{heading}}

diff --git a/public/question/bank/managecategories/templates/move_category_list.mustache b/public/question/bank/managecategories/templates/move_category_list.mustache index 9004544e1ec..212f7cb34b6 100644 --- a/public/question/bank/managecategories/templates/move_category_list.mustache +++ b/public/question/bank/managecategories/templates/move_category_list.mustache @@ -24,56 +24,94 @@ Context variables required for this template: * categories - Array of category objects. - * categoryname - The name of the category to move to. - * categoryid - The ID of the category. - * firstchild - Is this the first child of its parent? If so, a "before" option will be rendered as well as "after". - * current - Is this the category being moved? If so, skip rendering it as a target. + * categoryid - The ID of the category to move relative to. + * movingcategoryid - The ID of the category we are moving. + * categoryname - The name of the category to move relative to. + * lastchild - Is this the last child of its parent? If true, an "after X" option will be rendered. + * precedingsiblingid - The ID of the preceding category under the same parent. + * parent - The ID of the parent category. + * newchild - If true, render this as a "new child of X" option rather than "before X". * categories - Array containing child categories of this category. - If there are none, this must be set null or [] to avoid infinite recursion. + If there are none, this must be to [] to avoid infinite recursion. Example context (json): { "categories": [ { - "categoryname": "Default category for course 1", - "categoryid": 3, - "firstchild": true, - "current": false, - "hascategories": false, - "categories": [] - }, - { - "categoryname": "Test category 1", - "categoryid": 4, - "firstchild": false, - "current": false, - "hascategories": false, - "categories": [] - }, - { - "categoryname": "Test category 2", - "categoryid": 5, - "firstchild": false, - "current": true, - "hascategories": false, - "categories": [] - }, - { - "categoryname": "Test category 3 x < 1 && y > 2 ", - "categoryid": 6, - "firstchild": false, - "current": false, - "hascategories": true, + "categoryid": "154001", + "movingcategoryid": 154007, + "precedingsiblingid": 0, + "parent": "154000", + "categoryname": "Default for Qbank 1", "categories": [ { - "categoryname": "Test category 4", - "categoryid": 7, - "firstchild": true, - "current": false, - "hascategories": false, - "categories": [] + "movingcategoryid": 154007, + "precedingsiblingid": 0, + "parent": "154001", + "categoryname": "Default for Qbank 1", + "categories": [], + "newchild": true } ] + }, + { + "categoryid": "154004", + "movingcategoryid": 154007, + "precedingsiblingid": "154001", + "parent": "154000", + "categoryname": "Course category 1", + "categories": [ + { + "categoryid": "154006", + "movingcategoryid": 154007, + "precedingsiblingid": 0, + "parent": "154004", + "categoryname": "Course category 3", + "categories": [ + { + "movingcategoryid": 154007, + "precedingsiblingid": 0, + "parent": "154006", + "categoryname": "Course category 3", + "categories": [], + "newchild": true + } + ] + }, + { + "movingcategoryid": 154007, + "precedingsiblingid": "154006", + "parent": "154004", + "categoryname": "Course category 3", + "categories": [], + "lastchild": true + } + ] + }, + { + "categoryid": "154005", + "movingcategoryid": 154007, + "precedingsiblingid": "154004", + "parent": "154000", + "categoryname": "Course category 2", + "categories": [ + { + "movingcategoryid": 154007, + "precedingsiblingid": 0, + "parent": "154005", + "categoryname": "Course category 2", + "categories": [], + "newchild": true + } + ] + }, + { + "movingcategoryid": 154007, + "precedingsiblingid": "154005", + "parent": "154000", + "categoryname": "Course category 2", + "categories": [], + "lastchild": true } ] } diff --git a/public/question/bank/managecategories/templates/move_context_list.mustache b/public/question/bank/managecategories/templates/move_context_list.mustache index 87e042a07d6..467750fb4be 100644 --- a/public/question/bank/managecategories/templates/move_context_list.mustache +++ b/public/question/bank/managecategories/templates/move_context_list.mustache @@ -27,6 +27,7 @@ Example context (json): { "contexid": 1, + "cmid": 1, "contextname": "Question bank: Qbank 1", "categories": [ { @@ -50,7 +51,7 @@ }}
-

{{{contextname}}}

+

{{{contextname}}}

diff --git a/public/question/bank/managecategories/tests/behat/move_question_categories.feature b/public/question/bank/managecategories/tests/behat/move_question_categories.feature index cb340a8289d..08fc8343f25 100644 --- a/public/question/bank/managecategories/tests/behat/move_question_categories.feature +++ b/public/question/bank/managecategories/tests/behat/move_question_categories.feature @@ -104,3 +104,34 @@ Feature: A teacher can reorder question categories And I should not see "+" in the "Course category 2" "list_item" When I drag "Course category 3" "list_item" and I drop it in "Course category 1" "text" Then I should see "+" in the "Course category 1" "list_item" + + Scenario: Teacher can move a category to another bank + Given the following "question categories" exist: + | contextlevel | reference | name | idnumber | questioncategory | + | Activity module | qbank1 | Course category 4 | questioncat4 | Course category 1 | + And I reload the page + And "Default for Qbank 1" "list_item" should exist + And "Course category 1" "list_item" should exist + And "Course category 2" "list_item" should exist + And "Course category 3" "list_item" should exist + And "Course category 4" "list_item" should exist + And I open the action menu in "Course category 1" "list_item" + And I choose "Move" in the open action menu + And I should see "Question bank: Qbank 1" in the "Move Course category 1" "dialogue" + And I should see "Before Course category 3" in the "Move Course category 1" "dialogue" + And I press "Switch bank" + And I click on "Qbank 2" "link" in the "Select question bank" "dialogue" + And I should see "Question bank: Qbank 2" in the "Move Course category 1" "dialogue" + And I should not see "Before Course category 3" in the "Move Course category 1" "dialogue" + And I should see "As new child of Default for Qbank 2" in the "Move Course category 1" "dialogue" + When I follow "As new child of Default for Qbank 2" + Then "Default for Qbank 1" "list_item" should exist + And "Course category 1" "list_item" should not exist + And "Course category 2" "list_item" should exist + And "Course category 3" "list_item" should exist + And "Course category 4" "list_item" should not exist + And I should see "Category moved to Question bank: Qbank 2" + And I follow "Question bank: Qbank 2" + And "Default for Qbank 2" "list_item" should exist + And "Course category 1" "list_item" should exist + And "Course category 4" "list_item" should exist diff --git a/public/question/bank/managecategories/tests/external/move_category_test.php b/public/question/bank/managecategories/tests/external/move_category_test.php index 8fb1964b369..2f2b333b01b 100644 --- a/public/question/bank/managecategories/tests/external/move_category_test.php +++ b/public/question/bank/managecategories/tests/external/move_category_test.php @@ -18,6 +18,7 @@ namespace qbank_managecategories\external; use context; use context_module; +use core\context\module; use moodle_url; use qbank_managecategories\question_categories; @@ -39,12 +40,12 @@ final class move_category_test extends \qbank_managecategories\manage_category_t /** * Return order of categories for a given context. * - * @param context $context The context to get the category order for. + * @param module $context The context to get the category order for. * @return array Nested array, keyed by category IDs. */ - private function get_current_order(context $context): array { - $categories = new question_categories(new moodle_url('/'), [$context]); - return $this->reduce_tree($categories->editlists[$context->id]->items); + private function get_current_order(module $context): array { + $categories = new question_categories(new moodle_url('/'), cmid: $context->instanceid); + return $this->reduce_tree($categories->editlist->items); } /** @@ -127,7 +128,7 @@ final class move_category_test extends \qbank_managecategories\manage_category_t ], ], ]; - $this->assertEquals($stateupdates, $expectedstateupdates); + $this->assertEquals($expectedstateupdates, $stateupdates); } /** diff --git a/public/question/classes/route/api/bank.php b/public/question/classes/route/api/bank.php index cec4e0683d5..f4f92c17246 100644 --- a/public/question/classes/route/api/bank.php +++ b/public/question/classes/route/api/bank.php @@ -25,14 +25,18 @@ use core\router\parameters\query_coursemodule; use core\router\require_login; use core\router\route; use core\router\schema\example; +use core\router\schema\objects\array_of_strings; use core\router\schema\objects\array_of_things; use core\router\schema\objects\schema_object; +use core\router\schema\parameters\path_parameter; 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\router\schema\response\response; use core_question\local\bank\formatted_bank; use core_question\local\bank\question_bank_helper; +use core_question\local\bank\question_edit_contexts; +use core_question\local\bank\question_version_status; use core_question\output\question_category_selector; use core_question\question_category; use Psr\Http\Message\ResponseInterface;