MDL-70821 course: Cover availability conditions for manual completion

When an activity has manual completion tracking, pressing the manual
completion checkbox reloads the page after toggling the completion
state when the activity is linked to availability conditions.
The "Mark as done" button needs to mimic this behaviour as well.

The approach being taken here is to add a core_course/view JS module
for the course homepage which listens for the manualCompletionToggled
event and reloads the page when the activity module has availability
conditions tied to it.

Perhaps for future development, instead of reloading the page, the
container of the restricted course sections/activities can reloaded via
AJAX as well.
This commit is contained in:
Jun Pataleta 2021-03-23 20:18:38 +08:00
parent 642059155c
commit 13f88df351
9 changed files with 77 additions and 4 deletions

View File

@ -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

File diff suppressed because one or more lines are too long

2
course/amd/build/view.min.js vendored Normal file
View File

@ -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

View File

@ -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 <http://www.gnu.org/licenses/>.\n\n/**\n * JS module for the course homepage.\n *\n * @module core_course/view\n * @package core_course\n * @copyright 2021 Jun Pataleta <jun@moodle.com>\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"}

View File

@ -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.

50
course/amd/src/view.js Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
/**
* JS module for the course homepage.
*
* @module core_course/view
* @package core_course
* @copyright 2021 Jun Pataleta <jun@moodle.com>
* @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;
};

View File

@ -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) {

View File

@ -28,7 +28,7 @@
}}
{{#istrackeduser}}
{{#overallcomplete}}
<button class="btn btn-outline-success" data-action="toggle-manual-completion" data-toggletype="manual:undo" data-cmid="{{cmid}}" data-activityname="{{activityname}}" {{!
<button class="btn btn-outline-success" data-action="toggle-manual-completion" data-toggletype="manual:undo" data-cmid="{{cmid}}" data-activityname="{{activityname}}" data-withavailability="{{withavailability}}" {{!
}}{{#accessibledescription}}{{!
}}title="{{.}}" {{!
}}aria-label="{{.}}" {{!
@ -41,7 +41,7 @@
</button>
{{/overallcomplete}}
{{#overallincomplete}}
<button class="btn btn-outline-secondary" data-action="toggle-manual-completion" data-toggletype="manual:mark-done" data-cmid="{{cmid}}" data-activityname="{{activityname}}" {{!
<button class="btn btn-outline-secondary" data-action="toggle-manual-completion" data-toggletype="manual:mark-done" data-cmid="{{cmid}}" data-activityname="{{activityname}}" data-withavailability="{{withavailability}}" {{!
}}{{#accessibledescription}}{{!
}}title="{{.}}" {{!
}}aria-label="{{.}}" {{!

View File

@ -314,4 +314,9 @@
$PAGE->requires->js_call_amd('core_course/downloadcontent', 'init');
}
// Load the view JS module if completion tracking is enabled for this course.
if ($completion->is_enabled()) {
$PAGE->requires->js_call_amd('core_course/view', 'init');
}
echo $OUTPUT->footer();