diff --git a/course/amd/build/manual_completion_toggle.min.js b/course/amd/build/manual_completion_toggle.min.js
index 0bd933bdf6b..2dce9b36bfa 100644
--- a/course/amd/build/manual_completion_toggle.min.js
+++ b/course/amd/build/manual_completion_toggle.min.js
@@ -1,2 +1,2 @@
-function _typeof(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){_typeof=function(a){return typeof a}}else{_typeof=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return _typeof(a)}define ("core_course/manual_completion_toggle",["exports","core/templates","core/notification","core_course/repository","core_course/events"],function(a,b,c,d,e){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=h(b);c=h(c);e=g(e);function f(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;f=function(){return a};return a}function g(a){if(a&&a.__esModule){return a}if(null===a||"object"!==_typeof(a)&&"function"!=typeof a){return{default:a}}var b=f();if(b&&b.has(a)){return b.get(a)}var c={},d=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var e in a){if(Object.prototype.hasOwnProperty.call(a,e)){var g=d?Object.getOwnPropertyDescriptor(a,e):null;if(g&&(g.get||g.set)){Object.defineProperty(c,e,g)}else{c[e]=a[e]}}}c.default=a;if(b){b.set(a,c)}return c}function h(a){return a&&a.__esModule?a:{default:a}}function i(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function j(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var h=a.apply(b,c);function f(a){i(h,d,e,f,g,"next",a)}function g(a){i(h,d,e,f,g,"throw",a)}f(void 0)})}}var k={MANUAL_TOGGLE:"button[data-action=toggle-manual-completion]"},l={TOGGLE_MARK_DONE:"manual:mark-done",TOGGLE_UNDO:"manual:undo"},m=!1,n=function(){if(m){return}document.addEventListener("click",function(a){var b=a.target.closest(k.MANUAL_TOGGLE);if(b){a.preventDefault();o(b).catch(c.default.exception)}});m=!0};a.init=n;var o=function(){var a=j(regeneratorRuntime.mark(function a(f){var g,h,i,j,k,m,n,o,p,q,r;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:g=f.innerHTML;f.setAttribute("disabled","disabled");h=f.getAttribute("data-toggletype");i=f.getAttribute("data-cmid");j=f.getAttribute("data-activityname");k=h===l.TOGGLE_MARK_DONE;a.next=8;return b.default.render("core/loading",{});case 8:m=a.sent;a.next=11;return b.default.replaceNodeContents(f,m,"");case 11:a.prev=11;a.next=14;return(0,d.toggleManualCompletion)(i,k);case 14:n={cmid:i,activityname:j,overallcomplete:k,overallincomplete:!k,istrackeduser:!0};a.next=17;return b.default.renderForPromise("core_course/completion_manual",n);case 17:o=a.sent;a.next=20;return b.default.replaceNode(f,o.html,o.js);case 20:p=a.sent;q=p.pop();r=new CustomEvent(e.manualCompletionToggled,{bubbles:!0,detail:{cmid:i,activityname:j,completed:k}});q.dispatchEvent(r);a.next=31;break;case 26:a.prev=26;a.t0=a["catch"](11);f.removeAttribute("disabled");f.innerHTML=g;c.default.exception(a.t0);case 31:case"end":return a.stop();}}},a,null,[[11,26]])}));return function(){return a.apply(this,arguments)}}()});
+function _typeof(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){_typeof=function(a){return typeof a}}else{_typeof=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return _typeof(a)}define ("core_course/manual_completion_toggle",["exports","core/templates","core/notification","core_course/repository","core_course/events"],function(a,b,c,d,e){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=h(b);c=h(c);e=g(e);function f(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;f=function(){return a};return a}function g(a){if(a&&a.__esModule){return a}if(null===a||"object"!==_typeof(a)&&"function"!=typeof a){return{default:a}}var b=f();if(b&&b.has(a)){return b.get(a)}var c={},d=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var e in a){if(Object.prototype.hasOwnProperty.call(a,e)){var g=d?Object.getOwnPropertyDescriptor(a,e):null;if(g&&(g.get||g.set)){Object.defineProperty(c,e,g)}else{c[e]=a[e]}}}c.default=a;if(b){b.set(a,c)}return c}function h(a){return a&&a.__esModule?a:{default:a}}function i(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function j(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var h=a.apply(b,c);function f(a){i(h,d,e,f,g,"next",a)}function g(a){i(h,d,e,f,g,"throw",a)}f(void 0)})}}var k={MANUAL_TOGGLE:"button[data-action=toggle-manual-completion]"},l={TOGGLE_MARK_DONE:"manual:mark-done",TOGGLE_UNDO:"manual:undo"},m=!1,n=function(){if(m){return}document.addEventListener("click",function(a){var b=a.target.closest(k.MANUAL_TOGGLE);if(b){a.preventDefault();o(b).catch(c.default.exception)}});m=!0};a.init=n;var o=function(){var a=j(regeneratorRuntime.mark(function a(f){var g,h,i,j,k,m,n,o,p,q,r,s;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:g=f.innerHTML;f.setAttribute("disabled","disabled");h=f.getAttribute("data-toggletype");i=f.getAttribute("data-cmid");j=f.getAttribute("data-activityname");k=h===l.TOGGLE_MARK_DONE;a.next=8;return b.default.render("core/loading",{});case 8:m=a.sent;a.next=11;return b.default.replaceNodeContents(f,m,"");case 11:a.prev=11;a.next=14;return(0,d.toggleManualCompletion)(i,k);case 14:n={cmid:i,activityname:j,overallcomplete:k,overallincomplete:!k,istrackeduser:!0};a.next=17;return b.default.renderForPromise("core_course/completion_manual",n);case 17:o=a.sent;a.next=20;return b.default.replaceNode(f,o.html,o.js);case 20:p=a.sent;q=p.pop();r=f.getAttribute("data-withavailability");s=new CustomEvent(e.manualCompletionToggled,{bubbles:!0,detail:{cmid:i,activityname:j,completed:k,withAvailability:r}});q.dispatchEvent(s);a.next=32;break;case 27:a.prev=27;a.t0=a["catch"](11);f.removeAttribute("disabled");f.innerHTML=g;c.default.exception(a.t0);case 32:case"end":return a.stop();}}},a,null,[[11,27]])}));return function(){return a.apply(this,arguments)}}()});
//# sourceMappingURL=manual_completion_toggle.min.js.map
diff --git a/course/amd/build/manual_completion_toggle.min.js.map b/course/amd/build/manual_completion_toggle.min.js.map
index a82ad987a34..a9a8ec8915a 100644
--- a/course/amd/build/manual_completion_toggle.min.js.map
+++ b/course/amd/build/manual_completion_toggle.min.js.map
@@ -1 +1 @@
-{"version":3,"sources":["../src/manual_completion_toggle.js"],"names":["SELECTORS","MANUAL_TOGGLE","TOGGLE_TYPES","TOGGLE_MARK_DONE","TOGGLE_UNDO","registered","init","document","addEventListener","e","toggleButton","target","closest","preventDefault","toggleManualCompletionState","catch","Notification","exception","originalInnerHtml","innerHTML","setAttribute","toggleType","getAttribute","cmid","activityname","completed","Templates","render","loadingHtml","replaceNodeContents","templateContext","overallcomplete","overallincomplete","istrackeduser","renderForPromise","renderObject","replaceNode","html","js","replacedNode","newToggleButton","pop","toggledEvent","CustomEvent","CourseEvents","manualCompletionToggled","bubbles","detail","dispatchEvent","removeAttribute"],"mappings":"ihBAyBA,OACA,OAEA,O,25BAOMA,CAAAA,CAAS,CAAG,CACdC,aAAa,CAAE,8CADD,C,CASZC,CAAY,CAAG,CACjBC,gBAAgB,CAAE,kBADD,CAEjBC,WAAW,CAAE,aAFI,C,CAUjBC,CAAU,G,CAKDC,CAAI,CAAG,UAAM,CACtB,GAAID,CAAJ,CAAgB,CACZ,MACH,CACDE,QAAQ,CAACC,gBAAT,CAA0B,OAA1B,CAAmC,SAACC,CAAD,CAAO,CACtC,GAAMC,CAAAA,CAAY,CAAGD,CAAC,CAACE,MAAF,CAASC,OAAT,CAAiBZ,CAAS,CAACC,aAA3B,CAArB,CACA,GAAIS,CAAJ,CAAkB,CACdD,CAAC,CAACI,cAAF,GACAC,CAA2B,CAACJ,CAAD,CAA3B,CAA0CK,KAA1C,CAAgDC,UAAaC,SAA7D,CACH,CACJ,CAND,EAOAZ,CAAU,GACb,C,UAQD,GAAMS,CAAAA,CAA2B,4CAAG,WAAMJ,CAAN,6GAE1BQ,CAF0B,CAENR,CAAY,CAACS,SAFP,CAKhCT,CAAY,CAACU,YAAb,CAA0B,UAA1B,CAAsC,UAAtC,EAGMC,CAR0B,CAQbX,CAAY,CAACY,YAAb,CAA0B,iBAA1B,CARa,CAS1BC,CAT0B,CASnBb,CAAY,CAACY,YAAb,CAA0B,WAA1B,CATmB,CAU1BE,CAV0B,CAUXd,CAAY,CAACY,YAAb,CAA0B,mBAA1B,CAVW,CAY1BG,CAZ0B,CAYdJ,CAAU,GAAKnB,CAAY,CAACC,gBAZd,gBAeNuB,WAAUC,MAAV,CAAiB,cAAjB,CAAiC,EAAjC,CAfM,QAe1BC,CAf0B,wBAgB1BF,WAAUG,mBAAV,CAA8BnB,CAA9B,CAA4CkB,CAA5C,CAAyD,EAAzD,CAhB0B,mCAoBtB,6BAAuBL,CAAvB,CAA6BE,CAA7B,CApBsB,SAuBtBK,CAvBsB,CAuBJ,CACpBP,IAAI,CAAEA,CADc,CAEpBC,YAAY,CAAEA,CAFM,CAGpBO,eAAe,CAAEN,CAHG,CAIpBO,iBAAiB,CAAE,CAACP,CAJA,CAKpBQ,aAAa,GALO,CAvBI,iBA8BDP,WAAUQ,gBAAV,CAA2B,+BAA3B,CAA4DJ,CAA5D,CA9BC,SA8BtBK,CA9BsB,wBAiCDT,WAAUU,WAAV,CAAsB1B,CAAtB,CAAoCyB,CAAY,CAACE,IAAjD,CAAuDF,CAAY,CAACG,EAApE,CAjCC,SAiCtBC,CAjCsB,QAkCtBC,CAlCsB,CAkCJD,CAAY,CAACE,GAAb,EAlCI,CAqCtBC,CArCsB,CAqCP,GAAIC,CAAAA,WAAJ,CAAgBC,CAAY,CAACC,uBAA7B,CAAsD,CACvEC,OAAO,GADgE,CAEvEC,MAAM,CAAE,CACJxB,IAAI,CAAJA,CADI,CAEJC,YAAY,CAAZA,CAFI,CAGJC,SAAS,CAATA,CAHI,CAF+D,CAAtD,CArCO,CA8C5Be,CAAe,CAACQ,aAAhB,CAA8BN,CAA9B,EA9C4B,sDAkD5BhC,CAAY,CAACuC,eAAb,CAA6B,UAA7B,EACAvC,CAAY,CAACS,SAAb,CAAyBD,CAAzB,CAGAF,UAAaC,SAAb,OAtD4B,wDAAH,uD","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Provides the functionality for toggling the manual completion state of a course module through\n * the manual completion button.\n *\n * @module core_course/manual_completion_toggle\n * @package core_course\n * @copyright 2021 Jun Pataleta \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Templates from 'core/templates';\nimport Notification from 'core/notification';\nimport {toggleManualCompletion} from 'core_course/repository';\nimport * as CourseEvents from 'core_course/events';\n\n/**\n * Selectors in the manual completion template.\n *\n * @type {{MANUAL_TOGGLE: string}}\n */\nconst SELECTORS = {\n MANUAL_TOGGLE: 'button[data-action=toggle-manual-completion]',\n};\n\n/**\n * Toggle type values for the data-toggletype attribute in the core_course/completion_manual template.\n *\n * @type {{TOGGLE_UNDO: string, TOGGLE_MARK_DONE: string}}\n */\nconst TOGGLE_TYPES = {\n TOGGLE_MARK_DONE: 'manual:mark-done',\n TOGGLE_UNDO: 'manual:undo',\n};\n\n/**\n * Whether the event listener has already been registered for this module.\n *\n * @type {boolean}\n */\nlet registered = false;\n\n/**\n * Registers the click event listener for the manual completion toggle button.\n */\nexport const init = () => {\n if (registered) {\n return;\n }\n document.addEventListener('click', (e) => {\n const toggleButton = e.target.closest(SELECTORS.MANUAL_TOGGLE);\n if (toggleButton) {\n e.preventDefault();\n toggleManualCompletionState(toggleButton).catch(Notification.exception);\n }\n });\n registered = true;\n};\n\n/**\n * Toggles the manual completion state of the module for the given user.\n *\n * @param {HTMLElement} toggleButton\n * @returns {Promise}\n */\nconst toggleManualCompletionState = async(toggleButton) => {\n // Make a copy of the original content of the button.\n const originalInnerHtml = toggleButton.innerHTML;\n\n // Disable the button to prevent double clicks.\n toggleButton.setAttribute('disabled', 'disabled');\n\n // Get button data.\n const toggleType = toggleButton.getAttribute('data-toggletype');\n const cmid = toggleButton.getAttribute('data-cmid');\n const activityname = toggleButton.getAttribute('data-activityname');\n // Get the target completion state.\n const completed = toggleType === TOGGLE_TYPES.TOGGLE_MARK_DONE;\n\n // Replace the button contents with the loading icon.\n const loadingHtml = await Templates.render('core/loading', {});\n await Templates.replaceNodeContents(toggleButton, loadingHtml, '');\n\n try {\n // Call the webservice to update the manual completion status.\n await toggleManualCompletion(cmid, completed);\n\n // All good so far. Refresh the manual completion button to reflect its new state by re-rendering the template.\n const templateContext = {\n cmid: cmid,\n activityname: activityname,\n overallcomplete: completed,\n overallincomplete: !completed,\n istrackeduser: true, // We know that we're tracking completion for this user given the presence of this button.\n };\n const renderObject = await Templates.renderForPromise('core_course/completion_manual', templateContext);\n\n // Replace the toggle button with the newly loaded template.\n const replacedNode = await Templates.replaceNode(toggleButton, renderObject.html, renderObject.js);\n const newToggleButton = replacedNode.pop();\n\n // Build manualCompletionToggled custom event.\n const toggledEvent = new CustomEvent(CourseEvents.manualCompletionToggled, {\n bubbles: true,\n detail: {\n cmid,\n activityname,\n completed,\n }\n });\n // Dispatch the manualCompletionToggled custom event.\n newToggleButton.dispatchEvent(toggledEvent);\n\n } catch (exception) {\n // In case of an error, revert the original state and appearance of the button.\n toggleButton.removeAttribute('disabled');\n toggleButton.innerHTML = originalInnerHtml;\n\n // Show the exception.\n Notification.exception(exception);\n }\n};\n"],"file":"manual_completion_toggle.min.js"}
\ No newline at end of file
+{"version":3,"sources":["../src/manual_completion_toggle.js"],"names":["SELECTORS","MANUAL_TOGGLE","TOGGLE_TYPES","TOGGLE_MARK_DONE","TOGGLE_UNDO","registered","init","document","addEventListener","e","toggleButton","target","closest","preventDefault","toggleManualCompletionState","catch","Notification","exception","originalInnerHtml","innerHTML","setAttribute","toggleType","getAttribute","cmid","activityname","completed","Templates","render","loadingHtml","replaceNodeContents","templateContext","overallcomplete","overallincomplete","istrackeduser","renderForPromise","renderObject","replaceNode","html","js","replacedNode","newToggleButton","pop","withAvailability","toggledEvent","CustomEvent","CourseEvents","manualCompletionToggled","bubbles","detail","dispatchEvent","removeAttribute"],"mappings":"ihBAyBA,OACA,OAEA,O,25BAOMA,CAAAA,CAAS,CAAG,CACdC,aAAa,CAAE,8CADD,C,CASZC,CAAY,CAAG,CACjBC,gBAAgB,CAAE,kBADD,CAEjBC,WAAW,CAAE,aAFI,C,CAUjBC,CAAU,G,CAKDC,CAAI,CAAG,UAAM,CACtB,GAAID,CAAJ,CAAgB,CACZ,MACH,CACDE,QAAQ,CAACC,gBAAT,CAA0B,OAA1B,CAAmC,SAACC,CAAD,CAAO,CACtC,GAAMC,CAAAA,CAAY,CAAGD,CAAC,CAACE,MAAF,CAASC,OAAT,CAAiBZ,CAAS,CAACC,aAA3B,CAArB,CACA,GAAIS,CAAJ,CAAkB,CACdD,CAAC,CAACI,cAAF,GACAC,CAA2B,CAACJ,CAAD,CAA3B,CAA0CK,KAA1C,CAAgDC,UAAaC,SAA7D,CACH,CACJ,CAND,EAOAZ,CAAU,GACb,C,UAQD,GAAMS,CAAAA,CAA2B,4CAAG,WAAMJ,CAAN,+GAE1BQ,CAF0B,CAENR,CAAY,CAACS,SAFP,CAKhCT,CAAY,CAACU,YAAb,CAA0B,UAA1B,CAAsC,UAAtC,EAGMC,CAR0B,CAQbX,CAAY,CAACY,YAAb,CAA0B,iBAA1B,CARa,CAS1BC,CAT0B,CASnBb,CAAY,CAACY,YAAb,CAA0B,WAA1B,CATmB,CAU1BE,CAV0B,CAUXd,CAAY,CAACY,YAAb,CAA0B,mBAA1B,CAVW,CAY1BG,CAZ0B,CAYdJ,CAAU,GAAKnB,CAAY,CAACC,gBAZd,gBAeNuB,WAAUC,MAAV,CAAiB,cAAjB,CAAiC,EAAjC,CAfM,QAe1BC,CAf0B,wBAgB1BF,WAAUG,mBAAV,CAA8BnB,CAA9B,CAA4CkB,CAA5C,CAAyD,EAAzD,CAhB0B,mCAoBtB,6BAAuBL,CAAvB,CAA6BE,CAA7B,CApBsB,SAuBtBK,CAvBsB,CAuBJ,CACpBP,IAAI,CAAEA,CADc,CAEpBC,YAAY,CAAEA,CAFM,CAGpBO,eAAe,CAAEN,CAHG,CAIpBO,iBAAiB,CAAE,CAACP,CAJA,CAKpBQ,aAAa,GALO,CAvBI,iBA8BDP,WAAUQ,gBAAV,CAA2B,+BAA3B,CAA4DJ,CAA5D,CA9BC,SA8BtBK,CA9BsB,wBAiCDT,WAAUU,WAAV,CAAsB1B,CAAtB,CAAoCyB,CAAY,CAACE,IAAjD,CAAuDF,CAAY,CAACG,EAApE,CAjCC,SAiCtBC,CAjCsB,QAkCtBC,CAlCsB,CAkCJD,CAAY,CAACE,GAAb,EAlCI,CAqCtBC,CArCsB,CAqCHhC,CAAY,CAACY,YAAb,CAA0B,uBAA1B,CArCG,CAsCtBqB,CAtCsB,CAsCP,GAAIC,CAAAA,WAAJ,CAAgBC,CAAY,CAACC,uBAA7B,CAAsD,CACvEC,OAAO,GADgE,CAEvEC,MAAM,CAAE,CACJzB,IAAI,CAAJA,CADI,CAEJC,YAAY,CAAZA,CAFI,CAGJC,SAAS,CAATA,CAHI,CAIJiB,gBAAgB,CAAhBA,CAJI,CAF+D,CAAtD,CAtCO,CAgD5BF,CAAe,CAACS,aAAhB,CAA8BN,CAA9B,EAhD4B,sDAoD5BjC,CAAY,CAACwC,eAAb,CAA6B,UAA7B,EACAxC,CAAY,CAACS,SAAb,CAAyBD,CAAzB,CAGAF,UAAaC,SAAb,OAxD4B,wDAAH,uD","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Provides the functionality for toggling the manual completion state of a course module through\n * the manual completion button.\n *\n * @module core_course/manual_completion_toggle\n * @package core_course\n * @copyright 2021 Jun Pataleta \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Templates from 'core/templates';\nimport Notification from 'core/notification';\nimport {toggleManualCompletion} from 'core_course/repository';\nimport * as CourseEvents from 'core_course/events';\n\n/**\n * Selectors in the manual completion template.\n *\n * @type {{MANUAL_TOGGLE: string}}\n */\nconst SELECTORS = {\n MANUAL_TOGGLE: 'button[data-action=toggle-manual-completion]',\n};\n\n/**\n * Toggle type values for the data-toggletype attribute in the core_course/completion_manual template.\n *\n * @type {{TOGGLE_UNDO: string, TOGGLE_MARK_DONE: string}}\n */\nconst TOGGLE_TYPES = {\n TOGGLE_MARK_DONE: 'manual:mark-done',\n TOGGLE_UNDO: 'manual:undo',\n};\n\n/**\n * Whether the event listener has already been registered for this module.\n *\n * @type {boolean}\n */\nlet registered = false;\n\n/**\n * Registers the click event listener for the manual completion toggle button.\n */\nexport const init = () => {\n if (registered) {\n return;\n }\n document.addEventListener('click', (e) => {\n const toggleButton = e.target.closest(SELECTORS.MANUAL_TOGGLE);\n if (toggleButton) {\n e.preventDefault();\n toggleManualCompletionState(toggleButton).catch(Notification.exception);\n }\n });\n registered = true;\n};\n\n/**\n * Toggles the manual completion state of the module for the given user.\n *\n * @param {HTMLElement} toggleButton\n * @returns {Promise}\n */\nconst toggleManualCompletionState = async(toggleButton) => {\n // Make a copy of the original content of the button.\n const originalInnerHtml = toggleButton.innerHTML;\n\n // Disable the button to prevent double clicks.\n toggleButton.setAttribute('disabled', 'disabled');\n\n // Get button data.\n const toggleType = toggleButton.getAttribute('data-toggletype');\n const cmid = toggleButton.getAttribute('data-cmid');\n const activityname = toggleButton.getAttribute('data-activityname');\n // Get the target completion state.\n const completed = toggleType === TOGGLE_TYPES.TOGGLE_MARK_DONE;\n\n // Replace the button contents with the loading icon.\n const loadingHtml = await Templates.render('core/loading', {});\n await Templates.replaceNodeContents(toggleButton, loadingHtml, '');\n\n try {\n // Call the webservice to update the manual completion status.\n await toggleManualCompletion(cmid, completed);\n\n // All good so far. Refresh the manual completion button to reflect its new state by re-rendering the template.\n const templateContext = {\n cmid: cmid,\n activityname: activityname,\n overallcomplete: completed,\n overallincomplete: !completed,\n istrackeduser: true, // We know that we're tracking completion for this user given the presence of this button.\n };\n const renderObject = await Templates.renderForPromise('core_course/completion_manual', templateContext);\n\n // Replace the toggle button with the newly loaded template.\n const replacedNode = await Templates.replaceNode(toggleButton, renderObject.html, renderObject.js);\n const newToggleButton = replacedNode.pop();\n\n // Build manualCompletionToggled custom event.\n const withAvailability = toggleButton.getAttribute('data-withavailability');\n const toggledEvent = new CustomEvent(CourseEvents.manualCompletionToggled, {\n bubbles: true,\n detail: {\n cmid,\n activityname,\n completed,\n withAvailability,\n }\n });\n // Dispatch the manualCompletionToggled custom event.\n newToggleButton.dispatchEvent(toggledEvent);\n\n } catch (exception) {\n // In case of an error, revert the original state and appearance of the button.\n toggleButton.removeAttribute('disabled');\n toggleButton.innerHTML = originalInnerHtml;\n\n // Show the exception.\n Notification.exception(exception);\n }\n};\n"],"file":"manual_completion_toggle.min.js"}
\ No newline at end of file
diff --git a/course/amd/build/view.min.js b/course/amd/build/view.min.js
new file mode 100644
index 00000000000..4add734de96
--- /dev/null
+++ b/course/amd/build/view.min.js
@@ -0,0 +1,2 @@
+function _typeof(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){_typeof=function(a){return typeof a}}else{_typeof=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return _typeof(a)}define ("core_course/view",["exports","core_course/events"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=d(b);function c(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;c=function(){return a};return a}function d(a){if(a&&a.__esModule){return a}if(null===a||"object"!==_typeof(a)&&"function"!=typeof a){return{default:a}}var b=c();if(b&&b.has(a)){return b.get(a)}var d={},e=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var f in a){if(Object.prototype.hasOwnProperty.call(a,f)){var g=e?Object.getOwnPropertyDescriptor(a,f):null;if(g&&(g.get||g.set)){Object.defineProperty(d,f,g)}else{d[f]=a[f]}}}d.default=a;if(b){b.set(a,d)}return d}var e=!1,f=function(){if(e){return}document.addEventListener(b.manualCompletionToggled,function(a){var b=parseInt(a.detail.withAvailability);if(b){window.location.reload()}});e=!0};a.init=f});
+//# sourceMappingURL=view.min.js.map
diff --git a/course/amd/build/view.min.js.map b/course/amd/build/view.min.js.map
new file mode 100644
index 00000000000..49e5e62c599
--- /dev/null
+++ b/course/amd/build/view.min.js.map
@@ -0,0 +1 @@
+{"version":3,"sources":["../src/view.js"],"names":["registered","init","document","addEventListener","CourseEvents","manualCompletionToggled","e","withAvailability","parseInt","detail","window","location","reload"],"mappings":"ybAwBA,O,yiBAOIA,CAAAA,CAAU,G,CAKDC,CAAI,CAAG,UAAM,CACtB,GAAID,CAAJ,CAAgB,CACZ,MACH,CAEDE,QAAQ,CAACC,gBAAT,CAA0BC,CAAY,CAACC,uBAAvC,CAAgE,SAACC,CAAD,CAAO,CACnE,GAAMC,CAAAA,CAAgB,CAAGC,QAAQ,CAACF,CAAC,CAACG,MAAF,CAASF,gBAAV,CAAjC,CACA,GAAIA,CAAJ,CAAsB,CAElBG,MAAM,CAACC,QAAP,CAAgBC,MAAhB,EACH,CACJ,CAND,EAOAZ,CAAU,GACb,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * JS module for the course homepage.\n *\n * @module core_course/view\n * @package core_course\n * @copyright 2021 Jun Pataleta \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport * as CourseEvents from 'core_course/events';\n\n/**\n * Whether the event listener has already been registered for this module.\n *\n * @type {boolean}\n */\nlet registered = false;\n\n/**\n * Function to intialise and register event listeners for this module.\n */\nexport const init = () => {\n if (registered) {\n return;\n }\n // Listen for toggled manual completion states of activities.\n document.addEventListener(CourseEvents.manualCompletionToggled, (e) => {\n const withAvailability = parseInt(e.detail.withAvailability);\n if (withAvailability) {\n // Reload the page when the toggled manual completion button has availability conditions linked to it.\n window.location.reload();\n }\n });\n registered = true;\n};\n"],"file":"view.min.js"}
\ No newline at end of file
diff --git a/course/amd/src/manual_completion_toggle.js b/course/amd/src/manual_completion_toggle.js
index 4429bee2d75..20506de5fcd 100644
--- a/course/amd/src/manual_completion_toggle.js
+++ b/course/amd/src/manual_completion_toggle.js
@@ -114,12 +114,14 @@ const toggleManualCompletionState = async(toggleButton) => {
const newToggleButton = replacedNode.pop();
// Build manualCompletionToggled custom event.
+ const withAvailability = toggleButton.getAttribute('data-withavailability');
const toggledEvent = new CustomEvent(CourseEvents.manualCompletionToggled, {
bubbles: true,
detail: {
cmid,
activityname,
completed,
+ withAvailability,
}
});
// Dispatch the manualCompletionToggled custom event.
diff --git a/course/amd/src/view.js b/course/amd/src/view.js
new file mode 100644
index 00000000000..721067ddc60
--- /dev/null
+++ b/course/amd/src/view.js
@@ -0,0 +1,50 @@
+// 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 .
+
+/**
+ * JS module for the course homepage.
+ *
+ * @module core_course/view
+ * @package core_course
+ * @copyright 2021 Jun Pataleta
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+import * as CourseEvents from 'core_course/events';
+
+/**
+ * Whether the event listener has already been registered for this module.
+ *
+ * @type {boolean}
+ */
+let registered = false;
+
+/**
+ * Function to intialise and register event listeners for this module.
+ */
+export const init = () => {
+ if (registered) {
+ return;
+ }
+ // Listen for toggled manual completion states of activities.
+ document.addEventListener(CourseEvents.manualCompletionToggled, (e) => {
+ const withAvailability = parseInt(e.detail.withAvailability);
+ if (withAvailability) {
+ // Reload the page when the toggled manual completion button has availability conditions linked to it.
+ window.location.reload();
+ }
+ });
+ registered = true;
+};
diff --git a/course/classes/output/activity_information.php b/course/classes/output/activity_information.php
index 2d281c31b22..6847ca7b65a 100644
--- a/course/classes/output/activity_information.php
+++ b/course/classes/output/activity_information.php
@@ -29,6 +29,7 @@ use cm_info;
use completion_info;
use context;
use core\activity_dates;
+use core_availability\info;
use core_completion\cm_completion_details;
use core_user;
use core_user\fields;
@@ -90,6 +91,8 @@ class activity_information implements renderable, templatable {
* @return stdClass
*/
protected function build_completion_data(): stdClass {
+ global $CFG;
+
$data = new stdClass();
$data->hascompletion = $this->cmcompletion->has_completion();
@@ -124,6 +127,16 @@ class activity_information implements renderable, templatable {
$data->accessibledescription = get_string($setbylangkey, 'course', $setbydata);
}
+ // Whether the completion of this activity controls the availability of other activities/sections in the course.
+ $data->withavailability = false;
+ $course = $this->cminfo->get_course();
+ // An activity with manual completion tracking which is used to enable access to other activities/sections in
+ // the course needs to refresh the page after having its completion state toggled. This withavailability flag will enable
+ // this functionality on the course homepage. Otherwise, the completion toggling will just happen normally via ajax.
+ if ($this->cmcompletion->has_completion() && !$this->cmcompletion->is_automatic()) {
+ $data->withavailability = !empty($CFG->enableavailability) && info::completion_value_used($course, $this->cminfo->id);
+ }
+
// Build automatic completion details.
$details = [];
foreach ($this->cmcompletion->get_details() as $key => $detail) {
diff --git a/course/templates/completion_manual.mustache b/course/templates/completion_manual.mustache
index c8b38b1ac37..fd1f57b28e9 100644
--- a/course/templates/completion_manual.mustache
+++ b/course/templates/completion_manual.mustache
@@ -28,7 +28,7 @@
}}
{{#istrackeduser}}
{{#overallcomplete}}
-