From 7c249a7d840426a95fda549fdbec989996b287f0 Mon Sep 17 00:00:00 2001 From: Ferran Recio Date: Thu, 20 Jan 2022 12:20:52 +0100 Subject: [PATCH] MDL-73538 core_courseformat: fix course index manual completion The current manual completion button triggers an event to alert the page of any completion change. This event is capture in the course page by the core_courseformat/local/content module but it was ignored when this happens in an activity page. Now the activity header has its own component to capture this event and support possible future reactive actions. --- .../local/content/activity_header.min.js | 2 + .../local/content/activity_header.min.js.map | 1 + .../amd/src/local/content/activity_header.js | 84 +++++++++++++++++++ .../behat/courseindex_completion.feature | 13 ++- lib/classes/output/activity_header.php | 8 ++ lib/outputrequirementslib.php | 9 +- lib/templates/activity_header.mustache | 2 +- 7 files changed, 113 insertions(+), 6 deletions(-) create mode 100644 course/format/amd/build/local/content/activity_header.min.js create mode 100644 course/format/amd/build/local/content/activity_header.min.js.map create mode 100644 course/format/amd/src/local/content/activity_header.js diff --git a/course/format/amd/build/local/content/activity_header.min.js b/course/format/amd/build/local/content/activity_header.min.js new file mode 100644 index 00000000000..2cc101f8448 --- /dev/null +++ b/course/format/amd/build/local/content/activity_header.min.js @@ -0,0 +1,2 @@ +define ("core_courseformat/local/content/activity_header",["exports","core/reactive","core_courseformat/courseeditor","core_course/events"],function(a,b,c,d){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;d=f(d);function e(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;e=function(){return a};return a}function f(a){if(a&&a.__esModule){return a}if(null===a||"object"!==g(a)&&"function"!=typeof a){return{default:a}}var b=e();if(b&&b.has(a)){return b.get(a)}var c={},d=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var f in a){if(Object.prototype.hasOwnProperty.call(a,f)){var h=d?Object.getOwnPropertyDescriptor(a,f):null;if(h&&(h.get||h.set)){Object.defineProperty(c,f,h)}else{c[f]=a[f]}}}c.default=a;if(b){b.set(a,c)}return c}function g(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){g=function(a){return typeof a}}else{g=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return g(a)}function h(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function i(a,b){for(var c=0,d;c.\n\n/**\n * The activity header component.\n *\n * @module core_courseformat/local/content/activity_header\n * @class core_courseformat/local/content/activity_header\n * @copyright 2021 Ferran Recio \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 * as CourseEvents from 'core_course/events';\n\n// Global page selectors.\nconst SELECTORS = {\n ACTIVITY_HEADER: `[data-for='page-activity-header']`,\n};\n\nexport default class Component extends BaseComponent {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'activity_header';\n }\n\n /**\n * Static method to create a component instance form 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 const elementselector = (target) ? target : SELECTORS.ACTIVITY_HEADER;\n return new Component({\n element: document.querySelector(elementselector),\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 CourseEvents.manualCompletionToggled,\n this._completionHandler\n );\n }\n\n /**\n * Activity manual completion listener.\n *\n * @param {Event} event the custom event\n * @param {object} event.detail the event details\n */\n _completionHandler({detail}) {\n if (detail === undefined) {\n return;\n }\n this.reactive.dispatch('cmCompletion', [detail.cmid], detail.completed);\n }\n}\n"],"file":"activity_header.min.js"} \ No newline at end of file diff --git a/course/format/amd/src/local/content/activity_header.js b/course/format/amd/src/local/content/activity_header.js new file mode 100644 index 00000000000..ec5ac52c5f1 --- /dev/null +++ b/course/format/amd/src/local/content/activity_header.js @@ -0,0 +1,84 @@ +// 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 . + +/** + * The activity header component. + * + * @module core_courseformat/local/content/activity_header + * @class core_courseformat/local/content/activity_header + * @copyright 2021 Ferran Recio + * @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 * as CourseEvents from 'core_course/events'; + +// Global page selectors. +const SELECTORS = { + ACTIVITY_HEADER: `[data-for='page-activity-header']`, +}; + +export default class Component extends BaseComponent { + + /** + * Constructor hook. + */ + create() { + // Optional component name for debugging. + this.name = 'activity_header'; + } + + /** + * Static method to create a component instance form 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) { + const elementselector = (target) ? target : SELECTORS.ACTIVITY_HEADER; + return new Component({ + element: document.querySelector(elementselector), + reactive: getCurrentCourseEditor(), + selectors + }); + } + + /** + * Initial state ready method. + */ + stateReady() { + // Capture completion events. + this.addEventListener( + this.element, + CourseEvents.manualCompletionToggled, + this._completionHandler + ); + } + + /** + * Activity manual completion listener. + * + * @param {Event} event the custom event + * @param {object} event.detail the event details + */ + _completionHandler({detail}) { + if (detail === undefined) { + return; + } + this.reactive.dispatch('cmCompletion', [detail.cmid], detail.completed); + } +} diff --git a/course/format/tests/behat/courseindex_completion.feature b/course/format/tests/behat/courseindex_completion.feature index 8a842c1bcb2..8d10341a7d6 100644 --- a/course/format/tests/behat/courseindex_completion.feature +++ b/course/format/tests/behat/courseindex_completion.feature @@ -40,7 +40,7 @@ Feature: Course index completion icons And "To do" "icon" should exist in the "courseindex-content" "region" @javascript - Scenario: Manual completion shoudl update the course index completion + Scenario: Manual completion should update the course index completion Given I am on the "C1" "Course" page logged in as "student1" And "To do" "icon" should exist in the "courseindex-content" "region" When I press "Mark as done" @@ -50,6 +50,17 @@ Feature: Course index completion icons And I wait until "Mark as done" "button" exists And "To do" "icon" should exist in the "courseindex-content" "region" + @javascript + Scenario: Manual completion in an activity page should update the course index + Given I am on the "sample1" "Activity" page logged in as "student1" + And "To do" "icon" should exist in the "courseindex-content" "region" + When I press "Mark as done" + And I wait until "Done" "button" exists + Then "Done" "icon" should exist in the "courseindex-content" "region" + And I press "Done" + And I wait until "Mark as done" "button" exists + And "To do" "icon" should exist in the "courseindex-content" "region" + @javascript Scenario: Refresh the page should keep the completion consistent Given I am on the "C1" "Course" page logged in as "student1" diff --git a/lib/classes/output/activity_header.php b/lib/classes/output/activity_header.php index af1bd2bf904..7af3340b0da 100644 --- a/lib/classes/output/activity_header.php +++ b/lib/classes/output/activity_header.php @@ -174,6 +174,14 @@ class activity_header implements \renderable, \templatable { $completion = $output->activity_information($this->page->cm, $completiondetails, $activitydates); } + $format = course_get_format($this->page->course); + if ($format->supports_components()) { + $this->page->requires->js_call_amd( + 'core_courseformat/local/content/activity_header', + 'init' + ); + } + return [ 'title' => $this->title, 'description' => $this->description, diff --git a/lib/outputrequirementslib.php b/lib/outputrequirementslib.php index b9a835cd861..afe8b9842b7 100644 --- a/lib/outputrequirementslib.php +++ b/lib/outputrequirementslib.php @@ -1041,11 +1041,12 @@ class page_requirements_manager { public function js_call_amd($fullmodule, $func = null, $params = array()) { global $CFG; - list($component, $module) = explode('/', $fullmodule, 2); + $modulepath = explode('/', $fullmodule); - $component = clean_param($component, PARAM_COMPONENT); - $module = clean_param($module, PARAM_ALPHANUMEXT); - $modname = "{$component}/{$module}"; + $modname = clean_param(array_shift($modulepath), PARAM_COMPONENT); + foreach ($modulepath as $module) { + $modname .= '/' . clean_param($module, PARAM_ALPHANUMEXT); + } $functioncode = []; if ($func !== null) { diff --git a/lib/templates/activity_header.mustache b/lib/templates/activity_header.mustache index 42339b6748f..149f32adfa9 100644 --- a/lib/templates/activity_header.mustache +++ b/lib/templates/activity_header.mustache @@ -70,7 +70,7 @@ {{#title}}

{{{title}}}

{{/title}} -
+
{{#completion}} {{#str}} overallaggregation, completion {{/str}} {{{completion}}}