diff --git a/lib/amd/build/bulkactions/bulk_actions.min.js b/lib/amd/build/bulkactions/bulk_actions.min.js index 71c9a69ad2e..3a206455e2b 100644 --- a/lib/amd/build/bulkactions/bulk_actions.min.js +++ b/lib/amd/build/bulkactions/bulk_actions.min.js @@ -5,6 +5,6 @@ define("core/bulkactions/bulk_actions",["exports","core/templates","core/str","c * @module core/bulkactions/bulk_actions * @copyright 2023 Mihail Geshoski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_templates=(obj=_templates)&&obj.__esModule?obj:{default:obj};const Selectors_stickyFooterContainer="#sticky-footer",Selectors_selectedItemsCountContainer='[data-type="bulkactions"] [data-for="bulkcount"]',Selectors_cancelBulkActionModeElement='[data-type="bulkactions"] [data-action="bulkcancel"]',Selectors_bulkModeContainer='[data-type="bulkactions"]',Selectors_bulkActionsContainer='[data-type="bulkactions"] [data-for="bulktools"]';return _exports.default=class{constructor(){let maxButtons=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null;if(_defineProperty(this,"initialStickyFooterContent",null),_defineProperty(this,"selectedItems",[]),_defineProperty(this,"isBulkActionsModeEnabled",!1),_defineProperty(this,"maxButtons",5),!this.getStickyFooterContainer())throw new Error("Sticky footer not found.");this.initialStickyFooterContent=this.getStickyFooterContainer().innerHTML,maxButtons&&(this.maxButtons=maxButtons),this.registerItemSelectChangeEvent((async()=>{this.selectedItems=this.getSelectedItems(),this.selectedItems.length>0?this.isBulkActionsModeEnabled?await this.updateBulkItemSelection():await this.enableBulkActionsMode():this.disableBulkActionsMode()}))}getBulkActions(){throw new Error("getBulkActions() must be implemented in ".concat(this.constructor.name))}getSelectedItems(){throw new Error("getSelectedItems() must be implemented in ".concat(this.constructor.name))}registerItemSelectChangeEvent(eventHandler){throw new Error("registerItemSelectChangeEvent(".concat(eventHandler,") must be implemented in ").concat(this.constructor.name))}deselectItem(selectedItem){throw new Error("deselectItem(".concat(selectedItem,") must be implemented in ").concat(this.constructor.name))}getStickyFooterContainer(){return document.querySelector(Selectors_stickyFooterContainer)}async enableBulkActionsMode(){(0,_stickyFooter.enableStickyFooter)(),this.getStickyFooterContainer().innerHTML=await this.renderBulkActions();const bulkModeContainer=this.getStickyFooterContainer().querySelector(Selectors_bulkModeContainer),bulkActionsContainer=bulkModeContainer.querySelector(Selectors_bulkActionsContainer);this.getBulkActions().forEach((bulkAction=>{bulkAction.registerListenerEvents(bulkActionsContainer),bulkAction.setSelectedItems(this.selectedItems)})),bulkModeContainer.addEventListener("click",(e=>{e.target.closest(Selectors_cancelBulkActionModeElement)&&(this.selectedItems.forEach((item=>{this.deselectItem(item)})),this.disableBulkActionsMode())})),this.isBulkActionsModeEnabled=!0}disableBulkActionsMode(){this.initialStickyFooterContent.length>0?this.getStickyFooterContainer().innerHTML=this.initialStickyFooterContent:(0,_stickyFooter.disableStickyFooter)(),this.isBulkActionsModeEnabled=!1}async renderBulkActions(){const data={bulkselectioncount:this.selectedItems.length,actions:[],moreactions:[],hasmoreactions:!1},bulkActions=this.getBulkActions(),showMoreButton=bulkActions.length>this.maxButtons;return(await Promise.all(bulkActions.map(((bulkAction,index)=>bulkAction.renderBulkActionTrigger(showMoreButton&&index>=this.maxButtons-1,index))))).forEach(((actionTrigger,index)=>{showMoreButton&&index>=this.maxButtons-1?data.moreactions.push({actiontrigger:actionTrigger}):data.actions.push({actiontrigger:actionTrigger})})),data.hasmoreactions=data.moreactions.length>0,_templates.default.render("core/bulkactions/bulk_actions",data)}async updateBulkItemSelection(){const bulkSelection=await(0,_str.get_string)("bulkselection","core",this.selectedItems.length);document.querySelector(Selectors_selectedItemsCountContainer).innerHTML=bulkSelection}},_exports.default})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_templates=(obj=_templates)&&obj.__esModule?obj:{default:obj};const Selectors_stickyFooterContainer="#sticky-footer",Selectors_selectedItemsCountContainer='[data-type="bulkactions"] [data-for="bulkcount"]',Selectors_cancelBulkActionModeElement='[data-type="bulkactions"] [data-action="bulkcancel"]',Selectors_bulkModeContainer='[data-type="bulkactions"]',Selectors_bulkActionsContainer='[data-type="bulkactions"] [data-for="bulktools"]';return _exports.default=class{constructor(){let maxButtons=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null;if(_defineProperty(this,"initialStickyFooterContent",null),_defineProperty(this,"selectedItems",[]),_defineProperty(this,"isBulkActionsModeEnabled",!1),_defineProperty(this,"maxButtons",5),!this.getStickyFooterContainer())throw new Error("Sticky footer not found.");this.initialStickyFooterContent=this.getStickyFooterContainer().innerHTML,maxButtons&&(this.maxButtons=maxButtons),this.registerItemSelectChangeEvent((async()=>{this.selectedItems=this.getSelectedItems(),this.selectedItems.length>0?this.isBulkActionsModeEnabled?await this.updateBulkItemSelection():await this.enableBulkActionsMode():this.disableBulkActionsMode()}))}getBulkActions(){throw new Error("getBulkActions() must be implemented in ".concat(this.constructor.name))}getSelectedItems(){throw new Error("getSelectedItems() must be implemented in ".concat(this.constructor.name))}registerItemSelectChangeEvent(eventHandler){throw new Error("registerItemSelectChangeEvent(".concat(eventHandler,") must be implemented in ").concat(this.constructor.name))}deselectItem(selectedItem){throw new Error("deselectItem(".concat(selectedItem,") must be implemented in ").concat(this.constructor.name))}getStickyFooterContainer(){return document.querySelector(Selectors_stickyFooterContainer)}async enableBulkActionsMode(){(0,_stickyFooter.enableStickyFooter)(),this.getStickyFooterContainer().innerHTML=await this.renderBulkActions();const bulkModeContainer=this.getStickyFooterContainer().querySelector(Selectors_bulkModeContainer),bulkActionsContainer=bulkModeContainer.querySelector(Selectors_bulkActionsContainer);this.getBulkActions().forEach((bulkAction=>{bulkAction.registerListenerEvents(bulkActionsContainer),bulkAction.setSelectedItems(this.selectedItems)})),bulkModeContainer.addEventListener("click",(e=>{e.target.closest(Selectors_cancelBulkActionModeElement)&&(this.selectedItems.forEach((item=>{this.deselectItem(item)})),this.disableBulkActionsMode())})),this.isBulkActionsModeEnabled=!0,setTimeout((async()=>{await this.updateBulkItemSelection()}),300)}disableBulkActionsMode(){this.initialStickyFooterContent.length>0?this.getStickyFooterContainer().innerHTML=this.initialStickyFooterContent:(0,_stickyFooter.disableStickyFooter)(),this.isBulkActionsModeEnabled=!1}async renderBulkActions(){const data={bulkselectioncount:this.selectedItems.length,actions:[],moreactions:[],hasmoreactions:!1},bulkActions=this.getBulkActions(),showMoreButton=bulkActions.length>this.maxButtons;return(await Promise.all(bulkActions.map(((bulkAction,index)=>bulkAction.renderBulkActionTrigger(showMoreButton&&index>=this.maxButtons-1,index))))).forEach(((actionTrigger,index)=>{showMoreButton&&index>=this.maxButtons-1?data.moreactions.push({actiontrigger:actionTrigger}):data.actions.push({actiontrigger:actionTrigger})})),data.hasmoreactions=data.moreactions.length>0,_templates.default.render("core/bulkactions/bulk_actions",data)}async updateBulkItemSelection(){const bulkSelection=await(0,_str.get_string)("bulkselection","core",this.selectedItems.length);document.querySelector(Selectors_selectedItemsCountContainer).innerHTML=bulkSelection}},_exports.default})); //# sourceMappingURL=bulk_actions.min.js.map \ No newline at end of file diff --git a/lib/amd/build/bulkactions/bulk_actions.min.js.map b/lib/amd/build/bulkactions/bulk_actions.min.js.map index 1feb7641756..e3da3407c32 100644 --- a/lib/amd/build/bulkactions/bulk_actions.min.js.map +++ b/lib/amd/build/bulkactions/bulk_actions.min.js.map @@ -1 +1 @@ -{"version":3,"file":"bulk_actions.min.js","sources":["../../src/bulkactions/bulk_actions.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\nimport Templates from 'core/templates';\nimport {get_string as getString} from 'core/str';\nimport {disableStickyFooter, enableStickyFooter} from 'core/sticky-footer';\n\n/**\n * Base class for defining a bulk actions area within a page.\n *\n * @module core/bulkactions/bulk_actions\n * @copyright 2023 Mihail Geshoski \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/** @constant {Object} The object containing the relevant selectors. */\nconst Selectors = {\n stickyFooterContainer: '#sticky-footer',\n selectedItemsCountContainer: '[data-type=\"bulkactions\"] [data-for=\"bulkcount\"]',\n cancelBulkActionModeElement: '[data-type=\"bulkactions\"] [data-action=\"bulkcancel\"]',\n bulkModeContainer: '[data-type=\"bulkactions\"]',\n bulkActionsContainer: '[data-type=\"bulkactions\"] [data-for=\"bulktools\"]'\n};\n\nexport default class BulkActions {\n\n /** @property {string|null} initialStickyFooterContent The initial content of the sticky footer. */\n initialStickyFooterContent = null;\n\n /** @property {Array} selectedItems The array of selected item elements. */\n selectedItems = [];\n\n /** @property {boolean} isBulkActionsModeEnabled Whether the bulk actions mode is enabled. */\n isBulkActionsModeEnabled = false;\n\n /**\n * @property {int} maxButtons Sets the maximum number of action buttons to display. If exceeded, additional actions\n * are shown in a dropdown menu.\n */\n maxButtons = 5;\n\n /**\n * The class constructor.\n *\n * @param {int|null} maxButtons Sets the maximum number of action buttons to display. If exceeded, additional actions\n * are shown in a dropdown menu.\n * @returns {void}\n */\n constructor(maxButtons = null) {\n if (!this.getStickyFooterContainer()) {\n throw new Error('Sticky footer not found.');\n }\n // Store any pre-existing content in the sticky footer. When bulk actions mode is enabled, this content will be\n // replaced with the bulk actions content and restored when bulk actions mode is disabled.\n this.initialStickyFooterContent = this.getStickyFooterContainer().innerHTML;\n if (maxButtons) {\n this.maxButtons = maxButtons;\n }\n // Register and handle the item select change event.\n this.registerItemSelectChangeEvent(async() => {\n this.selectedItems = this.getSelectedItems();\n if (this.selectedItems.length > 0) { // At least one item is selected.\n // If the bulk actions mode is already enabled only update the selected items count.\n if (this.isBulkActionsModeEnabled) {\n await this.updateBulkItemSelection();\n } else { // Otherwise, enable the bulk action mode.\n await this.enableBulkActionsMode();\n }\n } else { // No items are selected, disable the bulk action mode.\n this.disableBulkActionsMode();\n }\n });\n }\n\n /**\n * Returns the array of the relevant bulk action objects.\n *\n * @method getBulkActions\n * @returns {Array}\n */\n getBulkActions() {\n throw new Error(`getBulkActions() must be implemented in ${this.constructor.name}`);\n }\n\n /**\n * Returns the array of selected items.\n *\n * @method getSelectedItems\n * @returns {Array}\n */\n getSelectedItems() {\n throw new Error(`getSelectedItems() must be implemented in ${this.constructor.name}`);\n }\n\n /**\n * Adds the listener for the item select change event.\n * The event handler function that is passed as a parameter should be called right after the event is triggered.\n *\n * @method registerItemSelectChangeEvent\n * @param {function} eventHandler The event handler function.\n * @returns {void}\n */\n registerItemSelectChangeEvent(eventHandler) {\n throw new Error(`registerItemSelectChangeEvent(${eventHandler}) must be implemented in ${this.constructor.name}`);\n }\n\n /**\n * Defines the action for deselecting a selected item.\n *\n * The base bulk actions class supports deselecting all selected items but does not have knowledge of the type of the\n * selected element. Therefore, each subclass must explicitly define the action of resetting the attributes that\n * indicate a selected state.\n *\n * @method deselectItem\n * @param {HTMLElement} selectedItem The selected element.\n * @returns {void}\n */\n deselectItem(selectedItem) {\n throw new Error(`deselectItem(${selectedItem}) must be implemented in ${this.constructor.name}`);\n }\n\n /**\n * Returns the sticky footer container.\n *\n * @method getStickyFooterContainer\n * @returns {HTMLElement}\n */\n getStickyFooterContainer() {\n return document.querySelector(Selectors.stickyFooterContainer);\n }\n\n /**\n * Enables the bulk action mode.\n *\n * @method enableBulkActionsMode\n * @returns {Promise}\n */\n async enableBulkActionsMode() {\n // Make sure that the sticky footer is enabled.\n enableStickyFooter();\n // Render the bulk actions content in the sticky footer container.\n this.getStickyFooterContainer().innerHTML = await this.renderBulkActions();\n const bulkModeContainer = this.getStickyFooterContainer().querySelector(Selectors.bulkModeContainer);\n const bulkActionsContainer = bulkModeContainer.querySelector(Selectors.bulkActionsContainer);\n this.getBulkActions().forEach((bulkAction) => {\n // Register the listener events for each available bulk action.\n bulkAction.registerListenerEvents(bulkActionsContainer);\n // Set the selected items for each available bulk action.\n bulkAction.setSelectedItems(this.selectedItems);\n });\n // Register the click listener event for the cancel bulk mode button.\n bulkModeContainer.addEventListener('click', (e) => {\n if (e.target.closest(Selectors.cancelBulkActionModeElement)) {\n // Deselect all selected items.\n this.selectedItems.forEach((item) => {\n this.deselectItem(item);\n });\n // Disable the bulk action mode.\n this.disableBulkActionsMode();\n }\n });\n this.isBulkActionsModeEnabled = true;\n }\n\n /**\n * Disables the bulk action mode.\n *\n * @method disableBulkActionsMode\n * @returns {void}\n */\n disableBulkActionsMode() {\n // If there was any previous (initial) content in the sticky footer, restore it.\n if (this.initialStickyFooterContent.length > 0) {\n this.getStickyFooterContainer().innerHTML = this.initialStickyFooterContent;\n } else { // No previous content to restore, disable the sticky footer.\n disableStickyFooter();\n }\n this.isBulkActionsModeEnabled = false;\n }\n\n /**\n * Renders the bulk actions content.\n *\n * @method renderBulkActions\n * @returns {Promise}\n */\n async renderBulkActions() {\n const data = {\n bulkselectioncount: this.selectedItems.length,\n actions: [],\n moreactions: [],\n hasmoreactions: false,\n };\n const bulkActions = this.getBulkActions();\n const showMoreButton = bulkActions.length > this.maxButtons;\n\n // Get all bulk actions and render them in order.\n const actions = await Promise.all(\n bulkActions.map((bulkAction, index) =>\n bulkAction.renderBulkActionTrigger(\n showMoreButton && (index >= this.maxButtons - 1),\n index\n )\n )\n );\n\n // Separate rendered actions into data.actions and data.moreactions in the correct order.\n actions.forEach((actionTrigger, index) => {\n if (showMoreButton && (index >= this.maxButtons - 1)) {\n data.moreactions.push({'actiontrigger': actionTrigger});\n } else {\n data.actions.push({'actiontrigger': actionTrigger});\n }\n });\n\n data.hasmoreactions = data.moreactions.length > 0;\n\n return Templates.render('core/bulkactions/bulk_actions', data);\n }\n\n /**\n * Updates the selected items count in the bulk actions content.\n *\n * @method updateBulkItemSelection\n * @returns {void}\n */\n async updateBulkItemSelection() {\n const bulkSelection = await getString('bulkselection', 'core', this.selectedItems.length);\n document.querySelector(Selectors.selectedItemsCountContainer).innerHTML = bulkSelection;\n }\n}\n"],"names":["Selectors","constructor","maxButtons","this","getStickyFooterContainer","Error","initialStickyFooterContent","innerHTML","registerItemSelectChangeEvent","async","selectedItems","getSelectedItems","length","isBulkActionsModeEnabled","updateBulkItemSelection","enableBulkActionsMode","disableBulkActionsMode","getBulkActions","name","eventHandler","deselectItem","selectedItem","document","querySelector","renderBulkActions","bulkModeContainer","bulkActionsContainer","forEach","bulkAction","registerListenerEvents","setSelectedItems","addEventListener","e","target","closest","item","data","bulkselectioncount","actions","moreactions","hasmoreactions","bulkActions","showMoreButton","Promise","all","map","index","renderBulkActionTrigger","actionTrigger","push","Templates","render","bulkSelection"],"mappings":";;;;;;;yJA4BMA,gCACqB,iBADrBA,sCAE2B,mDAF3BA,sCAG2B,uDAH3BA,4BAIiB,4BAJjBA,+BAKoB,iFA2BtBC,kBAAYC,kEAAa,0DArBI,2CAGb,qDAGW,qCAMd,IAUJC,KAAKC,iCACA,IAAIC,MAAM,iCAIfC,2BAA6BH,KAAKC,2BAA2BG,UAC9DL,kBACKA,WAAaA,iBAGjBM,+BAA8BC,eAC1BC,cAAgBP,KAAKQ,mBACtBR,KAAKO,cAAcE,OAAS,EAExBT,KAAKU,+BACCV,KAAKW,gCAELX,KAAKY,6BAGVC,4BAWjBC,uBACU,IAAIZ,wDAAiDF,KAAKF,YAAYiB,OAShFP,yBACU,IAAIN,0DAAmDF,KAAKF,YAAYiB,OAWlFV,8BAA8BW,oBACpB,IAAId,8CAAuCc,iDAAwChB,KAAKF,YAAYiB,OAc9GE,aAAaC,oBACH,IAAIhB,6BAAsBgB,iDAAwClB,KAAKF,YAAYiB,OAS7Fd,kCACWkB,SAASC,cAAcvB,2GAazBI,2BAA2BG,gBAAkBJ,KAAKqB,0BACjDC,kBAAoBtB,KAAKC,2BAA2BmB,cAAcvB,6BAClE0B,qBAAuBD,kBAAkBF,cAAcvB,qCACxDiB,iBAAiBU,SAASC,aAE3BA,WAAWC,uBAAuBH,sBAElCE,WAAWE,iBAAiB3B,KAAKO,kBAGrCe,kBAAkBM,iBAAiB,SAAUC,IACrCA,EAAEC,OAAOC,QAAQlC,8CAEZU,cAAciB,SAASQ,YACnBf,aAAae,cAGjBnB,kCAGRH,0BAA2B,EASpCG,yBAEQb,KAAKG,2BAA2BM,OAAS,OACpCR,2BAA2BG,UAAYJ,KAAKG,wEAIhDO,0BAA2B,kCAU1BuB,KAAO,CACTC,mBAAoBlC,KAAKO,cAAcE,OACvC0B,QAAS,GACTC,YAAa,GACbC,gBAAgB,GAEdC,YAActC,KAAKc,iBACnByB,eAAiBD,YAAY7B,OAAST,KAAKD,wBAG3ByC,QAAQC,IAC1BH,YAAYI,KAAI,CAACjB,WAAYkB,QACzBlB,WAAWmB,wBACPL,gBAAmBI,OAAS3C,KAAKD,WAAa,EAC9C4C,WAMJnB,SAAQ,CAACqB,cAAeF,SACxBJ,gBAAmBI,OAAS3C,KAAKD,WAAa,EAC9CkC,KAAKG,YAAYU,KAAK,eAAkBD,gBAExCZ,KAAKE,QAAQW,KAAK,eAAkBD,mBAI5CZ,KAAKI,eAAiBJ,KAAKG,YAAY3B,OAAS,EAEzCsC,mBAAUC,OAAO,gCAAiCf,4CAUnDgB,oBAAsB,mBAAU,gBAAiB,OAAQjD,KAAKO,cAAcE,QAClFU,SAASC,cAAcvB,uCAAuCO,UAAY6C"} \ No newline at end of file +{"version":3,"file":"bulk_actions.min.js","sources":["../../src/bulkactions/bulk_actions.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\nimport Templates from 'core/templates';\nimport {get_string as getString} from 'core/str';\nimport {disableStickyFooter, enableStickyFooter} from 'core/sticky-footer';\n\n/**\n * Base class for defining a bulk actions area within a page.\n *\n * @module core/bulkactions/bulk_actions\n * @copyright 2023 Mihail Geshoski \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/** @constant {Object} The object containing the relevant selectors. */\nconst Selectors = {\n stickyFooterContainer: '#sticky-footer',\n selectedItemsCountContainer: '[data-type=\"bulkactions\"] [data-for=\"bulkcount\"]',\n cancelBulkActionModeElement: '[data-type=\"bulkactions\"] [data-action=\"bulkcancel\"]',\n bulkModeContainer: '[data-type=\"bulkactions\"]',\n bulkActionsContainer: '[data-type=\"bulkactions\"] [data-for=\"bulktools\"]'\n};\n\nexport default class BulkActions {\n\n /** @property {string|null} initialStickyFooterContent The initial content of the sticky footer. */\n initialStickyFooterContent = null;\n\n /** @property {Array} selectedItems The array of selected item elements. */\n selectedItems = [];\n\n /** @property {boolean} isBulkActionsModeEnabled Whether the bulk actions mode is enabled. */\n isBulkActionsModeEnabled = false;\n\n /**\n * @property {int} maxButtons Sets the maximum number of action buttons to display. If exceeded, additional actions\n * are shown in a dropdown menu.\n */\n maxButtons = 5;\n\n /**\n * The class constructor.\n *\n * @param {int|null} maxButtons Sets the maximum number of action buttons to display. If exceeded, additional actions\n * are shown in a dropdown menu.\n * @returns {void}\n */\n constructor(maxButtons = null) {\n if (!this.getStickyFooterContainer()) {\n throw new Error('Sticky footer not found.');\n }\n // Store any pre-existing content in the sticky footer. When bulk actions mode is enabled, this content will be\n // replaced with the bulk actions content and restored when bulk actions mode is disabled.\n this.initialStickyFooterContent = this.getStickyFooterContainer().innerHTML;\n if (maxButtons) {\n this.maxButtons = maxButtons;\n }\n // Register and handle the item select change event.\n this.registerItemSelectChangeEvent(async() => {\n this.selectedItems = this.getSelectedItems();\n if (this.selectedItems.length > 0) { // At least one item is selected.\n // If the bulk actions mode is already enabled only update the selected items count.\n if (this.isBulkActionsModeEnabled) {\n await this.updateBulkItemSelection();\n } else { // Otherwise, enable the bulk action mode.\n await this.enableBulkActionsMode();\n }\n } else { // No items are selected, disable the bulk action mode.\n this.disableBulkActionsMode();\n }\n });\n }\n\n /**\n * Returns the array of the relevant bulk action objects.\n *\n * @method getBulkActions\n * @returns {Array}\n */\n getBulkActions() {\n throw new Error(`getBulkActions() must be implemented in ${this.constructor.name}`);\n }\n\n /**\n * Returns the array of selected items.\n *\n * @method getSelectedItems\n * @returns {Array}\n */\n getSelectedItems() {\n throw new Error(`getSelectedItems() must be implemented in ${this.constructor.name}`);\n }\n\n /**\n * Adds the listener for the item select change event.\n * The event handler function that is passed as a parameter should be called right after the event is triggered.\n *\n * @method registerItemSelectChangeEvent\n * @param {function} eventHandler The event handler function.\n * @returns {void}\n */\n registerItemSelectChangeEvent(eventHandler) {\n throw new Error(`registerItemSelectChangeEvent(${eventHandler}) must be implemented in ${this.constructor.name}`);\n }\n\n /**\n * Defines the action for deselecting a selected item.\n *\n * The base bulk actions class supports deselecting all selected items but does not have knowledge of the type of the\n * selected element. Therefore, each subclass must explicitly define the action of resetting the attributes that\n * indicate a selected state.\n *\n * @method deselectItem\n * @param {HTMLElement} selectedItem The selected element.\n * @returns {void}\n */\n deselectItem(selectedItem) {\n throw new Error(`deselectItem(${selectedItem}) must be implemented in ${this.constructor.name}`);\n }\n\n /**\n * Returns the sticky footer container.\n *\n * @method getStickyFooterContainer\n * @returns {HTMLElement}\n */\n getStickyFooterContainer() {\n return document.querySelector(Selectors.stickyFooterContainer);\n }\n\n /**\n * Enables the bulk action mode.\n *\n * @method enableBulkActionsMode\n * @returns {Promise}\n */\n async enableBulkActionsMode() {\n // Make sure that the sticky footer is enabled.\n enableStickyFooter();\n // Render the bulk actions content in the sticky footer container.\n this.getStickyFooterContainer().innerHTML = await this.renderBulkActions();\n const bulkModeContainer = this.getStickyFooterContainer().querySelector(Selectors.bulkModeContainer);\n const bulkActionsContainer = bulkModeContainer.querySelector(Selectors.bulkActionsContainer);\n this.getBulkActions().forEach((bulkAction) => {\n // Register the listener events for each available bulk action.\n bulkAction.registerListenerEvents(bulkActionsContainer);\n // Set the selected items for each available bulk action.\n bulkAction.setSelectedItems(this.selectedItems);\n });\n // Register the click listener event for the cancel bulk mode button.\n bulkModeContainer.addEventListener('click', (e) => {\n if (e.target.closest(Selectors.cancelBulkActionModeElement)) {\n // Deselect all selected items.\n this.selectedItems.forEach((item) => {\n this.deselectItem(item);\n });\n // Disable the bulk action mode.\n this.disableBulkActionsMode();\n }\n });\n this.isBulkActionsModeEnabled = true;\n\n // Calling `renderBulkActions()` already renders the item selection count.\n // Because of this, screen readers will not announce the item selection count on first load given that aria-live regions\n // must be present in the DOM and have their contents changed before screen readers can announce their content.\n // So, we call `updateBulkItemSelection()` after a short delay to ensure that screen readers announce the item count.\n setTimeout(async() => {\n await this.updateBulkItemSelection();\n }, 300);\n }\n\n /**\n * Disables the bulk action mode.\n *\n * @method disableBulkActionsMode\n * @returns {void}\n */\n disableBulkActionsMode() {\n // If there was any previous (initial) content in the sticky footer, restore it.\n if (this.initialStickyFooterContent.length > 0) {\n this.getStickyFooterContainer().innerHTML = this.initialStickyFooterContent;\n } else { // No previous content to restore, disable the sticky footer.\n disableStickyFooter();\n }\n this.isBulkActionsModeEnabled = false;\n }\n\n /**\n * Renders the bulk actions content.\n *\n * @method renderBulkActions\n * @returns {Promise}\n */\n async renderBulkActions() {\n const data = {\n bulkselectioncount: this.selectedItems.length,\n actions: [],\n moreactions: [],\n hasmoreactions: false,\n };\n const bulkActions = this.getBulkActions();\n const showMoreButton = bulkActions.length > this.maxButtons;\n\n // Get all bulk actions and render them in order.\n const actions = await Promise.all(\n bulkActions.map((bulkAction, index) =>\n bulkAction.renderBulkActionTrigger(\n showMoreButton && (index >= this.maxButtons - 1),\n index\n )\n )\n );\n\n // Separate rendered actions into data.actions and data.moreactions in the correct order.\n actions.forEach((actionTrigger, index) => {\n if (showMoreButton && (index >= this.maxButtons - 1)) {\n data.moreactions.push({'actiontrigger': actionTrigger});\n } else {\n data.actions.push({'actiontrigger': actionTrigger});\n }\n });\n\n data.hasmoreactions = data.moreactions.length > 0;\n\n return Templates.render('core/bulkactions/bulk_actions', data);\n }\n\n /**\n * Updates the selected items count in the bulk actions content.\n *\n * @method updateBulkItemSelection\n * @returns {void}\n */\n async updateBulkItemSelection() {\n const bulkSelection = await getString('bulkselection', 'core', this.selectedItems.length);\n document.querySelector(Selectors.selectedItemsCountContainer).innerHTML = bulkSelection;\n }\n}\n"],"names":["Selectors","constructor","maxButtons","this","getStickyFooterContainer","Error","initialStickyFooterContent","innerHTML","registerItemSelectChangeEvent","async","selectedItems","getSelectedItems","length","isBulkActionsModeEnabled","updateBulkItemSelection","enableBulkActionsMode","disableBulkActionsMode","getBulkActions","name","eventHandler","deselectItem","selectedItem","document","querySelector","renderBulkActions","bulkModeContainer","bulkActionsContainer","forEach","bulkAction","registerListenerEvents","setSelectedItems","addEventListener","e","target","closest","item","setTimeout","data","bulkselectioncount","actions","moreactions","hasmoreactions","bulkActions","showMoreButton","Promise","all","map","index","renderBulkActionTrigger","actionTrigger","push","Templates","render","bulkSelection"],"mappings":";;;;;;;yJA4BMA,gCACqB,iBADrBA,sCAE2B,mDAF3BA,sCAG2B,uDAH3BA,4BAIiB,4BAJjBA,+BAKoB,iFA2BtBC,kBAAYC,kEAAa,0DArBI,2CAGb,qDAGW,qCAMd,IAUJC,KAAKC,iCACA,IAAIC,MAAM,iCAIfC,2BAA6BH,KAAKC,2BAA2BG,UAC9DL,kBACKA,WAAaA,iBAGjBM,+BAA8BC,eAC1BC,cAAgBP,KAAKQ,mBACtBR,KAAKO,cAAcE,OAAS,EAExBT,KAAKU,+BACCV,KAAKW,gCAELX,KAAKY,6BAGVC,4BAWjBC,uBACU,IAAIZ,wDAAiDF,KAAKF,YAAYiB,OAShFP,yBACU,IAAIN,0DAAmDF,KAAKF,YAAYiB,OAWlFV,8BAA8BW,oBACpB,IAAId,8CAAuCc,iDAAwChB,KAAKF,YAAYiB,OAc9GE,aAAaC,oBACH,IAAIhB,6BAAsBgB,iDAAwClB,KAAKF,YAAYiB,OAS7Fd,kCACWkB,SAASC,cAAcvB,2GAazBI,2BAA2BG,gBAAkBJ,KAAKqB,0BACjDC,kBAAoBtB,KAAKC,2BAA2BmB,cAAcvB,6BAClE0B,qBAAuBD,kBAAkBF,cAAcvB,qCACxDiB,iBAAiBU,SAASC,aAE3BA,WAAWC,uBAAuBH,sBAElCE,WAAWE,iBAAiB3B,KAAKO,kBAGrCe,kBAAkBM,iBAAiB,SAAUC,IACrCA,EAAEC,OAAOC,QAAQlC,8CAEZU,cAAciB,SAASQ,YACnBf,aAAae,cAGjBnB,kCAGRH,0BAA2B,EAMhCuB,YAAW3B,gBACDN,KAAKW,4BACZ,KASPE,yBAEQb,KAAKG,2BAA2BM,OAAS,OACpCR,2BAA2BG,UAAYJ,KAAKG,wEAIhDO,0BAA2B,kCAU1BwB,KAAO,CACTC,mBAAoBnC,KAAKO,cAAcE,OACvC2B,QAAS,GACTC,YAAa,GACbC,gBAAgB,GAEdC,YAAcvC,KAAKc,iBACnB0B,eAAiBD,YAAY9B,OAAST,KAAKD,wBAG3B0C,QAAQC,IAC1BH,YAAYI,KAAI,CAAClB,WAAYmB,QACzBnB,WAAWoB,wBACPL,gBAAmBI,OAAS5C,KAAKD,WAAa,EAC9C6C,WAMJpB,SAAQ,CAACsB,cAAeF,SACxBJ,gBAAmBI,OAAS5C,KAAKD,WAAa,EAC9CmC,KAAKG,YAAYU,KAAK,eAAkBD,gBAExCZ,KAAKE,QAAQW,KAAK,eAAkBD,mBAI5CZ,KAAKI,eAAiBJ,KAAKG,YAAY5B,OAAS,EAEzCuC,mBAAUC,OAAO,gCAAiCf,4CAUnDgB,oBAAsB,mBAAU,gBAAiB,OAAQlD,KAAKO,cAAcE,QAClFU,SAASC,cAAcvB,uCAAuCO,UAAY8C"} \ No newline at end of file diff --git a/lib/amd/src/bulkactions/bulk_actions.js b/lib/amd/src/bulkactions/bulk_actions.js index 7397ffd8490..1c01f0147a4 100644 --- a/lib/amd/src/bulkactions/bulk_actions.js +++ b/lib/amd/src/bulkactions/bulk_actions.js @@ -172,6 +172,14 @@ export default class BulkActions { } }); this.isBulkActionsModeEnabled = true; + + // Calling `renderBulkActions()` already renders the item selection count. + // Because of this, screen readers will not announce the item selection count on first load given that aria-live regions + // must be present in the DOM and have their contents changed before screen readers can announce their content. + // So, we call `updateBulkItemSelection()` after a short delay to ensure that screen readers announce the item count. + setTimeout(async() => { + await this.updateBulkItemSelection(); + }, 300); } /** diff --git a/lib/templates/bulkactions/bulk_actions.mustache b/lib/templates/bulkactions/bulk_actions.mustache index c6cd8d45a71..d257e663f68 100644 --- a/lib/templates/bulkactions/bulk_actions.mustache +++ b/lib/templates/bulkactions/bulk_actions.mustache @@ -87,7 +87,7 @@
- {{#str}} bulkselection, core, {{bulkselectioncount}} {{/str}} +  {{#str}} bulkselection, core, {{bulkselectioncount}} {{/str}}