mirror of
https://github.com/moodle/moodle.git
synced 2025-08-05 00:46:50 +02:00
Merge branch 'MDL-76783-master' of https://github.com/ferranrecio/moodle
This commit is contained in:
commit
98b8ee2e1e
61 changed files with 1651 additions and 55 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
11
course/format/amd/build/local/content/bulkedittoggler.min.js
vendored
Normal file
11
course/format/amd/build/local/content/bulkedittoggler.min.js
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
define("core_courseformat/local/content/bulkedittoggler",["exports","core/reactive","core_courseformat/courseeditor","core/pending"],(function(_exports,_reactive,_courseeditor,_pending){var obj;
|
||||
/**
|
||||
* The bulk editor toggler button control.
|
||||
*
|
||||
* @module core_courseformat/local/content/bulkedittoggler
|
||||
* @class core_courseformat/local/content/bulkedittoggler
|
||||
* @copyright 2023 Ferran Recio <ferran@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_pending=(obj=_pending)&&obj.__esModule?obj:{default:obj};class Component extends _reactive.BaseComponent{create(){this.name="bulk_editor_toogler",this.selectors={BODY:"body",SELECTABLE:"[data-bulkcheckbox][data-is-selectable]"},this.classes={HIDDEN:"d-none",BULK:"bulkenabled"}}static init(target,selectors){return new this({element:document.querySelector(target),reactive:(0,_courseeditor.getCurrentCourseEditor)(),selectors:selectors})}stateReady(){this.addEventListener(this.element,"click",this._enableBulk)}getWatchers(){return[{watch:"bulk.enabled:updated",handler:this._refreshToggler}]}_refreshToggler(_ref){var _element$enabled,_document$querySelect;let{element:element}=_ref;this.element.classList.toggle(this.classes.HIDDEN,null!==(_element$enabled=element.enabled)&&void 0!==_element$enabled&&_element$enabled),null===(_document$querySelect=document.querySelector(this.selectors.BODY))||void 0===_document$querySelect||_document$querySelect.classList.toggle(this.classes.BULK,element.enabled)}_enableBulk(){const pendingToggle=new _pending.default("courseformat/content:bulktoggle_on");this.reactive.dispatch("bulkEnable",!0),setTimeout((()=>{var _document$querySelect2;null===(_document$querySelect2=document.querySelector(this.selectors.SELECTABLE))||void 0===_document$querySelect2||_document$querySelect2.focus(),pendingToggle.resolve()}),150)}}return _exports.default=Component,_exports.default}));
|
||||
|
||||
//# sourceMappingURL=bulkedittoggler.min.js.map
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"bulkedittoggler.min.js","sources":["../../../src/local/content/bulkedittoggler.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * The bulk editor toggler button control.\n *\n * @module core_courseformat/local/content/bulkedittoggler\n * @class core_courseformat/local/content/bulkedittoggler\n * @copyright 2023 Ferran Recio <ferran@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {BaseComponent} from 'core/reactive';\nimport {getCurrentCourseEditor} from 'core_courseformat/courseeditor';\nimport Pending from 'core/pending';\n\nexport default class Component extends BaseComponent {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'bulk_editor_toogler';\n // Default query selectors.\n this.selectors = {\n BODY: `body`,\n SELECTABLE: `[data-bulkcheckbox][data-is-selectable]`,\n };\n // Component css classes.\n this.classes = {\n HIDDEN: `d-none`,\n BULK: `bulkenabled`,\n };\n }\n\n /**\n * Static method to create a component instance from the mustache template.\n *\n * @param {string} target optional altentative DOM main element CSS selector\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 reactive: getCurrentCourseEditor(),\n selectors\n });\n }\n\n /**\n * Initial state ready method.\n */\n stateReady() {\n // Capture completion events.\n this.addEventListener(\n this.element,\n 'click',\n this._enableBulk\n );\n }\n\n /**\n * Component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n return [\n {watch: `bulk.enabled:updated`, handler: this._refreshToggler},\n ];\n }\n\n /**\n * Update a content section using the state information.\n *\n * @param {object} param\n * @param {Object} param.element details the update details (state.bulk in this case).\n */\n _refreshToggler({element}) {\n this.element.classList.toggle(this.classes.HIDDEN, element.enabled ?? false);\n document.querySelector(this.selectors.BODY)?.classList.toggle(this.classes.BULK, element.enabled);\n }\n\n /**\n * Dispatch the enable bulk mutation.\n *\n * The enable bulk button is outside of the course content main div.\n * Because content/actions captures click events only in the course\n * content, this button needs to trigger the enable bulk mutation\n * by itself.\n */\n _enableBulk() {\n const pendingToggle = new Pending(`courseformat/content:bulktoggle_on`);\n this.reactive.dispatch('bulkEnable', true);\n // Wait for a while and focus on the first checkbox.\n setTimeout(() => {\n document.querySelector(this.selectors.SELECTABLE)?.focus();\n pendingToggle.resolve();\n }, 150);\n }\n}\n"],"names":["Component","BaseComponent","create","name","selectors","BODY","SELECTABLE","classes","HIDDEN","BULK","target","this","element","document","querySelector","reactive","stateReady","addEventListener","_enableBulk","getWatchers","watch","handler","_refreshToggler","classList","toggle","enabled","pendingToggle","Pending","dispatch","setTimeout","focus","resolve"],"mappings":";;;;;;;;qJA4BqBA,kBAAkBC,wBAKnCC,cAESC,KAAO,2BAEPC,UAAY,CACbC,YACAC,2DAGCC,QAAU,CACXC,gBACAC,gCAWIC,OAAQN,kBACT,IAAIO,KAAK,CACZC,QAASC,SAASC,cAAcJ,QAChCK,UAAU,0CACVX,UAAAA,YAORY,kBAESC,iBACDN,KAAKC,QACL,QACAD,KAAKO,aASbC,oBACW,CACH,CAACC,6BAA+BC,QAASV,KAAKW,kBAUtDA,qEAAgBV,QAACA,mBACRA,QAAQW,UAAUC,OAAOb,KAAKJ,QAAQC,gCAAQI,QAAQa,qFAC3DZ,SAASC,cAAcH,KAAKP,UAAUC,8DAAOkB,UAAUC,OAAOb,KAAKJ,QAAQE,KAAMG,QAAQa,SAW7FP,oBACUQ,cAAgB,IAAIC,4DACrBZ,SAASa,SAAS,cAAc,GAErCC,YAAW,+DACPhB,SAASC,cAAcH,KAAKP,UAAUE,sEAAawB,QACnDJ,cAAcK,YACf"}
|
11
course/format/amd/build/local/content/bulkedittools.min.js
vendored
Normal file
11
course/format/amd/build/local/content/bulkedittools.min.js
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
define("core_courseformat/local/content/bulkedittools",["exports","core/reactive","core/sticky-footer","core_courseformat/courseeditor","core/str","core/pending","core/prefetch"],(function(_exports,_reactive,_stickyFooter,_courseeditor,_str,_pending,_prefetch){var obj;
|
||||
/**
|
||||
* The bulk editor tools bar.
|
||||
*
|
||||
* @module core_courseformat/local/content/bulkedittools
|
||||
* @class core_courseformat/local/content/bulkedittools
|
||||
* @copyright 2023 Ferran Recio <ferran@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_pending=(obj=_pending)&&obj.__esModule?obj:{default:obj},(0,_prefetch.prefetchStrings)("core_courseformat",["bulkselection"]);class Component extends _reactive.BaseComponent{create(){this.name="bulk_editor_tools",this.selectors={ACTIONS:'[data-for="bulkaction"]',ACTIONTOOL:'[data-for="bulkactions"] li',CANCEL:'[data-for="bulkcancel"]',COUNT:"[data-for='bulkcount']",SELECTABLE:"[data-bulkcheckbox][data-is-selectable]",SELECTALL:'[data-for="selectall"]',BULKBTN:'[data-for="enableBulk"]'},this.classes={HIDE:"d-none",DISABLED:"disabled"}}static init(target,selectors){return new this({element:document.querySelector(target),reactive:(0,_courseeditor.getCurrentCourseEditor)(),selectors:selectors})}stateReady(){const cancelBtn=this.getElement(this.selectors.CANCEL);cancelBtn&&this.addEventListener(cancelBtn,"click",this._cancelBulk);const selectAll=this.getElement(this.selectors.SELECTALL);selectAll&&this.addEventListener(selectAll,"change",this._selectAllClick)}getWatchers(){return[{watch:"bulk.enabled:updated",handler:this._refreshEnabled},{watch:"bulk:updated",handler:this._refreshTools}]}_refreshEnabled(_ref){let{element:element}=_ref;element.enabled?(0,_stickyFooter.enableStickyFooter)():(0,_stickyFooter.disableStickyFooter)()}_refreshTools(param){this._refreshSelectCount(param),this._refreshSelectAll(param),this._refreshActions(param)}async _refreshSelectCount(_ref2){let{element:bulk}=_ref2;const selectedCount=await(0,_str.get_string)("bulkselection","core_courseformat",bulk.selection.length),selectedElement=this.getElement(this.selectors.COUNT);selectedElement&&(selectedElement.innerHTML=selectedCount)}_refreshSelectAll(_ref3){let{element:bulk}=_ref3;const selectall=this.getElement(this.selectors.SELECTALL);if(!selectall)return;if(""===bulk.selectedType)return selectall.checked=!1,void(selectall.disabled=!0);selectall.disabled=!1;const maxSelection=document.querySelectorAll(this.selectors.SELECTABLE).length;selectall.checked=bulk.selection.length==maxSelection}_refreshActions(_ref4){let{element:bulk}=_ref4;const displayType="section"==bulk.selectedType?"section":"cm",enabled=""!==bulk.selectedType;this.getElements(this.selectors.ACTIONS).forEach((action=>{action.classList.toggle(this.classes.DISABLED,!enabled);const actionTool=action.closest(this.selectors.ACTIONTOOL),isHidden=action.dataset.bulk!=displayType;null==actionTool||actionTool.classList.toggle(this.classes.HIDE,isHidden)}))}_cancelBulk(){const pending=new _pending.default("courseformat/content:bulktoggle_off");this.reactive.dispatch("bulkEnable",!1),setTimeout((()=>{var _document$querySelect;null===(_document$querySelect=document.querySelector(this.selectors.BULKBTN))||void 0===_document$querySelect||_document$querySelect.focus(),pending.resolve()}),150)}_selectAllClick(event){const target=event.target,bulk=this.reactive.get("bulk");""!==bulk.selectedType&&(target.checked?this._handleSelectAll(bulk):this._handleUnselectAll())}_handleUnselectAll(){const pending=new _pending.default("courseformat/content:bulktUnselectAll");this.reactive.dispatch("bulkEnable",!0),setTimeout((()=>{var _document$querySelect2;null===(_document$querySelect2=document.querySelector(this.selectors.SELECTABLE))||void 0===_document$querySelect2||_document$querySelect2.focus(),pending.resolve()}),150)}_handleSelectAll(bulk){const selectableIds=[],selectables=document.querySelectorAll(this.selectors.SELECTABLE);if(0==selectables.length)return;selectables.forEach((selectable=>{selectableIds.push(selectable.dataset.id)}));const mutation="cm"===bulk.selectedType?"cmSelect":"sectionSelect";this.reactive.dispatch(mutation,selectableIds)}}return _exports.default=Component,_exports.default}));
|
||||
|
||||
//# sourceMappingURL=bulkedittools.min.js.map
|
File diff suppressed because one or more lines are too long
|
@ -8,6 +8,6 @@ define("core_courseformat/local/content/section/cmitem",["exports","core_coursef
|
|||
* @class core_courseformat/local/content/section/cmitem
|
||||
* @copyright 2021 Ferran Recio <ferran@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_dndcmitem=(obj=_dndcmitem)&&obj.__esModule?obj:{default:obj};class _default extends _dndcmitem.default{create(){this.name="content_section_cmitem",this.selectors={DRAGICON:".editing_move"},this.classes={LOCKED:"editinprogress"},this.id=this.element.dataset.id}stateReady(){var _this$getElement;this.configDragDrop(this.id),null===(_this$getElement=this.getElement(this.selectors.DRAGICON))||void 0===_this$getElement||_this$getElement.classList.add(this.classes.DRAGICON)}getWatchers(){return[{watch:"cm[".concat(this.id,"]:deleted"),handler:this.unregister},{watch:"cm[".concat(this.id,"]:updated"),handler:this._refreshCm}]}_refreshCm(_ref){var _element$dragging,_element$locked;let{element:element}=_ref;this.element.classList.toggle(this.classes.DRAGGING,null!==(_element$dragging=element.dragging)&&void 0!==_element$dragging&&_element$dragging),this.element.classList.toggle(this.classes.LOCKED,null!==(_element$locked=element.locked)&&void 0!==_element$locked&&_element$locked),this.locked=element.locked}}return _exports.default=_default,_exports.default}));
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_dndcmitem=(obj=_dndcmitem)&&obj.__esModule?obj:{default:obj};class _default extends _dndcmitem.default{create(){this.name="content_section_cmitem",this.selectors={BULKSELECT:"[data-for='cmBulkSelect']",BULKCHECKBOX:"[data-bulkcheckbox]",CARD:".activity-item",DRAGICON:".editing_move",INPLACEEDITABLE:"[data-inplaceeditablelink]"},this.classes={LOCKED:"editinprogress",HIDE:"d-none",SELECTED:"selected"},this.id=this.element.dataset.id}stateReady(state){var _this$getElement;this.configDragDrop(this.id),null===(_this$getElement=this.getElement(this.selectors.DRAGICON))||void 0===_this$getElement||_this$getElement.classList.add(this.classes.DRAGICON),this._refreshBulk({state:state}),this.addEventListener(this.element,"click",this._handleBulkModeClick)}getWatchers(){return[{watch:"cm[".concat(this.id,"]:deleted"),handler:this.unregister},{watch:"cm[".concat(this.id,"]:updated"),handler:this._refreshCm},{watch:"bulk:updated",handler:this._refreshBulk}]}_refreshCm(_ref){var _element$dragging,_element$locked;let{element:element}=_ref;this.element.classList.toggle(this.classes.DRAGGING,null!==(_element$dragging=element.dragging)&&void 0!==_element$dragging&&_element$dragging),this.element.classList.toggle(this.classes.LOCKED,null!==(_element$locked=element.locked)&&void 0!==_element$locked&&_element$locked),this.locked=element.locked}_refreshBulk(_ref2){var _this$getElement2;let{state:state}=_ref2;const bulk=state.bulk;this.setDraggable(!bulk.enabled),null===(_this$getElement2=this.getElement(this.selectors.BULKSELECT))||void 0===_this$getElement2||_this$getElement2.classList.toggle(this.classes.HIDE,!bulk.enabled);const disabled=!this._isCmBulkEnabled(bulk),selected=this._isSelected(bulk);this._refreshActivityCard(bulk,selected),this._setCheckboxValue(selected,disabled)}_refreshActivityCard(bulk,selected){var _this$getElement3,_this$getElement4;null===(_this$getElement3=this.getElement(this.selectors.INPLACEEDITABLE))||void 0===_this$getElement3||_this$getElement3.classList.toggle(this.classes.HIDE,bulk.enabled),null===(_this$getElement4=this.getElement(this.selectors.CARD))||void 0===_this$getElement4||_this$getElement4.classList.toggle(this.classes.SELECTED,selected),this.element.classList.toggle(this.classes.SELECTED,selected)}_setCheckboxValue(checked,disabled){const checkbox=this.getElement(this.selectors.BULKCHECKBOX);checkbox&&(checkbox.checked=checked,checkbox.disabled=disabled,disabled?checkbox.removeAttribute("data-is-selectable"):checkbox.dataset.isSelectable=1)}_handleBulkModeClick(event){if(event.target.closest(this.selectors.BULKSELECT))return;const bulk=this.reactive.get("bulk");if(!this._isCmBulkEnabled(bulk))return;event.preventDefault();const mutation=this._isSelected(bulk)?"cmUnselect":"cmSelect";this.reactive.dispatch(mutation,[this.id])}_isCmBulkEnabled(bulk){return!!bulk.enabled&&(""===bulk.selectedType||"cm"===bulk.selectedType)}_isSelected(bulk){return"cm"===bulk.selectedType&&bulk.selection.includes(this.id)}}return _exports.default=_default,_exports.default}));
|
||||
|
||||
//# sourceMappingURL=cmitem.min.js.map
|
File diff suppressed because one or more lines are too long
|
@ -8,6 +8,6 @@ define("core_courseformat/local/content/section/header",["exports","core_coursef
|
|||
* @class core_courseformat/local/content/section/header
|
||||
* @copyright 2021 Ferran Recio <ferran@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_dndsectionitem=(obj=_dndsectionitem)&&obj.__esModule?obj:{default:obj};class _default extends _dndsectionitem.default{create(descriptor){this.name="content_section_header",this.id=descriptor.id,this.section=descriptor.section,this.course=descriptor.course,this.fullregion=descriptor.fullregion}stateReady(state){this.configDragDrop(this.id,state,this.fullregion)}}return _exports.default=_default,_exports.default}));
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_dndsectionitem=(obj=_dndsectionitem)&&obj.__esModule?obj:{default:obj};class _default extends _dndsectionitem.default{create(descriptor){this.name="content_section_header",this.selectors={ACTIONSMENU:".section_action_menu",BULKSELECT:"[data-for='sectionBulkSelect']",BULKCHECKBOX:"[data-bulkcheckbox]"},this.classes={HIDE:"d-none",SELECTED:"selected"},this.id=descriptor.id,this.section=descriptor.section,this.course=descriptor.course,this.fullregion=descriptor.fullregion}stateReady(state){this.configDragDrop(this.id,state,this.fullregion),this._refreshBulk({state:state})}getWatchers(){return[{watch:"bulk:updated",handler:this._refreshBulk}]}_refreshBulk(_ref){var _this$getElement;let{state:state}=_ref;const bulk=state.bulk;if(!this._isSectionBulkEditable())return;this.setDraggable(!bulk.enabled),null===(_this$getElement=this.getElement(this.selectors.BULKSELECT))||void 0===_this$getElement||_this$getElement.classList.toggle(this.classes.HIDE,!bulk.enabled);const disabled=!this._isSectionBulkEnabled(bulk),selected=this._isSelected(bulk);this.element.classList.toggle(this.classes.SELECTED,selected),this._setCheckboxValue(selected,disabled)}_setCheckboxValue(checked,disabled){const checkbox=this.getElement(this.selectors.BULKCHECKBOX);checkbox&&(checkbox.checked=checked,checkbox.disabled=disabled,disabled?checkbox.removeAttribute("data-is-selectable"):checkbox.dataset.isSelectable=1)}_isSectionBulkEnabled(bulk){return!!bulk.enabled&&(""===bulk.selectedType||"section"===bulk.selectedType)}_isSectionBulkEditable(){var _section$bulkeditable;const section=this.reactive.get("section",this.id);return null!==(_section$bulkeditable=null==section?void 0:section.bulkeditable)&&void 0!==_section$bulkeditable&&_section$bulkeditable}_isSelected(bulk){return"section"===bulk.selectedType&&bulk.selection.includes(this.id)}}return _exports.default=_default,_exports.default}));
|
||||
|
||||
//# sourceMappingURL=header.min.js.map
|
File diff suppressed because one or more lines are too long
|
@ -9,6 +9,6 @@ define("core_courseformat/local/courseeditor/courseeditor",["exports","core/reac
|
|||
* @class core_courseformat/local/courseeditor/courseeditor
|
||||
* @copyright 2021 Ferran Recio <ferran@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_notification=_interopRequireDefault(_notification),_exporter=_interopRequireDefault(_exporter),_log=_interopRequireDefault(_log),_ajax=_interopRequireDefault(_ajax),Storage=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}(Storage);class _default extends _reactive.Reactive{constructor(){super(...arguments),_defineProperty(this,"stateKey",1),_defineProperty(this,"sectionReturn",0)}async loadCourse(courseId,serverStateKey){if(this.courseId)throw new Error("Cannot load ".concat(courseId,", course already loaded with id ").concat(this.courseId));let stateData;serverStateKey||(serverStateKey="invalidStateKey_".concat(Date.now())),this._editing=!1,this._supportscomponents=!1,this.courseId=courseId;const storeStateKey=Storage.get("course/".concat(courseId,"/stateKey"));try{this.isEditing||serverStateKey!=storeStateKey||(stateData=JSON.parse(Storage.get("course/".concat(courseId,"/staticState")))),stateData||(stateData=await this.getServerCourseState())}catch(error){return _log.default.error("EXCEPTION RAISED WHILE INIT COURSE EDITOR"),void _log.default.error(error)}if(this.setInitialState(stateData),this.isEditing)this.stateKey=null;else{const newState=JSON.stringify(stateData);var _stateData$course$sta,_stateData,_stateData$course;if(Storage.get("course/".concat(courseId,"/staticState"))!==newState||storeStateKey!==serverStateKey)Storage.set("course/".concat(courseId,"/staticState"),newState),Storage.set("course/".concat(courseId,"/stateKey"),null!==(_stateData$course$sta=null===(_stateData=stateData)||void 0===_stateData||null===(_stateData$course=_stateData.course)||void 0===_stateData$course?void 0:_stateData$course.statekey)&&void 0!==_stateData$course$sta?_stateData$course$sta:serverStateKey);this.stateKey=Storage.get("course/".concat(courseId,"/stateKey"))}}setViewFormat(setup){var _setup$editing,_setup$supportscompon;this._editing=null!==(_setup$editing=setup.editing)&&void 0!==_setup$editing&&_setup$editing,this._supportscomponents=null!==(_setup$supportscompon=setup.supportscomponents)&&void 0!==_setup$supportscompon&&_setup$supportscompon}async getServerCourseState(){const courseState=await _ajax.default.call([{methodname:"core_courseformat_get_state",args:{courseid:this.courseId}}])[0];return{course:{},section:[],cm:[],...JSON.parse(courseState)}}get isEditing(){var _this$_editing;return null!==(_this$_editing=this._editing)&&void 0!==_this$_editing&&_this$_editing}getExporter(){return new _exporter.default(this)}get supportComponents(){var _this$_supportscompon;return null!==(_this$_supportscompon=this._supportscomponents)&&void 0!==_this$_supportscompon&&_this$_supportscompon}getStorageValue(key){if(this.isEditing||!this.stateKey)return!1;const dataJson=Storage.get("course/".concat(this.courseId,"/").concat(key));if(!dataJson)return!1;try{const data=JSON.parse(dataJson);return(null==data?void 0:data.stateKey)===this.stateKey&&data.value}catch(error){return!1}}setStorageValue(key,value){if(this.isEditing)return!1;const data={stateKey:this.stateKey,value:value};return Storage.set("course/".concat(this.courseId,"/").concat(key),JSON.stringify(data))}async dispatch(){try{await super.dispatch(...arguments)}catch(error){_notification.default.exception(error),super.dispatch("unlockAll")}}}return _exports.default=_default,_exports.default}));
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_notification=_interopRequireDefault(_notification),_exporter=_interopRequireDefault(_exporter),_log=_interopRequireDefault(_log),_ajax=_interopRequireDefault(_ajax),Storage=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}(Storage);class _default extends _reactive.Reactive{constructor(){super(...arguments),_defineProperty(this,"stateKey",1),_defineProperty(this,"sectionReturn",0)}async loadCourse(courseId,serverStateKey){if(this.courseId)throw new Error("Cannot load ".concat(courseId,", course already loaded with id ").concat(this.courseId));let stateData;serverStateKey||(serverStateKey="invalidStateKey_".concat(Date.now())),this._editing=!1,this._supportscomponents=!1,this.courseId=courseId;const storeStateKey=Storage.get("course/".concat(courseId,"/stateKey"));try{this.isEditing||serverStateKey!=storeStateKey||(stateData=JSON.parse(Storage.get("course/".concat(courseId,"/staticState")))),stateData||(stateData=await this.getServerCourseState())}catch(error){return _log.default.error("EXCEPTION RAISED WHILE INIT COURSE EDITOR"),void _log.default.error(error)}if(stateData.bulk={enabled:!1,selectedType:"",selection:[]},this.setInitialState(stateData),this.isEditing)this.stateKey=null;else{const newState=JSON.stringify(stateData);var _stateData$course$sta,_stateData,_stateData$course;if(Storage.get("course/".concat(courseId,"/staticState"))!==newState||storeStateKey!==serverStateKey)Storage.set("course/".concat(courseId,"/staticState"),newState),Storage.set("course/".concat(courseId,"/stateKey"),null!==(_stateData$course$sta=null===(_stateData=stateData)||void 0===_stateData||null===(_stateData$course=_stateData.course)||void 0===_stateData$course?void 0:_stateData$course.statekey)&&void 0!==_stateData$course$sta?_stateData$course$sta:serverStateKey);this.stateKey=Storage.get("course/".concat(courseId,"/stateKey"))}}setViewFormat(setup){var _setup$editing,_setup$supportscompon;this._editing=null!==(_setup$editing=setup.editing)&&void 0!==_setup$editing&&_setup$editing,this._supportscomponents=null!==(_setup$supportscompon=setup.supportscomponents)&&void 0!==_setup$supportscompon&&_setup$supportscompon}async getServerCourseState(){const courseState=await _ajax.default.call([{methodname:"core_courseformat_get_state",args:{courseid:this.courseId}}])[0];return{course:{},section:[],cm:[],...JSON.parse(courseState)}}get isEditing(){var _this$_editing;return null!==(_this$_editing=this._editing)&&void 0!==_this$_editing&&_this$_editing}getExporter(){return new _exporter.default(this)}get supportComponents(){var _this$_supportscompon;return null!==(_this$_supportscompon=this._supportscomponents)&&void 0!==_this$_supportscompon&&_this$_supportscompon}getStorageValue(key){if(this.isEditing||!this.stateKey)return!1;const dataJson=Storage.get("course/".concat(this.courseId,"/").concat(key));if(!dataJson)return!1;try{const data=JSON.parse(dataJson);return(null==data?void 0:data.stateKey)===this.stateKey&&data.value}catch(error){return!1}}setStorageValue(key,value){if(this.isEditing)return!1;const data={stateKey:this.stateKey,value:value};return Storage.set("course/".concat(this.courseId,"/").concat(key),JSON.stringify(data))}async dispatch(){try{await super.dispatch(...arguments)}catch(error){_notification.default.exception(error),super.dispatch("unlockAll")}}}return _exports.default=_default,_exports.default}));
|
||||
|
||||
//# sourceMappingURL=courseeditor.min.js.map
|
File diff suppressed because one or more lines are too long
|
@ -10,6 +10,6 @@ define("core_courseformat/local/courseeditor/dndcmitem",["exports","core/reactiv
|
|||
* @copyright 2021 Ferran Recio <ferran@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class _default extends _reactive.BaseComponent{configDragDrop(cmid){this.id=cmid,this.reactive.isEditing&&this.reactive.supportComponents&&(this.dragdrop=new _reactive.DragDrop(this),this.classes=this.dragdrop.getClasses())}destroy(){void 0!==this.dragdrop&&this.dragdrop.unregister()}dragStart(dropdata){this.reactive.dispatch("cmDrag",[dropdata.id],!0)}dragEnd(dropdata){this.reactive.dispatch("cmDrag",[dropdata.id],!1)}getDraggableData(){return this.reactive.getExporter().cmDraggableData(this.reactive.state,this.id)}validateDropData(dropdata){return"cm"===(null==dropdata?void 0:dropdata.type)}showDropZone(dropdata){dropdata.nextcmid!=this.id&&dropdata.id!=this.id&&this.element.classList.add(this.classes.DROPUP)}hideDropZone(){this.element.classList.remove(this.classes.DROPUP)}drop(dropdata,event){if(dropdata.id!=this.id&&dropdata.nextcmid!=this.id){const mutation=event.altKey?"cmDuplicate":"cmMove";this.reactive.dispatch(mutation,[dropdata.id],null,this.id)}}}return _exports.default=_default,_exports.default}));
|
||||
class _default extends _reactive.BaseComponent{configDragDrop(cmid){this.id=cmid,this.reactive.isEditing&&this.reactive.supportComponents&&(this.dragdrop=new _reactive.DragDrop(this),this.classes=this.dragdrop.getClasses())}destroy(){void 0!==this.dragdrop&&this.dragdrop.unregister()}setDraggable(value){var _this$dragdrop;null===(_this$dragdrop=this.dragdrop)||void 0===_this$dragdrop||_this$dragdrop.setDraggable(value)}dragStart(dropdata){this.reactive.dispatch("cmDrag",[dropdata.id],!0)}dragEnd(dropdata){this.reactive.dispatch("cmDrag",[dropdata.id],!1)}getDraggableData(){return this.reactive.getExporter().cmDraggableData(this.reactive.state,this.id)}validateDropData(dropdata){return"cm"===(null==dropdata?void 0:dropdata.type)}showDropZone(dropdata){dropdata.nextcmid!=this.id&&dropdata.id!=this.id&&this.element.classList.add(this.classes.DROPUP)}hideDropZone(){this.element.classList.remove(this.classes.DROPUP)}drop(dropdata,event){if(dropdata.id!=this.id&&dropdata.nextcmid!=this.id){const mutation=event.altKey?"cmDuplicate":"cmMove";this.reactive.dispatch(mutation,[dropdata.id],null,this.id)}}}return _exports.default=_default,_exports.default}));
|
||||
|
||||
//# sourceMappingURL=dndcmitem.min.js.map
|
File diff suppressed because one or more lines are too long
|
@ -10,6 +10,6 @@ define("core_courseformat/local/courseeditor/dndsectionitem",["exports","core/re
|
|||
* @copyright 2021 Ferran Recio <ferran@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class _default extends _reactive.BaseComponent{configDragDrop(sectionid,state,fullregion){this.id=sectionid,void 0===this.section&&(this.section=state.section.get(this.id)),void 0===this.course&&(this.course=state.course),this.section.number>0&&(this.getDraggableData=this._getDraggableData),this.fullregion=fullregion,this.reactive.isEditing&&this.reactive.supportComponents&&(this.dragdrop=new _reactive.DragDrop(this),this.classes=this.dragdrop.getClasses())}destroy(){void 0!==this.dragdrop&&this.dragdrop.unregister()}dragStart(dropdata){this.reactive.dispatch("sectionDrag",[dropdata.id],!0)}dragEnd(dropdata){this.reactive.dispatch("sectionDrag",[dropdata.id],!1)}_getDraggableData(){return this.reactive.getExporter().sectionDraggableData(this.reactive.state,this.id)}validateDropData(dropdata){if("cm"===(null==dropdata?void 0:dropdata.type)){var _this$section;const firstcmid=null===(_this$section=this.section)||void 0===_this$section?void 0:_this$section.cmlist[0];return dropdata.id!==firstcmid}return!1}showDropZone(){this.element.classList.add(this.classes.DROPZONE)}hideDropZone(){this.element.classList.remove(this.classes.DROPZONE)}drop(dropdata,event){if("cm"==dropdata.type){var _this$section2;const mutation=event.altKey?"cmDuplicate":"cmMove";this.reactive.dispatch(mutation,[dropdata.id],this.id,null===(_this$section2=this.section)||void 0===_this$section2?void 0:_this$section2.cmlist[0])}}}return _exports.default=_default,_exports.default}));
|
||||
class _default extends _reactive.BaseComponent{configDragDrop(sectionid,state,fullregion){this.id=sectionid,void 0===this.section&&(this.section=state.section.get(this.id)),void 0===this.course&&(this.course=state.course),this.section.number>0&&(this.getDraggableData=this._getDraggableData),this.fullregion=fullregion,this.reactive.isEditing&&this.reactive.supportComponents&&(this.dragdrop=new _reactive.DragDrop(this),this.classes=this.dragdrop.getClasses())}destroy(){void 0!==this.dragdrop&&this.dragdrop.unregister()}setDraggable(value){var _this$dragdrop;this.getDraggableData&&(null===(_this$dragdrop=this.dragdrop)||void 0===_this$dragdrop||_this$dragdrop.setDraggable(value))}dragStart(dropdata){this.reactive.dispatch("sectionDrag",[dropdata.id],!0)}dragEnd(dropdata){this.reactive.dispatch("sectionDrag",[dropdata.id],!1)}_getDraggableData(){return this.reactive.getExporter().sectionDraggableData(this.reactive.state,this.id)}validateDropData(dropdata){if("cm"===(null==dropdata?void 0:dropdata.type)){var _this$section;const firstcmid=null===(_this$section=this.section)||void 0===_this$section?void 0:_this$section.cmlist[0];return dropdata.id!==firstcmid}return!1}showDropZone(){this.element.classList.add(this.classes.DROPZONE)}hideDropZone(){this.element.classList.remove(this.classes.DROPZONE)}drop(dropdata,event){if("cm"==dropdata.type){var _this$section2;const mutation=event.altKey?"cmDuplicate":"cmMove";this.reactive.dispatch(mutation,[dropdata.id],this.id,null===(_this$section2=this.section)||void 0===_this$section2?void 0:_this$section2.cmlist[0])}}}return _exports.default=_default,_exports.default}));
|
||||
|
||||
//# sourceMappingURL=dndsectionitem.min.js.map
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -393,6 +393,36 @@ export default class extends BaseComponent {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a toggle cm selection.
|
||||
*
|
||||
* @param {Element} target the dispatch action element
|
||||
*/
|
||||
async _requestToggleSelectionCm(target) {
|
||||
const cmId = target.dataset.id;
|
||||
if (!cmId) {
|
||||
return;
|
||||
}
|
||||
const value = target.checked ?? false;
|
||||
const mutation = (value) ? 'cmSelect' : 'cmUnselect';
|
||||
this.reactive.dispatch(mutation, [cmId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a toggle section selection.
|
||||
*
|
||||
* @param {Element} target the dispatch action element
|
||||
*/
|
||||
async _requestToggleSelectionSection(target) {
|
||||
const sectionId = target.dataset.id;
|
||||
if (!sectionId) {
|
||||
return;
|
||||
}
|
||||
const value = target.checked ?? false;
|
||||
const mutation = (value) ? 'sectionSelect' : 'sectionUnselect';
|
||||
this.reactive.dispatch(mutation, [sectionId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic mutation action helper.
|
||||
*
|
||||
|
|
115
course/format/amd/src/local/content/bulkedittoggler.js
Normal file
115
course/format/amd/src/local/content/bulkedittoggler.js
Normal file
|
@ -0,0 +1,115 @@
|
|||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* The bulk editor toggler button control.
|
||||
*
|
||||
* @module core_courseformat/local/content/bulkedittoggler
|
||||
* @class core_courseformat/local/content/bulkedittoggler
|
||||
* @copyright 2023 Ferran Recio <ferran@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
import {BaseComponent} from 'core/reactive';
|
||||
import {getCurrentCourseEditor} from 'core_courseformat/courseeditor';
|
||||
import Pending from 'core/pending';
|
||||
|
||||
export default class Component extends BaseComponent {
|
||||
|
||||
/**
|
||||
* Constructor hook.
|
||||
*/
|
||||
create() {
|
||||
// Optional component name for debugging.
|
||||
this.name = 'bulk_editor_toogler';
|
||||
// Default query selectors.
|
||||
this.selectors = {
|
||||
BODY: `body`,
|
||||
SELECTABLE: `[data-bulkcheckbox][data-is-selectable]`,
|
||||
};
|
||||
// Component css classes.
|
||||
this.classes = {
|
||||
HIDDEN: `d-none`,
|
||||
BULK: `bulkenabled`,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Static method to create a component instance from the mustache template.
|
||||
*
|
||||
* @param {string} target optional altentative DOM main element CSS selector
|
||||
* @param {object} selectors optional css selector overrides
|
||||
* @return {Component}
|
||||
*/
|
||||
static init(target, selectors) {
|
||||
return new this({
|
||||
element: document.querySelector(target),
|
||||
reactive: getCurrentCourseEditor(),
|
||||
selectors
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initial state ready method.
|
||||
*/
|
||||
stateReady() {
|
||||
// Capture completion events.
|
||||
this.addEventListener(
|
||||
this.element,
|
||||
'click',
|
||||
this._enableBulk
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Component watchers.
|
||||
*
|
||||
* @returns {Array} of watchers
|
||||
*/
|
||||
getWatchers() {
|
||||
return [
|
||||
{watch: `bulk.enabled:updated`, handler: this._refreshToggler},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a content section using the state information.
|
||||
*
|
||||
* @param {object} param
|
||||
* @param {Object} param.element details the update details (state.bulk in this case).
|
||||
*/
|
||||
_refreshToggler({element}) {
|
||||
this.element.classList.toggle(this.classes.HIDDEN, element.enabled ?? false);
|
||||
document.querySelector(this.selectors.BODY)?.classList.toggle(this.classes.BULK, element.enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch the enable bulk mutation.
|
||||
*
|
||||
* The enable bulk button is outside of the course content main div.
|
||||
* Because content/actions captures click events only in the course
|
||||
* content, this button needs to trigger the enable bulk mutation
|
||||
* by itself.
|
||||
*/
|
||||
_enableBulk() {
|
||||
const pendingToggle = new Pending(`courseformat/content:bulktoggle_on`);
|
||||
this.reactive.dispatch('bulkEnable', true);
|
||||
// Wait for a while and focus on the first checkbox.
|
||||
setTimeout(() => {
|
||||
document.querySelector(this.selectors.SELECTABLE)?.focus();
|
||||
pendingToggle.resolve();
|
||||
}, 150);
|
||||
}
|
||||
}
|
247
course/format/amd/src/local/content/bulkedittools.js
Normal file
247
course/format/amd/src/local/content/bulkedittools.js
Normal file
|
@ -0,0 +1,247 @@
|
|||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* The bulk editor tools bar.
|
||||
*
|
||||
* @module core_courseformat/local/content/bulkedittools
|
||||
* @class core_courseformat/local/content/bulkedittools
|
||||
* @copyright 2023 Ferran Recio <ferran@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
import {BaseComponent} from 'core/reactive';
|
||||
import {disableStickyFooter, enableStickyFooter} from 'core/sticky-footer';
|
||||
import {getCurrentCourseEditor} from 'core_courseformat/courseeditor';
|
||||
import {get_string as getString} from 'core/str';
|
||||
import Pending from 'core/pending';
|
||||
import {prefetchStrings} from 'core/prefetch';
|
||||
|
||||
// Load global strings.
|
||||
prefetchStrings(
|
||||
'core_courseformat',
|
||||
['bulkselection']
|
||||
);
|
||||
|
||||
export default class Component extends BaseComponent {
|
||||
|
||||
/**
|
||||
* Constructor hook.
|
||||
*/
|
||||
create() {
|
||||
// Optional component name for debugging.
|
||||
this.name = 'bulk_editor_tools';
|
||||
// Default query selectors.
|
||||
this.selectors = {
|
||||
ACTIONS: `[data-for="bulkaction"]`,
|
||||
ACTIONTOOL: `[data-for="bulkactions"] li`,
|
||||
CANCEL: `[data-for="bulkcancel"]`,
|
||||
COUNT: `[data-for='bulkcount']`,
|
||||
SELECTABLE: `[data-bulkcheckbox][data-is-selectable]`,
|
||||
SELECTALL: `[data-for="selectall"]`,
|
||||
BULKBTN: `[data-for="enableBulk"]`,
|
||||
};
|
||||
// Most classes will be loaded later by DndCmItem.
|
||||
this.classes = {
|
||||
HIDE: 'd-none',
|
||||
DISABLED: 'disabled',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Static method to create a component instance from the mustache template.
|
||||
*
|
||||
* @param {string} target optional altentative DOM main element CSS selector
|
||||
* @param {object} selectors optional css selector overrides
|
||||
* @return {Component}
|
||||
*/
|
||||
static init(target, selectors) {
|
||||
return new this({
|
||||
element: document.querySelector(target),
|
||||
reactive: getCurrentCourseEditor(),
|
||||
selectors
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initial state ready method.
|
||||
*/
|
||||
stateReady() {
|
||||
const cancelBtn = this.getElement(this.selectors.CANCEL);
|
||||
if (cancelBtn) {
|
||||
this.addEventListener(cancelBtn, 'click', this._cancelBulk);
|
||||
}
|
||||
const selectAll = this.getElement(this.selectors.SELECTALL);
|
||||
if (selectAll) {
|
||||
this.addEventListener(selectAll, 'change', this._selectAllClick);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Component watchers.
|
||||
*
|
||||
* @returns {Array} of watchers
|
||||
*/
|
||||
getWatchers() {
|
||||
return [
|
||||
{watch: `bulk.enabled:updated`, handler: this._refreshEnabled},
|
||||
{watch: `bulk:updated`, handler: this._refreshTools},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide and show the bulk edit tools.
|
||||
*
|
||||
* @param {object} param
|
||||
* @param {Object} param.element details the update details (state.bulk in this case).
|
||||
*/
|
||||
_refreshEnabled({element}) {
|
||||
if (element.enabled) {
|
||||
enableStickyFooter();
|
||||
} else {
|
||||
disableStickyFooter();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the tools depending on the current selection.
|
||||
*
|
||||
* @param {object} param the state watcher information
|
||||
* @param {Object} param.state the full state data.
|
||||
* @param {Object} param.element the affected element (bulk in this case).
|
||||
*/
|
||||
_refreshTools(param) {
|
||||
this._refreshSelectCount(param);
|
||||
this._refreshSelectAll(param);
|
||||
this._refreshActions(param);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the selection count.
|
||||
*
|
||||
* @param {object} param
|
||||
* @param {Object} param.element the affected element (bulk in this case).
|
||||
*/
|
||||
async _refreshSelectCount({element: bulk}) {
|
||||
const selectedCount = await getString('bulkselection', 'core_courseformat', bulk.selection.length);
|
||||
const selectedElement = this.getElement(this.selectors.COUNT);
|
||||
if (selectedElement) {
|
||||
selectedElement.innerHTML = selectedCount;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the select all element.
|
||||
*
|
||||
* @param {object} param
|
||||
* @param {Object} param.element the affected element (bulk in this case).
|
||||
*/
|
||||
_refreshSelectAll({element: bulk}) {
|
||||
const selectall = this.getElement(this.selectors.SELECTALL);
|
||||
if (!selectall) {
|
||||
return;
|
||||
}
|
||||
if (bulk.selectedType === '') {
|
||||
selectall.checked = false;
|
||||
selectall.disabled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
selectall.disabled = false;
|
||||
const maxSelection = document.querySelectorAll(this.selectors.SELECTABLE).length;
|
||||
selectall.checked = (bulk.selection.length == maxSelection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the visible action buttons depending on the selection type.
|
||||
*
|
||||
* @param {object} param
|
||||
* @param {Object} param.element the affected element (bulk in this case).
|
||||
*/
|
||||
_refreshActions({element: bulk}) {
|
||||
// By default, we show the cm options.
|
||||
const displayType = (bulk.selectedType == 'section') ? 'section' : 'cm';
|
||||
const enabled = (bulk.selectedType !== '');
|
||||
this.getElements(this.selectors.ACTIONS).forEach(action => {
|
||||
action.classList.toggle(this.classes.DISABLED, !enabled);
|
||||
|
||||
const actionTool = action.closest(this.selectors.ACTIONTOOL);
|
||||
const isHidden = (action.dataset.bulk != displayType);
|
||||
actionTool?.classList.toggle(this.classes.HIDE, isHidden);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel bulk handler.
|
||||
*/
|
||||
_cancelBulk() {
|
||||
const pending = new Pending(`courseformat/content:bulktoggle_off`);
|
||||
this.reactive.dispatch('bulkEnable', false);
|
||||
// Wait for a while and focus on enable bulk button.
|
||||
setTimeout(() => {
|
||||
document.querySelector(this.selectors.BULKBTN)?.focus();
|
||||
pending.resolve();
|
||||
}, 150);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select all elements click handler.
|
||||
* @param {Event} event
|
||||
*/
|
||||
_selectAllClick(event) {
|
||||
const target = event.target;
|
||||
const bulk = this.reactive.get('bulk');
|
||||
if (bulk.selectedType === '') {
|
||||
return;
|
||||
}
|
||||
if (!target.checked) {
|
||||
this._handleUnselectAll();
|
||||
return;
|
||||
}
|
||||
this._handleSelectAll(bulk);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process unselect all elements.
|
||||
*/
|
||||
_handleUnselectAll() {
|
||||
const pending = new Pending(`courseformat/content:bulktUnselectAll`);
|
||||
// Re-enable bulk will clean the selection and the selection type.
|
||||
this.reactive.dispatch('bulkEnable', true);
|
||||
// Wait for a while and focus on the first checkbox.
|
||||
setTimeout(() => {
|
||||
document.querySelector(this.selectors.SELECTABLE)?.focus();
|
||||
pending.resolve();
|
||||
}, 150);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a select all selectable elements.
|
||||
* @param {Object} bulk the state bulk data
|
||||
* @param {String} bulk.selectedType the current selected type (section/cm)
|
||||
*/
|
||||
_handleSelectAll(bulk) {
|
||||
const selectableIds = [];
|
||||
const selectables = document.querySelectorAll(this.selectors.SELECTABLE);
|
||||
if (selectables.length == 0) {
|
||||
return;
|
||||
}
|
||||
selectables.forEach(selectable => {
|
||||
selectableIds.push(selectable.dataset.id);
|
||||
});
|
||||
const mutation = (bulk.selectedType === 'cm') ? 'cmSelect' : 'sectionSelect';
|
||||
this.reactive.dispatch(mutation, selectableIds);
|
||||
}
|
||||
}
|
|
@ -36,11 +36,17 @@ export default class extends DndCmItem {
|
|||
this.name = 'content_section_cmitem';
|
||||
// Default query selectors.
|
||||
this.selectors = {
|
||||
BULKSELECT: `[data-for='cmBulkSelect']`,
|
||||
BULKCHECKBOX: `[data-bulkcheckbox]`,
|
||||
CARD: `.activity-item`,
|
||||
DRAGICON: `.editing_move`,
|
||||
INPLACEEDITABLE: `[data-inplaceeditablelink]`,
|
||||
};
|
||||
// Most classes will be loaded later by DndCmItem.
|
||||
this.classes = {
|
||||
LOCKED: 'editinprogress',
|
||||
HIDE: 'd-none',
|
||||
SELECTED: 'selected',
|
||||
};
|
||||
// We need our id to watch specific events.
|
||||
this.id = this.element.dataset.id;
|
||||
|
@ -48,10 +54,13 @@ export default class extends DndCmItem {
|
|||
|
||||
/**
|
||||
* Initial state ready method.
|
||||
* @param {Object} state the state data
|
||||
*/
|
||||
stateReady() {
|
||||
stateReady(state) {
|
||||
this.configDragDrop(this.id);
|
||||
this.getElement(this.selectors.DRAGICON)?.classList.add(this.classes.DRAGICON);
|
||||
this._refreshBulk({state});
|
||||
this.addEventListener(this.element, 'click', this._handleBulkModeClick);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -63,6 +72,7 @@ export default class extends DndCmItem {
|
|||
return [
|
||||
{watch: `cm[${this.id}]:deleted`, handler: this.unregister},
|
||||
{watch: `cm[${this.id}]:updated`, handler: this._refreshCm},
|
||||
{watch: `bulk:updated`, handler: this._refreshBulk},
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -78,4 +88,101 @@ export default class extends DndCmItem {
|
|||
this.element.classList.toggle(this.classes.LOCKED, element.locked ?? false);
|
||||
this.locked = element.locked;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the bulk editing interface.
|
||||
*
|
||||
* @param {object} param
|
||||
* @param {Object} param.state the state data
|
||||
*/
|
||||
_refreshBulk({state}) {
|
||||
const bulk = state.bulk;
|
||||
// For now, dragging elements in bulk is not possible.
|
||||
this.setDraggable(!bulk.enabled);
|
||||
|
||||
this.getElement(this.selectors.BULKSELECT)?.classList.toggle(this.classes.HIDE, !bulk.enabled);
|
||||
|
||||
const disabled = !this._isCmBulkEnabled(bulk);
|
||||
const selected = this._isSelected(bulk);
|
||||
this._refreshActivityCard(bulk, selected);
|
||||
this._setCheckboxValue(selected, disabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the activity card depending on the bulk selection.
|
||||
*
|
||||
* @param {Object} bulk the current bulk state data
|
||||
* @param {Boolean} selected if the activity is selected.
|
||||
*/
|
||||
_refreshActivityCard(bulk, selected) {
|
||||
this.getElement(this.selectors.INPLACEEDITABLE)?.classList.toggle(this.classes.HIDE, bulk.enabled);
|
||||
this.getElement(this.selectors.CARD)?.classList.toggle(this.classes.SELECTED, selected);
|
||||
this.element.classList.toggle(this.classes.SELECTED, selected);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify the checkbox element.
|
||||
* @param {Boolean} checked the new checked value
|
||||
* @param {Boolean} disabled the new disabled value
|
||||
*/
|
||||
_setCheckboxValue(checked, disabled) {
|
||||
const checkbox = this.getElement(this.selectors.BULKCHECKBOX);
|
||||
if (!checkbox) {
|
||||
return;
|
||||
}
|
||||
checkbox.checked = checked;
|
||||
checkbox.disabled = disabled;
|
||||
// Is selectable is used to easily scan the page for bulk checkboxes.
|
||||
if (disabled) {
|
||||
checkbox.removeAttribute('data-is-selectable');
|
||||
} else {
|
||||
checkbox.dataset.isSelectable = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the activity card click in bulk mode.
|
||||
* @param {Event} event the click event
|
||||
*/
|
||||
_handleBulkModeClick(event) {
|
||||
const selectElement = event.target.closest(this.selectors.BULKSELECT);
|
||||
if (selectElement) {
|
||||
// The select element checkbox execute a normal content action as
|
||||
// any regular action button. This is because the chengechecker module
|
||||
// is sniffing any form element and will with the checked value
|
||||
// changing it twice.
|
||||
return;
|
||||
}
|
||||
const bulk = this.reactive.get('bulk');
|
||||
if (!this._isCmBulkEnabled(bulk)) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
const mutation = (this._isSelected(bulk)) ? 'cmUnselect' : 'cmSelect';
|
||||
this.reactive.dispatch(mutation, [this.id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if cm bulk selection is available.
|
||||
* @param {Object} bulk the current state bulk attribute
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
_isCmBulkEnabled(bulk) {
|
||||
if (!bulk.enabled) {
|
||||
return false;
|
||||
}
|
||||
return (bulk.selectedType === '' || bulk.selectedType === 'cm');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the cm id is part of the current bulk selection.
|
||||
* @param {Object} bulk the current state bulk attribute
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
_isSelected(bulk) {
|
||||
if (bulk.selectedType !== 'cm') {
|
||||
return false;
|
||||
}
|
||||
return bulk.selection.includes(this.id);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,8 +36,16 @@ export default class extends DndSectionItem {
|
|||
create(descriptor) {
|
||||
// Optional component name for debugging.
|
||||
this.name = 'content_section_header';
|
||||
// We need our id to watch specific events.
|
||||
|
||||
// Default query selectors.
|
||||
this.selectors = {
|
||||
ACTIONSMENU: `.section_action_menu`,
|
||||
BULKSELECT: `[data-for='sectionBulkSelect']`,
|
||||
BULKCHECKBOX: `[data-bulkcheckbox]`,
|
||||
};
|
||||
this.classes = {
|
||||
HIDE: 'd-none',
|
||||
SELECTED: 'selected',
|
||||
};
|
||||
// Get main info from the descriptor.
|
||||
this.id = descriptor.id;
|
||||
this.section = descriptor.section;
|
||||
|
@ -52,5 +60,91 @@ export default class extends DndSectionItem {
|
|||
*/
|
||||
stateReady(state) {
|
||||
this.configDragDrop(this.id, state, this.fullregion);
|
||||
this._refreshBulk({state});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Component watchers.
|
||||
*
|
||||
* @returns {Array} of watchers
|
||||
*/
|
||||
getWatchers() {
|
||||
return [
|
||||
{watch: `bulk:updated`, handler: this._refreshBulk},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a bulk options.
|
||||
*
|
||||
* @param {object} param
|
||||
* @param {Object} param.state the state data
|
||||
*/
|
||||
_refreshBulk({state}) {
|
||||
const bulk = state.bulk;
|
||||
if (!this._isSectionBulkEditable()) {
|
||||
return;
|
||||
}
|
||||
// For now, dragging elements in bulk is not possible.
|
||||
this.setDraggable(!bulk.enabled);
|
||||
this.getElement(this.selectors.BULKSELECT)?.classList.toggle(this.classes.HIDE, !bulk.enabled);
|
||||
|
||||
const disabled = !this._isSectionBulkEnabled(bulk);
|
||||
const selected = this._isSelected(bulk);
|
||||
this.element.classList.toggle(this.classes.SELECTED, selected);
|
||||
this._setCheckboxValue(selected, disabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify the checkbox element.
|
||||
* @param {Boolean} checked the new checked value
|
||||
* @param {Boolean} disabled the new disabled value
|
||||
*/
|
||||
_setCheckboxValue(checked, disabled) {
|
||||
const checkbox = this.getElement(this.selectors.BULKCHECKBOX);
|
||||
if (!checkbox) {
|
||||
return;
|
||||
}
|
||||
checkbox.checked = checked;
|
||||
checkbox.disabled = disabled;
|
||||
// Is selectable is used to easily scan the page for bulk checkboxes.
|
||||
if (disabled) {
|
||||
checkbox.removeAttribute('data-is-selectable');
|
||||
} else {
|
||||
checkbox.dataset.isSelectable = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if cm bulk selection is available.
|
||||
* @param {Object} bulk the current state bulk attribute
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
_isSectionBulkEnabled(bulk) {
|
||||
if (!bulk.enabled) {
|
||||
return false;
|
||||
}
|
||||
return (bulk.selectedType === '' || bulk.selectedType === 'section');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the section is bulk editable.
|
||||
* @return {Boolean}
|
||||
*/
|
||||
_isSectionBulkEditable() {
|
||||
const section = this.reactive.get('section', this.id);
|
||||
return section?.bulkeditable ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the cm id is part of the current bulk selection.
|
||||
* @param {Object} bulk the current state bulk attribute
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
_isSelected(bulk) {
|
||||
if (bulk.selectedType !== 'section') {
|
||||
return false;
|
||||
}
|
||||
return bulk.selection.includes(this.id);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -103,6 +103,13 @@ export default class extends Reactive {
|
|||
return;
|
||||
}
|
||||
|
||||
// The bulk editing only applies to the frontend and the state data is not created in the backend.
|
||||
stateData.bulk = {
|
||||
enabled: false,
|
||||
selectedType: '',
|
||||
selection: [],
|
||||
};
|
||||
|
||||
this.setInitialState(stateData);
|
||||
|
||||
// In editing mode, the session cache is considered dirty always.
|
||||
|
|
|
@ -56,6 +56,15 @@ export default class extends BaseComponent {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable or disable the draggable property.
|
||||
*
|
||||
* @param {bool} value the new draggable value
|
||||
*/
|
||||
setDraggable(value) {
|
||||
this.dragdrop?.setDraggable(value);
|
||||
}
|
||||
|
||||
// Drag and drop methods.
|
||||
|
||||
/**
|
||||
|
|
|
@ -71,6 +71,17 @@ export default class extends BaseComponent {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable or disable the draggable property.
|
||||
*
|
||||
* @param {bool} value the new draggable value
|
||||
*/
|
||||
setDraggable(value) {
|
||||
if (this.getDraggableData) {
|
||||
this.dragdrop?.setDraggable(value);
|
||||
}
|
||||
}
|
||||
|
||||
// Drag and drop methods.
|
||||
|
||||
/**
|
||||
|
|
|
@ -479,6 +479,126 @@ export default class {
|
|||
return collapsedSectionIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable/disable bulk editing.
|
||||
*
|
||||
* Note: reenabling the bulk will clean the current selection.
|
||||
*
|
||||
* @param {StateManager} stateManager the current state manager
|
||||
* @param {Boolean} enabled the new bulk state.
|
||||
*/
|
||||
bulkEnable(stateManager, enabled) {
|
||||
const state = stateManager.state;
|
||||
stateManager.setReadOnly(false);
|
||||
state.bulk.enabled = enabled;
|
||||
state.bulk.selectedType = '';
|
||||
state.bulk.selection = [];
|
||||
stateManager.setReadOnly(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the current selection.
|
||||
* @param {StateManager} stateManager the current state manager
|
||||
*/
|
||||
bulkReset(stateManager) {
|
||||
const state = stateManager.state;
|
||||
stateManager.setReadOnly(false);
|
||||
state.bulk.selectedType = '';
|
||||
state.bulk.selection = [];
|
||||
stateManager.setReadOnly(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select a list of cms.
|
||||
* @param {StateManager} stateManager the current state manager
|
||||
* @param {array} cmIds the list of cm ids
|
||||
*/
|
||||
cmSelect(stateManager, cmIds) {
|
||||
this._addIdsToSelection(stateManager, 'cm', cmIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unselect a list of cms.
|
||||
* @param {StateManager} stateManager the current state manager
|
||||
* @param {array} cmIds the list of cm ids
|
||||
*/
|
||||
cmUnselect(stateManager, cmIds) {
|
||||
this._removeIdsFromSelection(stateManager, 'cm', cmIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select a list of sections.
|
||||
* @param {StateManager} stateManager the current state manager
|
||||
* @param {array} sectionIds the list of cm ids
|
||||
*/
|
||||
sectionSelect(stateManager, sectionIds) {
|
||||
this._addIdsToSelection(stateManager, 'section', sectionIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unselect a list of sections.
|
||||
* @param {StateManager} stateManager the current state manager
|
||||
* @param {array} sectionIds the list of cm ids
|
||||
*/
|
||||
sectionUnselect(stateManager, sectionIds) {
|
||||
this._removeIdsFromSelection(stateManager, 'section', sectionIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add some ids to the current bulk selection.
|
||||
* @param {StateManager} stateManager the current state manager
|
||||
* @param {String} typeName the type name (section/cm)
|
||||
* @param {array} ids the list of ids
|
||||
*/
|
||||
_addIdsToSelection(stateManager, typeName, ids) {
|
||||
const bulk = stateManager.state.bulk;
|
||||
if (!bulk?.enabled) {
|
||||
throw new Error(`Bulk is not enabled`);
|
||||
}
|
||||
if (bulk?.selectedType !== "" && bulk?.selectedType !== typeName) {
|
||||
throw new Error(`Cannot add ${typeName} to the current selection`);
|
||||
}
|
||||
|
||||
// Stored ids are strings for compatability with HTML data attributes.
|
||||
ids = ids.map(value => value.toString());
|
||||
|
||||
stateManager.setReadOnly(false);
|
||||
bulk.selectedType = typeName;
|
||||
const newSelection = new Set([...bulk.selection, ...ids]);
|
||||
bulk.selection = [...newSelection];
|
||||
stateManager.setReadOnly(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove some ids to the current bulk selection.
|
||||
*
|
||||
* The method resets the selection type if the current selection is empty.
|
||||
*
|
||||
* @param {StateManager} stateManager the current state manager
|
||||
* @param {String} typeName the type name (section/cm)
|
||||
* @param {array} ids the list of ids
|
||||
*/
|
||||
_removeIdsFromSelection(stateManager, typeName, ids) {
|
||||
const bulk = stateManager.state.bulk;
|
||||
if (!bulk?.enabled) {
|
||||
throw new Error(`Bulk is not enabled`);
|
||||
}
|
||||
if (bulk?.selectedType !== "" && bulk?.selectedType !== typeName) {
|
||||
throw new Error(`Cannot remove ${typeName} from the current selection`);
|
||||
}
|
||||
|
||||
// Stored ids are strings for compatability with HTML data attributes.
|
||||
ids = ids.map(value => value.toString());
|
||||
|
||||
stateManager.setReadOnly(false);
|
||||
const IdsToFilter = new Set(ids);
|
||||
bulk.selection = bulk.selection.filter(current => !IdsToFilter.has(current));
|
||||
if (bulk.selection.length === 0) {
|
||||
bulk.selectedType = '';
|
||||
}
|
||||
stateManager.setReadOnly(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get updated state data related to some cm ids.
|
||||
*
|
||||
|
|
|
@ -54,6 +54,9 @@ class content implements named_templatable, renderable {
|
|||
/** @var string section selector class name */
|
||||
protected $sectionselectorclass;
|
||||
|
||||
/** @var string bulk editor bar toolbox */
|
||||
protected $bulkedittoolsclass;
|
||||
|
||||
/** @var bool if uses add section */
|
||||
protected $hasaddsection = true;
|
||||
|
||||
|
@ -70,6 +73,7 @@ class content implements named_templatable, renderable {
|
|||
$this->addsectionclass = $format->get_output_classname('content\\addsection');
|
||||
$this->sectionnavigationclass = $format->get_output_classname('content\\sectionnavigation');
|
||||
$this->sectionselectorclass = $format->get_output_classname('content\\sectionselector');
|
||||
$this->bulkedittoolsclass = $format->get_output_classname('content\\bulkedittools');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -117,6 +121,11 @@ class content implements named_templatable, renderable {
|
|||
$data->numsections = $addsection->export_for_template($output);
|
||||
}
|
||||
|
||||
if ($format->show_editor()) {
|
||||
$bulkedittools = new $this->bulkedittoolsclass($format);
|
||||
$data->bulkedittools = $bulkedittools->export_for_template($output);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_courseformat\output\local\content;
|
||||
|
||||
use core\output\named_templatable;
|
||||
use core_courseformat\base as course_format;
|
||||
use core_courseformat\output\local\courseformat_named_templatable;
|
||||
use renderable;
|
||||
|
||||
/**
|
||||
* Course bulk edit mode toggler button.
|
||||
*
|
||||
* @package core_courseformat
|
||||
* @copyright 2023 Ferran Recio <ferran@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class bulkedittoggler implements named_templatable, renderable {
|
||||
use courseformat_named_templatable;
|
||||
|
||||
/** @var core_courseformat\base the course format class */
|
||||
protected $format;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param course_format $format the course format
|
||||
*/
|
||||
public function __construct(course_format $format) {
|
||||
$this->format = $format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export this data so it can be used as the context for a mustache template (core/inplace_editable).
|
||||
*
|
||||
* @param renderer_base $output typically, the renderer that's calling this function
|
||||
* @return stdClass data context for a mustache template
|
||||
*/
|
||||
public function export_for_template(\renderer_base $output) {
|
||||
$format = $this->format;
|
||||
$course = $format->get_course();
|
||||
|
||||
$data = (object)[
|
||||
'id' => $course->id,
|
||||
];
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
101
course/format/classes/output/local/content/bulkedittools.php
Normal file
101
course/format/classes/output/local/content/bulkedittools.php
Normal file
|
@ -0,0 +1,101 @@
|
|||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_courseformat\output\local\content;
|
||||
|
||||
use core\output\named_templatable;
|
||||
use core_courseformat\base as course_format;
|
||||
use core_courseformat\output\local\courseformat_named_templatable;
|
||||
use renderable;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Contains the bulk editor tools bar.
|
||||
*
|
||||
* @package core_courseformat
|
||||
* @copyright 2023 Ferran Recio <ferran@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class bulkedittools implements named_templatable, renderable {
|
||||
use courseformat_named_templatable;
|
||||
|
||||
/** @var core_courseformat\base the course format class */
|
||||
protected $format;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param course_format $format the course format
|
||||
*/
|
||||
public function __construct(course_format $format) {
|
||||
$this->format = $format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export this data so it can be used as the context for a mustache template (core/inplace_editable).
|
||||
*
|
||||
* @param renderer_base $output typically, the renderer that's calling this function
|
||||
* @return stdClass data context for a mustache template
|
||||
*/
|
||||
public function export_for_template(\renderer_base $output): stdClass {
|
||||
$format = $this->format;
|
||||
$course = $format->get_course();
|
||||
|
||||
$data = (object)[
|
||||
'id' => $course->id,
|
||||
'actions' => $this->get_toolbar_actions(),
|
||||
];
|
||||
$data->hasactions = !empty($data->actions);
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the toolbar actions.
|
||||
* @return array the array of buttons
|
||||
*/
|
||||
protected function get_toolbar_actions(): array {
|
||||
return array_merge(
|
||||
array_values($this->section_control_items()),
|
||||
array_values($this->cm_control_items()),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the bulk edit control items of a course module.
|
||||
*
|
||||
* Format plugins can override the method to add or remove elements
|
||||
* from the toolbar.
|
||||
*
|
||||
* @return array of edit control items
|
||||
*/
|
||||
protected function cm_control_items(): array {
|
||||
$controls = [];
|
||||
return $controls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the bulk edit control items of a section.
|
||||
*
|
||||
* Format plugins can override the method to add or remove elements
|
||||
* from the toolbar.
|
||||
*
|
||||
* @return array of edit control items
|
||||
*/
|
||||
protected function section_control_items(): array {
|
||||
$controls = [];
|
||||
return $controls;
|
||||
}
|
||||
}
|
|
@ -108,6 +108,7 @@ class cm implements named_templatable, renderable {
|
|||
'activityname' => $mod->get_formatted_name(),
|
||||
'textclasses' => $displayoptions['textclasses'],
|
||||
'classlist' => [],
|
||||
'cmid' => $mod->id,
|
||||
];
|
||||
|
||||
// Add partial data segments.
|
||||
|
|
|
@ -88,6 +88,7 @@ class section implements renderable {
|
|||
'indexcollapsed' => $indexcollapsed,
|
||||
'contentcollapsed' => $contentcollapsed,
|
||||
'hasrestrictions' => $this->get_has_restrictions(),
|
||||
'bulkeditable' => $this->is_bulk_editable(),
|
||||
];
|
||||
|
||||
if (empty($modinfo->sections[$section->section])) {
|
||||
|
@ -104,6 +105,15 @@ class section implements renderable {
|
|||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the section can be selected for bulk editing.
|
||||
* @return bool if the section can be edited in bulk
|
||||
*/
|
||||
protected function is_bulk_editable(): bool {
|
||||
$section = $this->section;
|
||||
return ($section->section != 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the section has a restrictions icon displayed or not.
|
||||
*
|
||||
|
|
|
@ -207,6 +207,20 @@ abstract class section_renderer extends core_course_renderer {
|
|||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the enable bulk editing button.
|
||||
* @param course_format $format the course format
|
||||
* @return string|null the enable bulk button HTML (or null if no bulk available).
|
||||
*/
|
||||
public function bulk_editing_button(course_format $format): ?string {
|
||||
if (!$format->show_editor() || !$format->supports_components()) {
|
||||
return null;
|
||||
}
|
||||
$widgetclass = $format->get_output_classname('content\\bulkedittoggler');
|
||||
$widget = new $widgetclass($format);
|
||||
return $this->render($widget);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the edit control action menu
|
||||
*
|
||||
|
|
|
@ -33,8 +33,10 @@
|
|||
"hasname": "true"
|
||||
},
|
||||
"id": 3,
|
||||
"cmid": 3,
|
||||
"module": "forum",
|
||||
"extraclasses": "newmessages"
|
||||
"extraclasses": "newmessages",
|
||||
"anchor": "module-3"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -61,9 +63,11 @@
|
|||
"cmname": "<a class=\"aalink\" href=\"#\"><span class=\"instancename\">Another forum</span></a>",
|
||||
"hasname": "true"
|
||||
},
|
||||
"id": 3,
|
||||
"id": 4,
|
||||
"cmid": 4,
|
||||
"module": "forum",
|
||||
"extraclasses": "newmessages"
|
||||
"extraclasses": "newmessages",
|
||||
"anchor": "module-4"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -90,8 +94,10 @@
|
|||
"hasname": "true"
|
||||
},
|
||||
"id": 5,
|
||||
"cmid": 5,
|
||||
"module": "forum",
|
||||
"extraclasses": "newmessages"
|
||||
"extraclasses": "newmessages",
|
||||
"anchor": "module-5"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -129,8 +135,8 @@
|
|||
},
|
||||
"sectionreturn": 1,
|
||||
"singlesection": {
|
||||
"num": 1,
|
||||
"id": 35,
|
||||
"num": 5,
|
||||
"id": 37,
|
||||
"header": {
|
||||
"name": "Single Section Example",
|
||||
"url": "#"
|
||||
|
@ -143,9 +149,11 @@
|
|||
"cmname": "<a class=\"aalink\" href=\"#\"><span class=\"instancename\">Assign example</span></a>",
|
||||
"hasname": "true"
|
||||
},
|
||||
"id": 4,
|
||||
"id": 6,
|
||||
"cmid": 6,
|
||||
"module": "assign",
|
||||
"extraclasses": ""
|
||||
"extraclasses": "",
|
||||
"anchor": "module-6"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -199,6 +207,11 @@
|
|||
{{> core_courseformat/local/content/addsection}}
|
||||
{{/ core_courseformat/local/content/addsection}}
|
||||
{{/numsections}}
|
||||
{{#bulkedittools}}
|
||||
{{$ core_courseformat/local/content/bulkedittools}}
|
||||
{{> core_courseformat/local/content/bulkedittools}}
|
||||
{{/ core_courseformat/local/content/bulkedittools}}
|
||||
{{/bulkedittools}}
|
||||
</div>
|
||||
{{#js}}
|
||||
require(['core_courseformat/local/content'], function(component) {
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
}
|
||||
}}
|
||||
{{#showaddsection}}
|
||||
<div class="mdl-left py-2 changenumsections">
|
||||
<div class="mdl-left py-2 changenumsections bulk-hidden">
|
||||
{{#increase}}
|
||||
<a href="{{{url}}}" class="increase-sections">
|
||||
{{#pix}}t/switch_plus, moodle, {{#str}} increasesections, moodle {{/str}}{{/pix}}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
{{!
|
||||
This file is part of Moodle - http://moodle.org/
|
||||
|
||||
Moodle is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Moodle is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@template core_courseformat/content/bulkedittoggler
|
||||
|
||||
Displays the bulk actions button in the page header.
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
}
|
||||
}}
|
||||
<button
|
||||
id="bulk-enable-{{uniqid}}"
|
||||
class="bulkEnable btn"
|
||||
data-for="enableBulk"
|
||||
>
|
||||
{{#str}} bulkedit, core_courseformat {{/str}} {{#pix}} i/edit, core {{/pix}}
|
||||
</button>
|
||||
{{#js}}
|
||||
require(['core_courseformat/local/content/bulkedittoggler'], function(component) {
|
||||
component.init('#bulk-enable-{{uniqid}}');
|
||||
});
|
||||
{{/js}}
|
92
course/format/templates/local/content/bulkedittools.mustache
Normal file
92
course/format/templates/local/content/bulkedittools.mustache
Normal file
|
@ -0,0 +1,92 @@
|
|||
{{!
|
||||
This file is part of Moodle - http://moodle.org/
|
||||
|
||||
Moodle is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Moodle is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@template core_courseformat/content/bulkedittoggler
|
||||
|
||||
Displays the bulk actions button in the page header.
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"id": 42,
|
||||
"hasactions": true,
|
||||
"actions": [
|
||||
{
|
||||
"icon": "i/delete",
|
||||
"action": "cmDelete",
|
||||
"name": "delete",
|
||||
"bulk": "cm",
|
||||
"title": "Delete activities"
|
||||
}
|
||||
]
|
||||
}
|
||||
}}
|
||||
{{< core/sticky_footer }}
|
||||
{{$ stickyclasses }} justify-content-between {{/ stickyclasses }}
|
||||
{{$ disable }} data-disable="true" {{/ disable }}
|
||||
{{$ extradata }} data-for="bulkedittools" {{/ extradata }}
|
||||
{{$ stickycontent }}
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="selectall" data-for="selectall" disabled>
|
||||
<label class="form-check-label" for="selectall">
|
||||
{{#str}} selectall {{/str}}
|
||||
</label>
|
||||
</div>
|
||||
<div data-for="bulktools">
|
||||
{{^hasactions}}
|
||||
{{#str}} nobulkaction, core_courseformat {{/str}}
|
||||
{{/hasactions}}
|
||||
{{#hasactions}}
|
||||
<ul class="actions nav" data-for="bulkactions">
|
||||
{{#actions}}
|
||||
<li class="nav-item">
|
||||
<button
|
||||
class="btn py-0 d-flex flex-column"
|
||||
data-action="{{action}}"
|
||||
data-bulk="{{bulk}}"
|
||||
data-for="bulkaction"
|
||||
{{#title}} title="{{title}}" {{/title}}
|
||||
>
|
||||
<div class="w-100 pl-2">{{#pix}}{{icon}}{{/pix}}</div>
|
||||
<div>{{name}}</div>
|
||||
</button>
|
||||
</li>
|
||||
{{/actions}}
|
||||
</ul>
|
||||
{{/hasactions}}
|
||||
</div>
|
||||
<div class="d-flex flex-column">
|
||||
<div class="ml-auto">
|
||||
<button
|
||||
class="btn pr-0 pb-0"
|
||||
data-action="bulkcancel"
|
||||
data-for="bulkcancel"
|
||||
title="{{#str}} bulkeditoff, core_courseformat {{/str}}"
|
||||
>
|
||||
{{#pix}} e/cancel, core {{/pix}}
|
||||
</button>
|
||||
</div>
|
||||
<div data-for="bulkcount">
|
||||
{{#str}} bulkselection, core_courseformat, 0 {{/str}}
|
||||
</div>
|
||||
</div>
|
||||
{{/ stickycontent }}
|
||||
{{/ core/sticky_footer }}
|
||||
{{#js}}
|
||||
require(['core_courseformat/local/content/bulkedittools'], function(component) {
|
||||
component.init('[data-for="bulkedittools"]');
|
||||
});
|
||||
{{/js}}
|
|
@ -57,13 +57,16 @@
|
|||
}
|
||||
}}
|
||||
{{#editing}}
|
||||
<div class="divider divider-plus" data-action="insert-before-{{activityname}}">
|
||||
<div class="divider divider-plus bulk-hidden" data-action="insert-before-{{activityname}}">
|
||||
{{> core_course/activitychooserbuttonactivity}}
|
||||
</div>
|
||||
{{/editing}}
|
||||
<div class="activity-item {{#modstealth}}hiddenactivity{{/modstealth}}{{!
|
||||
}}{{#modhiddenfromstudents}}hiddenactivity{{/modhiddenfromstudents}}{{!
|
||||
}}{{#modinline}}activityinline{{/modinline}}" data-activityname="{{activityname}}">
|
||||
{{$ core_courseformat/local/content/cm/bulkselect }}
|
||||
{{> core_courseformat/local/content/cm/bulkselect }}
|
||||
{{/ core_courseformat/local/content/cm/bulkselect }}
|
||||
{{!
|
||||
Place the actual content of the activity-item in a separate template to make it easier for other formats to add
|
||||
additional content to the activity wrapper.
|
||||
|
|
|
@ -85,7 +85,7 @@
|
|||
</div>
|
||||
|
||||
{{#controlmenu}}
|
||||
<div class="activity-actions align-self-start">
|
||||
<div class="activity-actions bulk-hidden align-self-start">
|
||||
{{$ core_courseformat/local/content/cm/controlmenu }}
|
||||
{{> core_courseformat/local/content/cm/controlmenu }}
|
||||
{{/ core_courseformat/local/content/cm/controlmenu }}
|
||||
|
|
39
course/format/templates/local/content/cm/bulkselect.mustache
Normal file
39
course/format/templates/local/content/cm/bulkselect.mustache
Normal file
|
@ -0,0 +1,39 @@
|
|||
{{!
|
||||
This file is part of Moodle - http://moodle.org/
|
||||
|
||||
Moodle is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Moodle is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@template core_courseformat/local/content/cm/bulkselect
|
||||
|
||||
Displays an activity bulk selector.
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"activityname": "Activity example",
|
||||
"cmid": 42
|
||||
}
|
||||
}}
|
||||
<div class="bulkselect d-none" data-for="cmBulkSelect">
|
||||
<input
|
||||
id="cmCheckbox{{cmid}}"
|
||||
type="checkbox"
|
||||
data-id="{{cmid}}"
|
||||
data-action="toggleSelectionCm"
|
||||
data-bulkcheckbox="1"
|
||||
/>
|
||||
<label class="sr-only" for="cmCheckbox{{cmid}}">
|
||||
{{#str}} selectcm, core_courseformat, {{activityname}}{{/str}}
|
||||
</label>
|
||||
</div>
|
|
@ -0,0 +1,39 @@
|
|||
{{!
|
||||
This file is part of Moodle - http://moodle.org/
|
||||
|
||||
Moodle is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Moodle is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@template core_courseformat/local/content/section/bulkselect
|
||||
|
||||
Displays an section bulk selector.
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"id": 35,
|
||||
"name": "Section title"
|
||||
}
|
||||
}}
|
||||
<div class="bulkselect align-self-center d-none" data-for="sectionBulkSelect">
|
||||
<input
|
||||
id="sectionCheckbox{{id}}"
|
||||
type="checkbox"
|
||||
data-id="{{id}}"
|
||||
data-action="toggleSelectionSection"
|
||||
data-bulkcheckbox="1"
|
||||
/>
|
||||
<label class="sr-only" for="sectionCheckbox{{id}}">
|
||||
{{#str}} selectsection, core_courseformat, {{name}}{{/str}}
|
||||
</label>
|
||||
</div>
|
|
@ -26,7 +26,7 @@
|
|||
}
|
||||
}}
|
||||
{{#hasmenu}}
|
||||
<div class="section_action_menu ml-auto" data-sectionid="{{id}}">
|
||||
<div class="section_action_menu bulk-hidden ml-auto" data-sectionid="{{id}}">
|
||||
{{{menu}}}
|
||||
</div>
|
||||
{{/hasmenu}}
|
||||
|
|
|
@ -36,6 +36,9 @@
|
|||
</h3>
|
||||
{{/headerdisplaymultipage}}
|
||||
{{^headerdisplaymultipage}}
|
||||
{{$ core_courseformat/local/content/section/bulkselect }}
|
||||
{{> core_courseformat/local/content/section/bulkselect }}
|
||||
{{/ core_courseformat/local/content/section/bulkselect }}
|
||||
{{#sitehome}}
|
||||
<h2 id="sectionid-{{id}}-title" class="sectionname">
|
||||
{{{title}}}
|
||||
|
|
118
course/format/tests/behat/bulk_select.feature
Normal file
118
course/format/tests/behat/bulk_select.feature
Normal file
|
@ -0,0 +1,118 @@
|
|||
@core @core_courseformat @show_editor @javascript
|
||||
Feature: Bulk activity and section selection.
|
||||
In order to edit the course activities
|
||||
As a teacher with capability 'moodle/course:manageactivities'
|
||||
I need to be able to bulk select activities or sections.
|
||||
|
||||
Background:
|
||||
Given the following "course" exists:
|
||||
| fullname | Course 1 |
|
||||
| shortname | C1 |
|
||||
| category | 0 |
|
||||
| numsections | 2 |
|
||||
And the following "activities" exist:
|
||||
| activity | name | intro | course | idnumber | section |
|
||||
| assign | Activity sample 1 | Test assignment description | C1 | sample1 | 1 |
|
||||
| assign | Activity sample 2 | Test assignment description | C1 | sample2 | 1 |
|
||||
| assign | Activity sample 3 | Test assignment description | C1 | sample3 | 2 |
|
||||
| assign | Activity sample 4 | Test assignment description | C1 | sample4 | 2 |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | 1 | teacher1@example.com |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
And I am on the "C1" "Course" page logged in as "teacher1"
|
||||
And I turn editing mode on
|
||||
|
||||
Scenario: Enable and disable bulk editing
|
||||
When I click on "Bulk edit" "button"
|
||||
Then I should see "0 selected" in the "sticky-footer" "region"
|
||||
And the focused element is "Select section Topic 1" "checkbox"
|
||||
And I click on "Close bulk edit" "button" in the "sticky-footer" "region"
|
||||
And "sticky-footer" "region" should not be visible
|
||||
And the focused element is "Bulk edit" "button"
|
||||
|
||||
Scenario: Selecting activities disable section selection
|
||||
Given I click on "Bulk edit" "button"
|
||||
And I should see "0 selected" in the "sticky-footer" "region"
|
||||
When I click on "Select activity Activity sample 1" "checkbox"
|
||||
And I should see "1 selected" in the "sticky-footer" "region"
|
||||
Then the "Select section Topic 1" "checkbox" should be disabled
|
||||
|
||||
Scenario: Selecting sections disable activity selection
|
||||
Given I click on "Bulk edit" "button"
|
||||
And I should see "0 selected" in the "sticky-footer" "region"
|
||||
When I click on "Select section Topic 1" "checkbox"
|
||||
And I should see "1 selected" in the "sticky-footer" "region"
|
||||
Then the "Select activity Activity sample 1" "checkbox" should be disabled
|
||||
|
||||
Scenario: Disable bulk resets the selection
|
||||
Given I click on "Bulk edit" "button"
|
||||
And I click on "Select activity Activity sample 1" "checkbox"
|
||||
And I click on "Select activity Activity sample 2" "checkbox"
|
||||
And I should see "2 selected" in the "sticky-footer" "region"
|
||||
When I click on "Close bulk edit" "button" in the "sticky-footer" "region"
|
||||
And I click on "Bulk edit" "button"
|
||||
Then I should see "0 selected" in the "sticky-footer" "region"
|
||||
|
||||
Scenario: Select all is disabled until an activity is selected
|
||||
Given I click on "Bulk edit" "button"
|
||||
And the "Select all" "checkbox" should be disabled
|
||||
When I click on "Select activity Activity sample 1" "checkbox"
|
||||
And I should see "1 selected" in the "sticky-footer" "region"
|
||||
Then the "Select all" "checkbox" should be enabled
|
||||
|
||||
Scenario: Select all is disabled until a section is selected
|
||||
Given I click on "Bulk edit" "button"
|
||||
And the "Select all" "checkbox" should be disabled
|
||||
When I click on "Select section Topic 1" "checkbox"
|
||||
And I should see "1 selected" in the "sticky-footer" "region"
|
||||
Then the "Select all" "checkbox" should be enabled
|
||||
|
||||
Scenario: Select all when an activity is selected will select all activities
|
||||
Given I click on "Bulk edit" "button"
|
||||
And I click on "Select activity Activity sample 1" "checkbox"
|
||||
And I should see "1 selected" in the "sticky-footer" "region"
|
||||
And the "Select all" "checkbox" should be enabled
|
||||
When I click on "Select all" "checkbox" in the "sticky-footer" "region"
|
||||
Then I should see "4 selected" in the "sticky-footer" "region"
|
||||
|
||||
Scenario: Select all when a section is selected will select all sections
|
||||
Given I click on "Bulk edit" "button"
|
||||
And I click on "Select section Topic 1" "checkbox"
|
||||
And I should see "1 selected" in the "sticky-footer" "region"
|
||||
And the "Select all" "checkbox" should be enabled
|
||||
When I click on "Select all" "checkbox" in the "sticky-footer" "region"
|
||||
Then I should see "2 selected" in the "sticky-footer" "region"
|
||||
|
||||
Scenario: Click on a select all with all sections selected unselects all sections
|
||||
Given I click on "Bulk edit" "button"
|
||||
And I click on "Select section Topic 1" "checkbox"
|
||||
And I click on "Select section Topic 2" "checkbox"
|
||||
And I should see "2 selected" in the "sticky-footer" "region"
|
||||
And the "Select all" "checkbox" should be enabled
|
||||
When I click on "Select all" "checkbox" in the "sticky-footer" "region"
|
||||
Then I should see "0 selected" in the "sticky-footer" "region"
|
||||
And the focused element is "Select section Topic 1" "checkbox"
|
||||
|
||||
Scenario: Click on a select all with all activity selected unselects all activities
|
||||
Given I click on "Bulk edit" "button"
|
||||
And I click on "Select activity Activity sample 1" "checkbox"
|
||||
And I click on "Select activity Activity sample 2" "checkbox"
|
||||
And I click on "Select activity Activity sample 3" "checkbox"
|
||||
And I click on "Select activity Activity sample 4" "checkbox"
|
||||
And I should see "4 selected" in the "sticky-footer" "region"
|
||||
And the "Select all" "checkbox" should be enabled
|
||||
When I click on "Select all" "checkbox" in the "sticky-footer" "region"
|
||||
Then I should see "0 selected" in the "sticky-footer" "region"
|
||||
And the focused element is "Select section Topic 1" "checkbox"
|
||||
|
||||
Scenario: Click an activity name in bulk mode select and unselects the activity
|
||||
Given I click on "Bulk edit" "button"
|
||||
And I should see "0 selected" in the "sticky-footer" "region"
|
||||
When I click on "Activity sample 1" "link" in the "Topic 1" "section"
|
||||
And I click on "Activity sample 2" "link" in the "Topic 1" "section"
|
||||
And I should see "2 selected" in the "sticky-footer" "region"
|
||||
Then I click on "Activity sample 1" "link" in the "Topic 1" "section"
|
||||
And I should see "1 selected" in the "sticky-footer" "region"
|
|
@ -10,6 +10,12 @@ Overview of this plugin type at http://docs.moodle.org/dev/Course_formats
|
|||
in the course content. Instead of using adhoc YUI methods and webservice, the new fragment methods are:
|
||||
- core_courseformat_output_fragment_cmitem
|
||||
- core_courseformat_output_fragment_section
|
||||
* New methods and outputs added for bulk editing (only available for formats compatible with reactive components):
|
||||
- Mutations for editing the bulk data: bulkEnable, bulkReset, cmSelect, cmUnselect, sectionSelect and sectionUnselect.
|
||||
- Output classes overridable by the plugins: content\bulkedittools, content\bulkedittoggler
|
||||
- Renderer method: core_courseformat\output\section_renderer::bulk_editing_button
|
||||
- New overridable checkboxes: content/cm/bulkselect.mustache and content/section/bulkselect.mustache
|
||||
* Plugins can use the CSS class "bulk-hidden" to hide elements when the bulk editing is enabled.
|
||||
|
||||
=== 4.1 ===
|
||||
* New \core_courseformat\stateupdates methods add_section_remove() and add_cm_remove() have been added to replace
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
"sectionreturn": 0
|
||||
}
|
||||
}}
|
||||
<button class="btn btn-link text-decoration-none section-modchooser section-modchooser-link activity-add d-flex align-items-center p-3 mb-3"
|
||||
<button class="btn btn-link text-decoration-none section-modchooser section-modchooser-link activity-add bulk-hidden d-flex align-items-center p-3 mb-3"
|
||||
data-action="open-chooser" data-sectionid="{{sectionid}}" data-sectionreturnid="{{sectionreturn}}">
|
||||
<span class="pluscontainer icon-no-margin icon-size-3 d-flex p-2 mr-3">
|
||||
{{#pix}} t/add, core {{/pix}}
|
||||
|
|
|
@ -137,7 +137,7 @@
|
|||
// Preload course format renderer before output starts.
|
||||
// This is a little hacky but necessary since
|
||||
// format.php is not included until after output starts
|
||||
$format->get_renderer($PAGE);
|
||||
$renderer = $format->get_renderer($PAGE);
|
||||
|
||||
if ($reset_user_allowed_editing) {
|
||||
// ugly hack
|
||||
|
@ -236,6 +236,12 @@
|
|||
$PAGE->set_title(get_string('coursetitle', 'moodle', array('course' => $course->fullname)));
|
||||
}
|
||||
|
||||
// Add bulk editing control.
|
||||
$bulkbutton = $renderer->bulk_editing_button($format);
|
||||
if (!empty($bulkbutton)) {
|
||||
$PAGE->add_header_action($bulkbutton);
|
||||
}
|
||||
|
||||
$PAGE->set_heading($course->fullname);
|
||||
echo $OUTPUT->header();
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue