From 6a230a0bcadfda0726aeb323576e06ff69fa52e2 Mon Sep 17 00:00:00 2001 From: ferran Date: Mon, 27 Jan 2025 13:01:48 +0100 Subject: [PATCH] MDL-83872 core_courseformat: prepare completion button for reuse The current completion button has specific templates only for the course view page. However, from now on this component may be used in other places and it needs a main template to be rendered in a standard way. --- .upgradenotes/MDL-83872-2025021312020327.yml | 10 +++ .../amd/build/manual_completion_toggle.min.js | 2 +- .../build/manual_completion_toggle.min.js.map | 2 +- course/amd/src/manual_completion_toggle.js | 1 + course/classes/output/activity_completion.php | 8 ++- .../output/local/content/cm/completion.php | 23 ++++++- .../local/content/cm/activity_info.mustache | 22 +----- .../local/content/cm/completion.mustache | 69 +++++++++++++++++++ course/templates/completion_manual.mustache | 60 ++++++++++------ 9 files changed, 151 insertions(+), 46 deletions(-) create mode 100644 .upgradenotes/MDL-83872-2025021312020327.yml create mode 100644 course/format/templates/local/content/cm/completion.mustache diff --git a/.upgradenotes/MDL-83872-2025021312020327.yml b/.upgradenotes/MDL-83872-2025021312020327.yml new file mode 100644 index 00000000000..ce0d1f9bcd3 --- /dev/null +++ b/.upgradenotes/MDL-83872-2025021312020327.yml @@ -0,0 +1,10 @@ +issueNumber: MDL-83872 +notes: + core_course: + - message: >- + Now the core_courseformat\local\content\cm\completion output is more + reusable. All the HTML has been moved to its own mustache file, + and the output class has a new set_smallbutton method to decide wether + to rendered it as a small button (like in the course page) or as a + normal one (for other types of pages). + type: improved diff --git a/course/amd/build/manual_completion_toggle.min.js b/course/amd/build/manual_completion_toggle.min.js index 18c7cdd69a9..4a1e5f346b6 100644 --- a/course/amd/build/manual_completion_toggle.min.js +++ b/course/amd/build/manual_completion_toggle.min.js @@ -6,6 +6,6 @@ define("core_course/manual_completion_toggle",["exports","core/templates","core/ * @module core_course/manual_completion_toggle * @copyright 2021 Jun Pataleta * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_templates=_interopRequireDefault(_templates),_notification=_interopRequireDefault(_notification),CourseEvents=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(CourseEvents),_pending=_interopRequireDefault(_pending);const SELECTORS_MANUAL_TOGGLE="button[data-action=toggle-manual-completion]",TOGGLE_TYPES_TOGGLE_MARK_DONE="manual:mark-done";let registered=!1;_exports.init=()=>{registered||(document.addEventListener("click",(e=>{const toggleButton=e.target.closest(SELECTORS_MANUAL_TOGGLE);toggleButton&&(e.preventDefault(),toggleManualCompletionState(toggleButton).catch(_notification.default.exception))})),registered=!0)};const toggleManualCompletionState=async toggleButton=>{const pendingPromise=new _pending.default("core_course:toggleManualCompletionState"),originalInnerHtml=toggleButton.innerHTML;toggleButton.setAttribute("disabled","disabled");const toggleType=toggleButton.getAttribute("data-toggletype"),cmid=toggleButton.getAttribute("data-cmid"),activityname=toggleButton.getAttribute("data-activityname"),completed=toggleType===TOGGLE_TYPES_TOGGLE_MARK_DONE;_templates.default.renderForPromise("core/loading",{}).then((loadingHtml=>{_templates.default.replaceNodeContents(toggleButton,loadingHtml,"")})).catch((()=>{}));try{await(0,_repository.toggleManualCompletion)(cmid,completed);const templateContext={cmid:cmid,activityname:activityname,overallcomplete:completed,overallincomplete:!completed,istrackeduser:!0},renderObject=await _templates.default.renderForPromise("core_course/completion_manual",templateContext),newToggleButton=(await _templates.default.replaceNode(toggleButton,renderObject.html,renderObject.js)).pop(),withAvailability=toggleButton.getAttribute("data-withavailability"),toggledEvent=new CustomEvent(CourseEvents.manualCompletionToggled,{bubbles:!0,detail:{cmid:cmid,activityname:activityname,completed:completed,withAvailability:withAvailability}});newToggleButton.dispatchEvent(toggledEvent)}catch(exception){toggleButton.removeAttribute("disabled"),toggleButton.innerHTML=originalInnerHtml,_notification.default.exception(exception)}pendingPromise.resolve()}})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_templates=_interopRequireDefault(_templates),_notification=_interopRequireDefault(_notification),CourseEvents=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(CourseEvents),_pending=_interopRequireDefault(_pending);const SELECTORS_MANUAL_TOGGLE="button[data-action=toggle-manual-completion]",TOGGLE_TYPES_TOGGLE_MARK_DONE="manual:mark-done";let registered=!1;_exports.init=()=>{registered||(document.addEventListener("click",(e=>{const toggleButton=e.target.closest(SELECTORS_MANUAL_TOGGLE);toggleButton&&(e.preventDefault(),toggleManualCompletionState(toggleButton).catch(_notification.default.exception))})),registered=!0)};const toggleManualCompletionState=async toggleButton=>{const pendingPromise=new _pending.default("core_course:toggleManualCompletionState"),originalInnerHtml=toggleButton.innerHTML;toggleButton.setAttribute("disabled","disabled");const toggleType=toggleButton.getAttribute("data-toggletype"),cmid=toggleButton.getAttribute("data-cmid"),activityname=toggleButton.getAttribute("data-activityname"),completed=toggleType===TOGGLE_TYPES_TOGGLE_MARK_DONE;_templates.default.renderForPromise("core/loading",{}).then((loadingHtml=>{_templates.default.replaceNodeContents(toggleButton,loadingHtml,"")})).catch((()=>{}));try{await(0,_repository.toggleManualCompletion)(cmid,completed);const templateContext={cmid:cmid,activityname:activityname,overallcomplete:completed,overallincomplete:!completed,istrackeduser:!0,normalbutton:!toggleButton.classList.contains("btn-sm")},renderObject=await _templates.default.renderForPromise("core_course/completion_manual",templateContext),newToggleButton=(await _templates.default.replaceNode(toggleButton,renderObject.html,renderObject.js)).pop(),withAvailability=toggleButton.getAttribute("data-withavailability"),toggledEvent=new CustomEvent(CourseEvents.manualCompletionToggled,{bubbles:!0,detail:{cmid:cmid,activityname:activityname,completed:completed,withAvailability:withAvailability}});newToggleButton.dispatchEvent(toggledEvent)}catch(exception){toggleButton.removeAttribute("disabled"),toggleButton.innerHTML=originalInnerHtml,_notification.default.exception(exception)}pendingPromise.resolve()}})); //# sourceMappingURL=manual_completion_toggle.min.js.map \ No newline at end of file diff --git a/course/amd/build/manual_completion_toggle.min.js.map b/course/amd/build/manual_completion_toggle.min.js.map index 7b22322106c..25a64a0a659 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,"file":"manual_completion_toggle.min.js","sources":["../src/manual_completion_toggle.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * 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 * @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';\nimport Pending from 'core/pending';\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 const pendingPromise = new Pending('core_course:toggleManualCompletionState');\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 Templates.renderForPromise('core/loading', {})\n .then((loadingHtml) => {\n Templates.replaceNodeContents(toggleButton, loadingHtml, '');\n return;\n }).catch(() => {});\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 pendingPromise.resolve();\n};\n"],"names":["SELECTORS","TOGGLE_TYPES","registered","document","addEventListener","e","toggleButton","target","closest","preventDefault","toggleManualCompletionState","catch","Notification","exception","async","pendingPromise","Pending","originalInnerHtml","innerHTML","setAttribute","toggleType","getAttribute","cmid","activityname","completed","renderForPromise","then","loadingHtml","replaceNodeContents","templateContext","overallcomplete","overallincomplete","istrackeduser","renderObject","Templates","newToggleButton","replaceNode","html","js","pop","withAvailability","toggledEvent","CustomEvent","CourseEvents","manualCompletionToggled","bubbles","detail","dispatchEvent","removeAttribute","resolve"],"mappings":";;;;;;;;44BAmCMA,wBACa,+CAQbC,8BACgB,uBASlBC,YAAa,gBAKG,KACZA,aAGJC,SAASC,iBAAiB,SAAUC,UAC1BC,aAAeD,EAAEE,OAAOC,QAAQR,yBAClCM,eACAD,EAAEI,iBACFC,4BAA4BJ,cAAcK,MAAMC,sBAAaC,eAGrEX,YAAa,UASXQ,4BAA8BI,MAAAA,qBAC1BC,eAAiB,IAAIC,iBAAQ,2CAE7BC,kBAAoBX,aAAaY,UAGvCZ,aAAaa,aAAa,WAAY,kBAGhCC,WAAad,aAAae,aAAa,mBACvCC,KAAOhB,aAAae,aAAa,aACjCE,aAAejB,aAAae,aAAa,qBAEzCG,UAAYJ,aAAenB,iDAGvBwB,iBAAiB,eAAgB,IAC1CC,MAAMC,iCACOC,oBAAoBtB,aAAcqB,YAAa,OAE1DhB,OAAM,mBAIC,sCAAuBW,KAAME,iBAG7BK,gBAAkB,CACpBP,KAAMA,KACNC,aAAcA,aACdO,gBAAiBN,UACjBO,mBAAoBP,UACpBQ,eAAe,GAEbC,mBAAqBC,mBAAUT,iBAAiB,gCAAiCI,iBAIjFM,uBADqBD,mBAAUE,YAAY9B,aAAc2B,aAAaI,KAAMJ,aAAaK,KAC1DC,MAG/BC,iBAAmBlC,aAAae,aAAa,yBAC7CoB,aAAe,IAAIC,YAAYC,aAAaC,wBAAyB,CACvEC,SAAS,EACTC,OAAQ,CACJxB,KAAAA,KACAC,aAAAA,aACAC,UAAAA,UACAgB,iBAAAA,oBAIRL,gBAAgBY,cAAcN,cAEhC,MAAO5B,WAELP,aAAa0C,gBAAgB,YAC7B1C,aAAaY,UAAYD,wCAGZJ,UAAUA,WAE3BE,eAAekC"} \ No newline at end of file +{"version":3,"file":"manual_completion_toggle.min.js","sources":["../src/manual_completion_toggle.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * 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 * @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';\nimport Pending from 'core/pending';\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 const pendingPromise = new Pending('core_course:toggleManualCompletionState');\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 Templates.renderForPromise('core/loading', {})\n .then((loadingHtml) => {\n Templates.replaceNodeContents(toggleButton, loadingHtml, '');\n return;\n }).catch(() => {});\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 normalbutton: !toggleButton.classList.contains('btn-sm'),\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 pendingPromise.resolve();\n};\n"],"names":["SELECTORS","TOGGLE_TYPES","registered","document","addEventListener","e","toggleButton","target","closest","preventDefault","toggleManualCompletionState","catch","Notification","exception","async","pendingPromise","Pending","originalInnerHtml","innerHTML","setAttribute","toggleType","getAttribute","cmid","activityname","completed","renderForPromise","then","loadingHtml","replaceNodeContents","templateContext","overallcomplete","overallincomplete","istrackeduser","normalbutton","classList","contains","renderObject","Templates","newToggleButton","replaceNode","html","js","pop","withAvailability","toggledEvent","CustomEvent","CourseEvents","manualCompletionToggled","bubbles","detail","dispatchEvent","removeAttribute","resolve"],"mappings":";;;;;;;;44BAmCMA,wBACa,+CAQbC,8BACgB,uBASlBC,YAAa,gBAKG,KACZA,aAGJC,SAASC,iBAAiB,SAAUC,UAC1BC,aAAeD,EAAEE,OAAOC,QAAQR,yBAClCM,eACAD,EAAEI,iBACFC,4BAA4BJ,cAAcK,MAAMC,sBAAaC,eAGrEX,YAAa,UASXQ,4BAA8BI,MAAAA,qBAC1BC,eAAiB,IAAIC,iBAAQ,2CAE7BC,kBAAoBX,aAAaY,UAGvCZ,aAAaa,aAAa,WAAY,kBAGhCC,WAAad,aAAae,aAAa,mBACvCC,KAAOhB,aAAae,aAAa,aACjCE,aAAejB,aAAae,aAAa,qBAEzCG,UAAYJ,aAAenB,iDAGvBwB,iBAAiB,eAAgB,IAC1CC,MAAMC,iCACOC,oBAAoBtB,aAAcqB,YAAa,OAE1DhB,OAAM,mBAIC,sCAAuBW,KAAME,iBAG7BK,gBAAkB,CACpBP,KAAMA,KACNC,aAAcA,aACdO,gBAAiBN,UACjBO,mBAAoBP,UACpBQ,eAAe,EACfC,cAAe3B,aAAa4B,UAAUC,SAAS,WAE7CC,mBAAqBC,mBAAUZ,iBAAiB,gCAAiCI,iBAIjFS,uBADqBD,mBAAUE,YAAYjC,aAAc8B,aAAaI,KAAMJ,aAAaK,KAC1DC,MAG/BC,iBAAmBrC,aAAae,aAAa,yBAC7CuB,aAAe,IAAIC,YAAYC,aAAaC,wBAAyB,CACvEC,SAAS,EACTC,OAAQ,CACJ3B,KAAAA,KACAC,aAAAA,aACAC,UAAAA,UACAmB,iBAAAA,oBAIRL,gBAAgBY,cAAcN,cAEhC,MAAO/B,WAELP,aAAa6C,gBAAgB,YAC7B7C,aAAaY,UAAYD,wCAGZJ,UAAUA,WAE3BE,eAAeqC"} \ 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 12200f7a878..ac32ab3280a 100644 --- a/course/amd/src/manual_completion_toggle.js +++ b/course/amd/src/manual_completion_toggle.js @@ -110,6 +110,7 @@ const toggleManualCompletionState = async(toggleButton) => { overallcomplete: completed, overallincomplete: !completed, istrackeduser: true, // We know that we're tracking completion for this user given the presence of this button. + normalbutton: !toggleButton.classList.contains('btn-sm'), }; const renderObject = await Templates.renderForPromise('core_course/completion_manual', templateContext); diff --git a/course/classes/output/activity_completion.php b/course/classes/output/activity_completion.php index b2475902edc..4022841469a 100644 --- a/course/classes/output/activity_completion.php +++ b/course/classes/output/activity_completion.php @@ -42,8 +42,12 @@ class activity_completion implements renderable, templatable { * @param cm_completion_details $cmcompletion The course module information. */ public function __construct( + /** @var cm_info $cminfo the activity cm_info. */ protected cm_info $cminfo, - protected cm_completion_details $cmcompletion + /** @var cm_completion_details $cmcompletion the activity completion details. */ + protected cm_completion_details $cmcompletion, + /** @var bool $smallbutton if the button is rendered small (like in course page). */ + protected bool $smallbutton = true, ) { } @@ -84,6 +88,8 @@ class activity_completion implements renderable, templatable { 'overrideby' => $overrideby, 'completiondetails' => $this->get_completion_details($overrideby), 'accessibledescription' => $this->get_accessible_description($overrideby, $overallcompletion), + // For backward compatibility, the template uses small button by default when not set normal size. + 'normalbutton' => !$this->smallbutton, ]; } diff --git a/course/format/classes/output/local/content/cm/completion.php b/course/format/classes/output/local/content/cm/completion.php index ce82cb6082b..32252e865d3 100644 --- a/course/format/classes/output/local/content/cm/completion.php +++ b/course/format/classes/output/local/content/cm/completion.php @@ -38,6 +38,9 @@ class completion implements named_templatable, renderable { use courseformat_named_templatable; + /** @var bool $smallbutton if the button is rendered small (like in course page). */ + private bool $smallbutton = true; + /** * Constructor. * @@ -52,6 +55,19 @@ class completion implements named_templatable, renderable { ) { } + /** + * Set if the button is rendered small. + * + * By default, the button is rendered small like in the course page. However, + * in other pages like the activities overview, the button is rendered like + * a regular button. + * + * @param bool $smallbutton if the button is rendered small (like in course page). + */ + public function set_smallbutton(bool $smallbutton): void { + $this->smallbutton = $smallbutton; + } + /** * Export this data so it can be used as the context for a mustache template. * @@ -76,7 +92,7 @@ class completion implements named_templatable, renderable { return null; } - $completion = new activity_completion($this->mod, $completiondetails); + $completion = new activity_completion($this->mod, $completiondetails, $this->smallbutton); $completiondata = $completion->export_for_template($output); if ($completiondata->isautomatic || ($completiondata->ismanual && !$completiondata->istrackeduser)) { @@ -108,6 +124,7 @@ class completion implements named_templatable, renderable { $buttoncontent = get_string('completionmenuitem', 'completion'); $buttonclass = ''; if ($completioninfo->istrackeduser) { + $buttonclass = 'btn-outline-secondary'; $buttoncontent = get_string('todo', 'completion'); if ($completioninfo->overallcomplete) { $buttoncontent = $output->pix_icon('i/checked', '') . " " . get_string('completion_manual:done', 'core_course'); @@ -115,9 +132,11 @@ class completion implements named_templatable, renderable { } } + $buttonclass .= $this->smallbutton ? ' btn-sm' : ''; + $completiondialog = new dropdown_dialog($buttoncontent, $dialogcontent, [ 'classes' => 'completion-dropdown', - 'buttonclasses' => 'btn btn-sm dropdown-toggle icon-no-margin ' . $buttonclass, + 'buttonclasses' => 'btn dropdown-toggle icon-no-margin ' . $buttonclass, 'dropdownposition' => dropdown_dialog::POSITION['end'], ]); diff --git a/course/format/templates/local/content/cm/activity_info.mustache b/course/format/templates/local/content/cm/activity_info.mustache index e355cc6bd56..3733c23e7fd 100644 --- a/course/format/templates/local/content/cm/activity_info.mustache +++ b/course/format/templates/local/content/cm/activity_info.mustache @@ -36,7 +36,7 @@ "isautomatic": true, "showmanualcompletion": true, "completiondetails": [ - { + { "statuscomplete": 1, "description": "Viewed" }, @@ -48,23 +48,5 @@ } }}
- {{#uservisible}} -
- {{! Completion dropdown dialog }} - {{#completiondialog}} -
- {{> core/local/dropdown/dialog }} -
- {{/completiondialog}} - - {{! Manual completion button }} - {{#showmanualcompletion}} - {{#istrackeduser}} - {{$ core_course/completion_manual }} - {{> core_course/completion_manual }} - {{/ core_course/completion_manual }} - {{/istrackeduser}} - {{/showmanualcompletion}} -
- {{/uservisible}} + {{> core_courseformat/local/content/cm/completion }}
diff --git a/course/format/templates/local/content/cm/completion.mustache b/course/format/templates/local/content/cm/completion.mustache new file mode 100644 index 00000000000..ef53768fedb --- /dev/null +++ b/course/format/templates/local/content/cm/completion.mustache @@ -0,0 +1,69 @@ +{{! + 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 . +}} +{{! + @template core_courseformat/local/content/cm/completion + + TODO describe template completion + + Example context (json): + { + "cmid": 123, + "activityname": "Course announcements", + "hascompletion": true, + "uservisible": true, + "completiondialog": { + "buttoncontent": "To do", + "dialogcontent": "You must view this activity to complete it.", + "buttonclasses": "btn btn-sm btn-outline-secondary dropdown-toggle", + "classes": "completion-dropdown" + }, + "isautomatic": true, + "showmanualcompletion": true, + "completiondetails": [ + { + "statuscomplete": 1, + "description": "Viewed" + }, + { + "statusincomplete": 1, + "description": "Receive a grade" + } + ] + } +}} +{{#uservisible}} +
+ {{! Completion dropdown dialog }} + {{#completiondialog}} +
+ {{> core/local/dropdown/dialog }} +
+ {{/completiondialog}} + + {{! Manual completion button }} + {{#showmanualcompletion}} + {{#istrackeduser}} + {{$ core_course/completion_manual }} + {{> core_course/completion_manual }} + {{/ core_course/completion_manual }} + {{/istrackeduser}} + {{/showmanualcompletion}} +
+{{/uservisible}} diff --git a/course/templates/completion_manual.mustache b/course/templates/completion_manual.mustache index cfc78571d51..8cfe1b387f4 100644 --- a/course/templates/completion_manual.mustache +++ b/course/templates/completion_manual.mustache @@ -23,45 +23,63 @@ { "cmid": 0, "overallcomplete": true, - "overallincomplete": false + "overallincomplete": false, + "normalbutton": true } }} {{#istrackeduser}} {{#overallcomplete}} - {{/overallcomplete}} {{#overallincomplete}} - {{/overallincomplete}} {{/istrackeduser}} {{^istrackeduser}} - {{/istrackeduser}} {{#js}} require(['core_course/manual_completion_toggle'], toggle => { - toggle.init() + toggle.init(); }); {{/js}}