mirror of
https://github.com/moodle/moodle.git
synced 2025-01-18 22:08:20 +01:00
MDL-76783 core_courseformat: add bulk editing interface
This commit is contained in:
parent
c9a8713539
commit
9930b7a2e6
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
@ -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
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);
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
||||
/**
|
||||
|
@ -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.
|
||||
|
@ -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) {
|
||||
|
@ -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}}
|
@ -64,6 +64,9 @@
|
||||
<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.
|
||||
|
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>
|
@ -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
|
||||
|
@ -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();
|
||||
|
||||
|
@ -22,6 +22,13 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
$string['bulkedit'] = 'Bulk edit';
|
||||
$string['bulkeditoff'] = 'Close bulk edit';
|
||||
$string['bulkcancel'] = 'Close bulk editing';
|
||||
$string['bulkselection'] = '{$a} selected';
|
||||
$string['courseindex'] = 'Course index';
|
||||
$string['nobulkaction'] = 'No bulk actions available';
|
||||
$string['preference:coursesectionspreferences'] = 'Section user preferences for course {$a}';
|
||||
$string['privacy:metadata:preference:coursesectionspreferences'] = 'Section user preferences like collapsed and expanded.';
|
||||
$string['selectcm'] = 'Select activity {$a}';
|
||||
$string['selectsection'] = 'Select section {$a}';
|
||||
|
@ -1500,7 +1500,8 @@ $activity-add-hover: theme-color-level('primary', -10) !default;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&:hover,
|
||||
&.selected {
|
||||
@include alert-variant($activity-item-hover, $activity-item-border, $activity-item-color);
|
||||
|
||||
.description .course-description-item,
|
||||
@ -1578,3 +1579,20 @@ $activity-add-hover: theme-color-level('primary', -10) !default;
|
||||
.bulkenabled .bulk-hidden {
|
||||
display: none !important; // stylelint-disable-line declaration-no-important
|
||||
}
|
||||
|
||||
.activity-item .bulkselect {
|
||||
position: absolute;
|
||||
left: -2rem;
|
||||
}
|
||||
|
||||
.course-section-header .bulkselect {
|
||||
left: -2rem;
|
||||
position: relative;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
.bulkenabled .course-content {
|
||||
margin-left: 2rem;
|
||||
}
|
||||
}
|
||||
|
@ -14949,17 +14949,19 @@ span.editinstructions {
|
||||
cursor: move; }
|
||||
.editing .activity-item .a {
|
||||
cursor: pointer; }
|
||||
.editing .activity-item:hover {
|
||||
.editing .activity-item:hover, .editing .activity-item.selected {
|
||||
color: #1d2125;
|
||||
background-color: #f5f9fc;
|
||||
border-color: #3584c9; }
|
||||
.editing .activity-item:hover hr {
|
||||
.editing .activity-item:hover hr, .editing .activity-item.selected hr {
|
||||
border-top-color: #3077b5; }
|
||||
.editing .activity-item:hover .alert-link {
|
||||
.editing .activity-item:hover .alert-link, .editing .activity-item.selected .alert-link {
|
||||
color: #070808; }
|
||||
.editing .activity-item:hover .description .course-description-item,
|
||||
.editing .activity-item:hover .activityiconcontainer,
|
||||
.editing .activity-item:hover .badge {
|
||||
.editing .activity-item:hover .badge, .editing .activity-item.selected .description .course-description-item,
|
||||
.editing .activity-item.selected .activityiconcontainer,
|
||||
.editing .activity-item.selected .badge {
|
||||
mix-blend-mode: multiply; }
|
||||
|
||||
.section .draggable .activity-item .dragicon {
|
||||
@ -15015,6 +15017,19 @@ span.editinstructions {
|
||||
.bulkenabled .bulk-hidden {
|
||||
display: none !important; }
|
||||
|
||||
.activity-item .bulkselect {
|
||||
position: absolute;
|
||||
left: -2rem; }
|
||||
|
||||
.course-section-header .bulkselect {
|
||||
left: -2rem;
|
||||
position: relative;
|
||||
width: 0; }
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
.bulkenabled .course-content {
|
||||
margin-left: 2rem; } }
|
||||
|
||||
/* Anchor link offset fix. This makes hash links scroll 60px down to account for the fixed header. */
|
||||
:target {
|
||||
scroll-margin-top: 70px; }
|
||||
|
@ -14949,17 +14949,19 @@ span.editinstructions {
|
||||
cursor: move; }
|
||||
.editing .activity-item .a {
|
||||
cursor: pointer; }
|
||||
.editing .activity-item:hover {
|
||||
.editing .activity-item:hover, .editing .activity-item.selected {
|
||||
color: #1d2125;
|
||||
background-color: #f5f9fc;
|
||||
border-color: #3584c9; }
|
||||
.editing .activity-item:hover hr {
|
||||
.editing .activity-item:hover hr, .editing .activity-item.selected hr {
|
||||
border-top-color: #3077b5; }
|
||||
.editing .activity-item:hover .alert-link {
|
||||
.editing .activity-item:hover .alert-link, .editing .activity-item.selected .alert-link {
|
||||
color: #070808; }
|
||||
.editing .activity-item:hover .description .course-description-item,
|
||||
.editing .activity-item:hover .activityiconcontainer,
|
||||
.editing .activity-item:hover .badge {
|
||||
.editing .activity-item:hover .badge, .editing .activity-item.selected .description .course-description-item,
|
||||
.editing .activity-item.selected .activityiconcontainer,
|
||||
.editing .activity-item.selected .badge {
|
||||
mix-blend-mode: multiply; }
|
||||
|
||||
.section .draggable .activity-item .dragicon {
|
||||
@ -15015,6 +15017,19 @@ span.editinstructions {
|
||||
.bulkenabled .bulk-hidden {
|
||||
display: none !important; }
|
||||
|
||||
.activity-item .bulkselect {
|
||||
position: absolute;
|
||||
left: -2rem; }
|
||||
|
||||
.course-section-header .bulkselect {
|
||||
left: -2rem;
|
||||
position: relative;
|
||||
width: 0; }
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
.bulkenabled .course-content {
|
||||
margin-left: 2rem; } }
|
||||
|
||||
/* Anchor link offset fix. This makes hash links scroll 60px down to account for the fixed header. */
|
||||
:target {
|
||||
scroll-margin-top: 60px; }
|
||||
|
Loading…
x
Reference in New Issue
Block a user