Merge branch 'MDL-67743-master-final' of git://github.com/andrewnicols/moodle

This commit is contained in:
Jun Pataleta 2020-05-27 14:51:25 +08:00
commit d5eec2a2fd
40 changed files with 1717 additions and 29 deletions

View File

@ -22,7 +22,19 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['addcondition'] = 'Add condition';
$string['adverbfor_and'] = 'and';
$string['adverbfor_andnot'] = 'and';
$string['adverbfor_or'] = 'or';
$string['applyfilters'] = 'Apply filters';
$string['clearfilterrow'] = 'Remove filter row';
$string['clearfilters'] = 'Clear filters';
$string['countparticipantsfound'] = '{$a} participants found';
$string['filtersetmatchdescription'] = 'How multiple filters should be combined';
$string['match'] = 'Match';
$string['matchofthefollowing'] = 'of the following:';
$string['placeholdertypeorselect'] = 'Type or select...';
$string['placeholdertype'] = 'Type...';
$string['privacy:courserequestpath'] = 'Requested courses';
$string['privacy:descriptionpath'] = 'Profile description';
$string['privacy:devicespath'] = 'User devices';
@ -126,6 +138,8 @@ $string['privacy:passwordresetpath'] = 'Password resets';
$string['privacy:profileimagespath'] = 'Profile images';
$string['privacy:privatefilespath'] = 'Private files';
$string['privacy:sessionpath'] = 'Session data';
$string['filterbykeyword'] = 'Keyword';
$string['selectfiltertype'] = 'Select';
$string['target:upcomingactivitiesdue'] = 'Upcoming activities due';
$string['target:upcomingactivitiesdue_help'] = 'This target generates reminders for upcoming activities due.';
$string['target:upcomingactivitiesdueinfo'] = 'All upcoming activities due insights are listed here. These students have received these insights directly.';

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -114,7 +114,7 @@ function($, log, str, templates, notification, LoadingIcon) {
});
var context = $.extend({items: items}, options, state);
// Render the template.
return templates.render('core/form_autocomplete_selection_items', context)
return templates.render(options.templates.items, context)
.then(function(html, js) {
// Add it to the page.
templates.replaceNodeContents(newSelection, html, js);
@ -970,10 +970,11 @@ function($, log, str, templates, notification, LoadingIcon) {
* @param {Boolean} showSuggestions - If suggestions should be shown
* @param {String} noSelectionString - Text to display when there is no selection
* @param {Boolean} closeSuggestionsOnSelect - Whether to close the suggestions immediately after making a selection.
* @param {Object} templateOverrides A set of templates to use instead of the standard templates
* @return {Promise}
*/
enhance: function(selector, tags, ajax, placeholder, caseSensitive, showSuggestions, noSelectionString,
closeSuggestionsOnSelect) {
closeSuggestionsOnSelect, templateOverrides) {
// Set some default values.
var options = {
selector: selector,
@ -982,7 +983,14 @@ function($, log, str, templates, notification, LoadingIcon) {
placeholder: placeholder,
caseSensitive: false,
showSuggestions: true,
noSelectionString: noSelectionString
noSelectionString: noSelectionString,
templates: $.extend({
input: 'core/form_autocomplete_input',
items: 'core/form_autocomplete_selection_items',
layout: 'core/form_autocomplete_layout',
selection: 'core/form_autocomplete_selection',
suggestions: 'core/form_autocomplete_suggestions',
}, templateOverrides),
};
var pendingKey = 'autocomplete-setup-' + selector;
M.util.js_pending(pendingKey);
@ -1058,27 +1066,35 @@ function($, log, str, templates, notification, LoadingIcon) {
// Collect rendered inline JS to be executed once the HTML is shown.
var collectedjs = '';
var renderInput = templates.render('core/form_autocomplete_input', context).then(function(html, js) {
collectedjs += js;
return html;
var renderLayout = templates.render(options.templates.layout, {})
.then(function(html) {
return $(html);
});
var renderDatalist = templates.render('core/form_autocomplete_suggestions', context).then(function(html, js) {
var renderInput = templates.render(options.templates.input, context).then(function(html, js) {
collectedjs += js;
return html;
return $(html);
});
var renderSelection = templates.render('core/form_autocomplete_selection', context).then(function(html, js) {
var renderDatalist = templates.render(options.templates.suggestions, context).then(function(html, js) {
collectedjs += js;
return html;
return $(html);
});
return $.when(renderInput, renderDatalist, renderSelection)
.then(function(input, suggestions, selection) {
var renderSelection = templates.render(options.templates.selection, context).then(function(html, js) {
collectedjs += js;
return $(html);
});
return $.when(renderLayout, renderInput, renderDatalist, renderSelection)
.then(function(layout, input, suggestions, selection) {
originalSelect.hide();
originalSelect.after(suggestions);
originalSelect.after(input);
originalSelect.after(selection);
var container = originalSelect.parent();
container.append(layout);
container.find('[data-region="form_autocomplete-input"]').replaceWith(input);
container.find('[data-region="form_autocomplete-suggestions"]').replaceWith(suggestions);
container.find('[data-region="form_autocomplete-selection"]').replaceWith(selection);
templates.runTemplateJS(collectedjs);

View File

@ -876,6 +876,7 @@ define([
* @param {String} newHTML - HTML to insert / replace.
* @param {String} newJS - Javascript to run after the insertion.
* @param {Boolean} replaceChildNodes - Replace only the childnodes, alternative is to replace the entire node.
* @return {Array} The list of new DOM Nodes
*/
var domReplace = function(element, newHTML, newJS, replaceChildNodes) {
var replaceNode = $(element);
@ -904,7 +905,11 @@ define([
runTemplateJS(newJS);
// Notify all filters about the new content.
event.notifyFilterContentUpdated(newNodes);
return newNodes.get();
}
return [];
};
/**
@ -1043,17 +1048,23 @@ define([
* @param {jQuery|String} element - Element or selector to prepend HTML to
* @param {String} html - HTML to prepend
* @param {String} js - Javascript to run after we prepend the html
* @return {Array} The list of new DOM Nodes
*/
var domPrepend = function(element, html, js) {
var node = $(element);
if (node.length) {
// Prepend the html.
node.prepend(html);
var newContent = $(html);
node.prepend(newContent);
// Run any javascript associated with the new HTML.
runTemplateJS(js);
// Notify all filters about the new content.
event.notifyFilterContentUpdated(node);
return newContent.get();
}
return [];
};
/**
@ -1064,17 +1075,23 @@ define([
* @param {jQuery|String} element - Element or selector to append HTML to
* @param {String} html - HTML to append
* @param {String} js - Javascript to run after we append the html
* @return {Array} The list of new DOM Nodes
*/
var domAppend = function(element, html, js) {
var node = $(element);
if (node.length) {
// Append the html.
node.append(html);
var newContent = $(html);
node.append(newContent);
// Run any javascript associated with the new HTML.
runTemplateJS(js);
// Notify all filters about the new content.
event.notifyFilterContentUpdated(node);
return newContent.get();
}
return [];
};
return /** @alias module:core/templates */ {
@ -1175,9 +1192,10 @@ define([
* @param {JQuery} element - Element or selector to replace.
* @param {String} newHTML - HTML to insert / replace.
* @param {String} newJS - Javascript to run after the insertion.
* @return {Array} The list of new DOM Nodes
*/
replaceNodeContents: function(element, newHTML, newJS) {
domReplace(element, newHTML, newJS, true);
return domReplace(element, newHTML, newJS, true);
},
/**
@ -1187,9 +1205,10 @@ define([
* @param {JQuery} element - Element or selector to replace.
* @param {String} newHTML - HTML to insert / replace.
* @param {String} newJS - Javascript to run after the insertion.
* @return {Array} The list of new DOM Nodes
*/
replaceNode: function(element, newHTML, newJS) {
domReplace(element, newHTML, newJS, false);
return domReplace(element, newHTML, newJS, false);
},
/**
@ -1199,9 +1218,10 @@ define([
* @param {jQuery|String} element - Element or selector to prepend HTML to
* @param {String} html - HTML to prepend
* @param {String} js - Javascript to run after we prepend the html
* @return {Array} The list of new DOM Nodes
*/
prependNodeContents: function(element, html, js) {
domPrepend(element, html, js);
return domPrepend(element, html, js);
},
/**
@ -1211,9 +1231,10 @@ define([
* @param {jQuery|String} element - Element or selector to append HTML to
* @param {String} html - HTML to append
* @param {String} js - Javascript to run after we append the html
* @return {Array} The list of new DOM Nodes
*/
appendNodeContents: function(element, html, js) {
domAppend(element, html, js);
return domAppend(element, html, js);
},
};
});

View File

@ -217,6 +217,7 @@ class fetch extends external_api {
}
$filterset = new $filtersetclass();
$filterset->set_join_type($jointype);
foreach ($filters as $rawfilter) {
$filterset->add_filter_from_params(
$rawfilter['name'],

View File

@ -1503,7 +1503,7 @@ class flexible_table {
$this->sortdata = [];
foreach ($sortdata as $sortitem) {
if (!array_key_exists($sortitem['sortby'], $this->sortdata)) {
$this->sortdata[$sortitem['sortby']] = $sortitem['sortorder'];
$this->sortdata[$sortitem['sortby']] = (int) $sortitem['sortorder'];
}
}
}

View File

@ -36,13 +36,15 @@
{ "inputID": 1, "suggestionsId": 2, "selectionId": 3, "downArrowId": 4, "placeholder": "Select something" }
}}
{{#showSuggestions}}
<div class="d-inline-block position-relative">
<div class="d-md-inline-block mr-md-2 position-relative">
<input type="text" id="{{inputId}}" class="form-control" list="{{suggestionsId}}" placeholder="{{placeholder}}" role="combobox" aria-expanded="false" autocomplete="off" autocorrect="off" autocapitalize="off" aria-autocomplete="list" aria-owns="{{suggestionsId}} {{selectionId}}" {{#tags}}data-tags="1"{{/tags}}/>
<span class="form-autocomplete-downarrow position-absolute p-1" id="{{downArrowId}}">&#x25BC;</span>
</div>
{{/showSuggestions}}
{{^showSuggestions}}
<input type="text" id="{{inputId}}" placeholder="{{placeholder}}" role="textbox" aria-owns="{{selectionId}}" {{#tags}}data-tags="1"{{/tags}}/>
<div class="d-md-inline-block mr-md-2">
<input type="text" id="{{inputId}}" class="form-control" placeholder="{{placeholder}}" role="textbox" aria-owns="{{selectionId}}" {{#tags}}data-tags="1"{{/tags}}/>
</div>
{{/showSuggestions}}
{{#js}}

View File

@ -0,0 +1,38 @@
{{!
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/>.
}}
{{!
@template core/form_autocomplete_layout
Moodle template for the layout of autocomplete elements.
Classes required for JS:
* none
Data attributes required for JS:
* data-region="form_autocomplete-input"
* data-region="form_autocomplete-suggestions"
* data-region="form_autocomplete-selection"
Context variables required for this template:
* none
Example context (json):
{}
}}
<div data-region="form_autocomplete-selection"></div>
<div data-region="form_autocomplete-input"></div>
<div data-region="form_autocomplete-suggestions"></div>

View File

@ -2462,6 +2462,12 @@ body.h5p-embed {
height: 1.5rem;
}
.border-radius {
@if $enable-rounded {
@include border-radius($card-border-radius);
}
}
// Emoji picker.
$picker-width: 350px !default;
$picker-width-xs: 320px !default;

View File

@ -299,3 +299,14 @@
.user-enroller-panel {
width: 600px;
}
[data-filterverbfor],
[data-filterregion="filter"]:last-child [data-filterregion="joinadverb"] {
display: none;
}
[data-filterverb="0"] [data-filterverbfor="0"],
[data-filterverb="1"] [data-filterverbfor="1"],
[data-filterverb="2"] [data-filterverbfor="2"] {
display: block;
}

View File

@ -16042,6 +16042,15 @@ body.path-question-type .mform fieldset.hidden {
.user-enroller-panel {
width: 600px; }
[data-filterverbfor],
[data-filterregion="filter"]:last-child [data-filterregion="joinadverb"] {
display: none; }
[data-filterverb="0"] [data-filterverbfor="0"],
[data-filterverb="1"] [data-filterverbfor="1"],
[data-filterverb="2"] [data-filterverbfor="2"] {
display: block; }
.search-results .result {
margin-left: 0;
margin-right: 0; }

View File

@ -11859,6 +11859,9 @@ body.h5p-embed .h5pmessages {
color: #343a40;
height: 1.5rem; }
.border-radius {
border-radius: 0.25rem; }
.emoji-picker {
width: 350px;
height: 400px; }
@ -16266,6 +16269,15 @@ body.path-question-type .mform fieldset.hidden {
.user-enroller-panel {
width: 600px; }
[data-filterverbfor],
[data-filterregion="filter"]:last-child [data-filterregion="joinadverb"] {
display: none; }
[data-filterverb="0"] [data-filterverbfor="0"],
[data-filterverb="1"] [data-filterverbfor="1"],
[data-filterverb="2"] [data-filterverbfor="2"] {
display: block; }
.search-results .result {
margin-left: 0;
margin-right: 0; }

View File

@ -0,0 +1,2 @@
define ("core_user/local/participantsfilter/filter",["exports","core/form-autocomplete","./selectors","core/str"],function(a,b,c,d){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=e(b);c=e(c);function e(a){return a&&a.__esModule?a:{default:a}}function f(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 g(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var i=a.apply(b,c);function g(a){f(i,d,e,g,h,"next",a)}function h(a){f(i,d,e,g,h,"throw",a)}g(void 0)})}}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<b.length;c++){d=b[c];d.enumerable=d.enumerable||!1;d.configurable=!0;if("value"in d)d.writable=!0;Object.defineProperty(a,d.key,d)}}function j(a,b,c){if(b)i(a.prototype,b);if(c)i(a,c);return a}var k=function(a){return a.querySelectorAll(":checked")},l=function(){function a(b,c){h(this,a);this.filterType=b;this.rootNode=c;this.addValueSelector()}j(a,[{key:"tearDown",value:function tearDown(){}},{key:"addValueSelector",value:function(){var a=g(regeneratorRuntime.mark(function a(){var c,d;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:c=this.getFilterValueNode();c.innerHTML=this.getSourceDataForFilter().outerHTML;d=c.querySelector("select");a.t0=b.default;a.t1=d;a.t2="1"==d.dataset.allowCustom;a.next=8;return this.placeholder;case 8:a.t3=a.sent;a.t4=this.showSuggestions;a.t5=!d.multiple;a.t6={items:"core_user/local/participantsfilter/autocomplete_selection_items",layout:"core_user/local/participantsfilter/autocomplete_layout",selection:"core_user/local/participantsfilter/autocomplete_selection"};a.t0.enhance.call(a.t0,a.t1,a.t2,null,a.t3,!1,a.t4,null,a.t5,a.t6);case 13:case"end":return a.stop();}}},a,this)}));return function addValueSelector(){return a.apply(this,arguments)}}()},{key:"getSourceDataForFilter",value:function getSourceDataForFilter(){var a=this.rootNode.querySelector(c.default.filterset.regions.datasource);return a.querySelector(c.default.data.fields.byName(this.filterType))}},{key:"getFilterValueNode",value:function getFilterValueNode(){return this.filterRoot.querySelector(c.default.filter.regions.values)}},{key:"placeholder",get:function get(){return(0,d.get_string)("placeholdertypeorselect","core_user")}},{key:"showSuggestions",get:function get(){return!0}},{key:"filterRoot",get:function get(){return this.rootNode.querySelector(c.default.filter.byName(this.filterType))}},{key:"name",get:function get(){return this.filterType}},{key:"jointype",get:function get(){return this.filterRoot.querySelector(c.default.filter.fields.join).value}},{key:"rawValues",get:function get(){var a=this.getFilterValueNode(),b=a.querySelector("select");return Object.values(k(b)).map(function(a){return a.value})}},{key:"values",get:function get(){return this.rawValues.map(function(a){return parseInt(a,10)})}},{key:"filterValue",get:function get(){return{name:this.name,jointype:this.jointype,values:this.values}}}]);return a}();a.default=l;return a.default});
//# sourceMappingURL=filter.min.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,2 @@
define ("core_user/local/participantsfilter/filtertypes/courseid",["exports","../filter"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);function c(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){c=function(a){return typeof a}}else{c=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return c(a)}function d(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 e(a){return function(){var b=this,c=arguments;return new Promise(function(e,f){var i=a.apply(b,c);function g(a){d(i,e,f,g,h,"next",a)}function h(a){d(i,e,f,g,h,"throw",a)}g(void 0)})}}function f(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function g(a,b){for(var c=0,d;c<b.length;c++){d=b[c];d.enumerable=d.enumerable||!1;d.configurable=!0;if("value"in d)d.writable=!0;Object.defineProperty(a,d.key,d)}}function h(a,b,c){if(b)g(a.prototype,b);if(c)g(a,c);return a}function i(a,b){if("function"!=typeof b&&null!==b){throw new TypeError("Super expression must either be null or a function")}a.prototype=Object.create(b&&b.prototype,{constructor:{value:a,writable:!0,configurable:!0}});if(b)j(a,b)}function j(a,b){j=Object.setPrototypeOf||function(a,b){a.__proto__=b;return a};return j(a,b)}function k(a){return function(){var b=p(a),c;if(n()){var d=p(this).constructor;c=Reflect.construct(b,arguments,d)}else{c=b.apply(this,arguments)}return l(this,c)}}function l(a,b){if(b&&("object"===c(b)||"function"==typeof b)){return b}return m(a)}function m(a){if(void 0===a){throw new ReferenceError("this hasn't been initialised - super() hasn't been called")}return a}function n(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{Date.prototype.toString.call(Reflect.construct(Date,[],function(){}));return!0}catch(a){return!1}}function p(a){p=Object.setPrototypeOf?Object.getPrototypeOf:function(a){return a.__proto__||Object.getPrototypeOf(a)};return p(a)}var q=function(a){i(b,a);var c=k(b);function b(a,d){f(this,b);return c.call(this,a,d)}h(b,[{key:"addValueSelector",value:function(){var a=e(regeneratorRuntime.mark(function a(){return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:case"end":return a.stop();}}},a)}));return function addValueSelector(){return a.apply(this,arguments)}}()},{key:"filterValue",get:function get(){return{name:this.name,jointype:1,values:[parseInt(this.rootNode.dataset.tableCourseId,10)]}}}]);return b}(b.default);a.default=q;return a.default});
//# sourceMappingURL=courseid.min.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/local/participantsfilter/filtertypes/courseid.js"],"names":["filterType","filterSet","name","jointype","values","parseInt","rootNode","dataset","tableCourseId","Filter"],"mappings":"uLAuBA,uD,+9DAGI,WAAYA,CAAZ,CAAwBC,CAAxB,CAAmC,8BACzBD,CADyB,CACbC,CADa,CAElC,C,4TAWiB,CACd,MAAO,CACHC,IAAI,CAAE,KAAKA,IADR,CAEHC,QAAQ,CAAE,CAFP,CAGHC,MAAM,CAAE,CAACC,QAAQ,CAAC,KAAKC,QAAL,CAAcC,OAAd,CAAsBC,aAAvB,CAAsC,EAAtC,CAAT,CAHL,CAKV,C,cApBwBC,S","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 * Course ID filter.\n *\n * @module core_user/local/participantsfilter/filtertypes/courseid\n * @package core_user\n * @copyright 2020 Andrew Nicols <andrew@nicols.co.uk>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport Filter from '../filter';\n\nexport default class extends Filter {\n constructor(filterType, filterSet) {\n super(filterType, filterSet);\n }\n\n async addValueSelector() {\n // eslint-disable-line no-empty-function\n }\n\n /**\n * Get the composed value for this filter.\n *\n * @returns {Object}\n */\n get filterValue() {\n return {\n name: this.name,\n jointype: 1,\n values: [parseInt(this.rootNode.dataset.tableCourseId, 10)],\n };\n }\n}\n"],"file":"courseid.min.js"}

View File

@ -0,0 +1,2 @@
define ("core_user/local/participantsfilter/filtertypes/keyword",["exports","../filter","core/str"],function(a,b,c){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);function d(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){d=function(a){return typeof a}}else{d=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return d(a)}function e(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function f(a,b){for(var c=0,d;c<b.length;c++){d=b[c];d.enumerable=d.enumerable||!1;d.configurable=!0;if("value"in d)d.writable=!0;Object.defineProperty(a,d.key,d)}}function g(a,b,c){if(b)f(a.prototype,b);if(c)f(a,c);return a}function h(a,b){if("function"!=typeof b&&null!==b){throw new TypeError("Super expression must either be null or a function")}a.prototype=Object.create(b&&b.prototype,{constructor:{value:a,writable:!0,configurable:!0}});if(b)i(a,b)}function i(a,b){i=Object.setPrototypeOf||function(a,b){a.__proto__=b;return a};return i(a,b)}function j(a){return function(){var b=n(a),c;if(m()){var d=n(this).constructor;c=Reflect.construct(b,arguments,d)}else{c=b.apply(this,arguments)}return k(this,c)}}function k(a,b){if(b&&("object"===d(b)||"function"==typeof b)){return b}return l(a)}function l(a){if(void 0===a){throw new ReferenceError("this hasn't been initialised - super() hasn't been called")}return a}function m(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{Date.prototype.toString.call(Reflect.construct(Date,[],function(){}));return!0}catch(a){return!1}}function n(a){n=Object.setPrototypeOf?Object.getPrototypeOf:function(a){return a.__proto__||Object.getPrototypeOf(a)};return n(a)}var o=function(a){h(b,a);var d=j(b);function b(a,c){e(this,b);return d.call(this,a,c)}g(b,[{key:"values",get:function get(){return this.rawValues}},{key:"placeholder",get:function get(){return(0,c.get_string)("placeholdertype","core_user")}},{key:"showSuggestions",get:function get(){return!1}}]);return b}(b.default);a.default=o;return a.default});
//# sourceMappingURL=keyword.min.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/local/participantsfilter/filtertypes/keyword.js"],"names":["filterType","filterSet","rawValues","Filter"],"mappings":"mMAuBA,uD,gqDAII,WAAYA,CAAZ,CAAwBC,CAAxB,CAAmC,8BACzBD,CADyB,CACbC,CADa,CAElC,C,qCAOY,CACT,MAAO,MAAKC,SACf,C,uCAOiB,CACd,MAAO,iBAAU,iBAAV,CAA6B,WAA7B,CACV,C,2CAOqB,CAClB,QACH,C,cA9BwBC,S","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 * Keyword filter.\n *\n * @module core_user/local/participantsfilter/filtertypes/keyword\n * @package core_user\n * @copyright 2020 Andrew Nicols <andrew@nicols.co.uk>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport Filter from '../filter';\nimport {get_string as getString} from 'core/str';\n\nexport default class extends Filter {\n constructor(filterType, filterSet) {\n super(filterType, filterSet);\n }\n\n /**\n * For keywords the final value is an Array of strings.\n *\n * @returns {Object}\n */\n get values() {\n return this.rawValues;\n }\n\n /**\n * Get the placeholder to use when showing the value selector.\n *\n * @return {Promise} Resolving to a String\n */\n get placeholder() {\n return getString('placeholdertype', 'core_user');\n }\n\n /**\n * Whether to show suggestions in the autocomplete.\n *\n * @return {Boolean}\n */\n get showSuggestions() {\n return false;\n }\n}\n"],"file":"keyword.min.js"}

View File

@ -0,0 +1,2 @@
define ("core_user/local/participantsfilter/selectors",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;var b=function(a){return"[data-filterregion=\"".concat(a,"\"]")},c=function(a){return"[data-filteraction=\"".concat(a,"\"]")},d=function(a){return"[data-filterfield=\"".concat(a,"\"]")},e={filter:{region:b("filter"),actions:{remove:c("remove")},fields:{join:d("join"),type:d("type")},regions:{values:b("value")},byName:function byName(a){return"".concat(b("filter"),"[data-filter-type=\"").concat(a,"\"]")}},filterset:{region:b("actions"),actions:{addRow:c("add"),applyFilters:c("apply"),resetFilters:c("reset")},regions:{filtermatch:b("filtermatch"),filterlist:b("filters"),datasource:b("filtertypedata")},fields:{join:"".concat(b("filtermatch")," ").concat(d("join"))}},data:{fields:{byName:function byName(a){return"[data-field-name=\"".concat(a,"\"]")},all:"".concat(b("filtertypedata")," [data-field-name]")},typeList:b("filtertypelist")}};a.default=e;return a.default});
//# sourceMappingURL=selectors.min.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../../../src/local/participantsfilter/selectors.js"],"names":["getFilterRegion","region","getFilterAction","action","getFilterField","field","filter","actions","remove","fields","join","type","regions","values","byName","name","filterset","addRow","applyFilters","resetFilters","filtermatch","filterlist","datasource","data","all","typeList"],"mappings":"iKAwBMA,CAAAA,CAAe,CAAG,SAAAC,CAAM,uCAA2BA,CAA3B,Q,CACxBC,CAAe,CAAG,SAAAC,CAAM,uCAA2BA,CAA3B,Q,CACxBC,CAAc,CAAG,SAAAC,CAAK,sCAA0BA,CAA1B,Q,GAEb,CACXC,MAAM,CAAE,CACJL,MAAM,CAAED,CAAe,CAAC,QAAD,CADnB,CAEJO,OAAO,CAAE,CACLC,MAAM,CAAEN,CAAe,CAAC,QAAD,CADlB,CAFL,CAKJO,MAAM,CAAE,CACJC,IAAI,CAAEN,CAAc,CAAC,MAAD,CADhB,CAEJO,IAAI,CAAEP,CAAc,CAAC,MAAD,CAFhB,CALJ,CASJQ,OAAO,CAAE,CACLC,MAAM,CAAEb,CAAe,CAAC,OAAD,CADlB,CATL,CAYJc,MAAM,CAAE,gBAAAC,CAAI,kBAAOf,CAAe,CAAC,QAAD,CAAtB,gCAAsDe,CAAtD,QAZR,CADG,CAeXC,SAAS,CAAE,CACPf,MAAM,CAAED,CAAe,CAAC,SAAD,CADhB,CAEPO,OAAO,CAAE,CACLU,MAAM,CAAEf,CAAe,CAAC,KAAD,CADlB,CAELgB,YAAY,CAAEhB,CAAe,CAAC,OAAD,CAFxB,CAGLiB,YAAY,CAAEjB,CAAe,CAAC,OAAD,CAHxB,CAFF,CAOPU,OAAO,CAAE,CACLQ,WAAW,CAAEpB,CAAe,CAAC,aAAD,CADvB,CAELqB,UAAU,CAAErB,CAAe,CAAC,SAAD,CAFtB,CAGLsB,UAAU,CAAEtB,CAAe,CAAC,gBAAD,CAHtB,CAPF,CAYPS,MAAM,CAAE,CACJC,IAAI,WAAKV,CAAe,CAAC,aAAD,CAApB,aAAuCI,CAAc,CAAC,MAAD,CAArD,CADA,CAZD,CAfA,CA+BXmB,IAAI,CAAE,CACFd,MAAM,CAAE,CACJK,MAAM,CAAE,gBAAAC,CAAI,qCAAyBA,CAAzB,QADR,CAEJS,GAAG,WAAKxB,CAAe,CAAC,gBAAD,CAApB,sBAFC,CADN,CAKFyB,QAAQ,CAAEzB,CAAe,CAAC,gBAAD,CALvB,CA/BK,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 * Module containing the selectors for user filters.\n *\n * @module core_user/local/user_filter/selectors\n * @package core_user\n * @copyright 2020 Michael Hawkins <michaelh@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nconst getFilterRegion = region => `[data-filterregion=\"${region}\"]`;\nconst getFilterAction = action => `[data-filteraction=\"${action}\"]`;\nconst getFilterField = field => `[data-filterfield=\"${field}\"]`;\n\nexport default {\n filter: {\n region: getFilterRegion('filter'),\n actions: {\n remove: getFilterAction('remove'),\n },\n fields: {\n join: getFilterField('join'),\n type: getFilterField('type'),\n },\n regions: {\n values: getFilterRegion('value'),\n },\n byName: name => `${getFilterRegion('filter')}[data-filter-type=\"${name}\"]`,\n },\n filterset: {\n region: getFilterRegion('actions'),\n actions: {\n addRow: getFilterAction('add'),\n applyFilters: getFilterAction('apply'),\n resetFilters: getFilterAction('reset'),\n },\n regions: {\n filtermatch: getFilterRegion('filtermatch'),\n filterlist: getFilterRegion('filters'),\n datasource: getFilterRegion('filtertypedata'),\n },\n fields: {\n join: `${getFilterRegion('filtermatch')} ${getFilterField('join')}`,\n },\n },\n data: {\n fields: {\n byName: name => `[data-field-name=\"${name}\"]`,\n all: `${getFilterRegion('filtertypedata')} [data-field-name]`,\n },\n typeList: getFilterRegion('filtertypelist'),\n },\n};\n"],"file":"selectors.min.js"}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,205 @@
// 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/>.
/**
* Base Filter class for a filter type in the participants filter UI.
*
* @module core_user/local/participantsfilter/filter
* @package core_user
* @copyright 2020 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import Autocomplete from 'core/form-autocomplete';
import Selectors from './selectors';
import {get_string as getString} from 'core/str';
/**
* Fetch all checked options in the select.
*
* This is a poor-man's polyfill for select.selectedOptions, which is not available in IE11.
*
* @param {HTMLSelectElement} select
* @returns {HTMLOptionElement[]} All selected options
*/
const getOptionsForSelect = select => {
return select.querySelectorAll(':checked');
};
export default class {
/**
* Constructor for a new filter.
*
* @param {String} filterType The type of filter that this relates to
* @param {HTMLElement} rootNode The root node for the participants filterset
*/
constructor(filterType, rootNode) {
this.filterType = filterType;
this.rootNode = rootNode;
this.addValueSelector();
}
/**
* Perform any tear-down for this filter type.
*/
tearDown() {
// eslint-disable-line no-empty-function
}
/**
* Get the placeholder to use when showing the value selector.
*
* @return {Promise} Resolving to a String
*/
get placeholder() {
return getString('placeholdertypeorselect', 'core_user');
}
/**
* Whether to show suggestions in the autocomplete.
*
* @return {Boolean}
*/
get showSuggestions() {
return true;
}
/**
* Add the value selector to the filter row.
*/
async addValueSelector() {
const filterValueNode = this.getFilterValueNode();
// Copy the data in place.
filterValueNode.innerHTML = this.getSourceDataForFilter().outerHTML;
const dataSource = filterValueNode.querySelector('select');
Autocomplete.enhance(
// The source select element.
dataSource,
// Whether to allow 'tags' (custom entries).
dataSource.dataset.allowCustom == "1",
// We do not require AJAX at all as standard.
null,
// The string to use as a placeholder.
await this.placeholder,
// Disable case sensitivity on searches.
false,
// Show suggestions.
this.showSuggestions,
// Do not override the 'no suggestions' string.
null,
// Close the suggestions if this is not a multi-select.
!dataSource.multiple,
// Template overrides.
{
items: 'core_user/local/participantsfilter/autocomplete_selection_items',
layout: 'core_user/local/participantsfilter/autocomplete_layout',
selection: 'core_user/local/participantsfilter/autocomplete_selection',
}
);
}
/**
* Get the root node for this filter.
*
* @returns {HTMLElement}
*/
get filterRoot() {
return this.rootNode.querySelector(Selectors.filter.byName(this.filterType));
}
/**
* Get the possible data for this filter type.
*
* @returns {Array}
*/
getSourceDataForFilter() {
const filterDataNode = this.rootNode.querySelector(Selectors.filterset.regions.datasource);
return filterDataNode.querySelector(Selectors.data.fields.byName(this.filterType));
}
/**
* Get the HTMLElement which contains the value selector.
*
* @returns {HTMLElement}
*/
getFilterValueNode() {
return this.filterRoot.querySelector(Selectors.filter.regions.values);
}
/**
* Get the name of this filter.
*
* @returns {String}
*/
get name() {
return this.filterType;
}
/**
* Get the type of join specified.
*
* @returns {Number}
*/
get jointype() {
return this.filterRoot.querySelector(Selectors.filter.fields.join).value;
}
/**
* Get the list of raw values for this filter type.
*
* @returns {Array}
*/
get rawValues() {
const filterValueNode = this.getFilterValueNode();
const filterValueSelect = filterValueNode.querySelector('select');
return Object.values(getOptionsForSelect(filterValueSelect)).map(option => option.value);
}
/**
* Get the list of values for this filter type.
*
* @returns {Array}
*/
get values() {
return this.rawValues.map(option => parseInt(option, 10));
}
/**
* Get the composed value for this filter.
*
* @returns {Object}
*/
get filterValue() {
return {
name: this.name,
jointype: this.jointype,
values: this.values,
};
}
}

View File

@ -0,0 +1,47 @@
// 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/>.
/**
* Course ID filter.
*
* @module core_user/local/participantsfilter/filtertypes/courseid
* @package core_user
* @copyright 2020 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import Filter from '../filter';
export default class extends Filter {
constructor(filterType, filterSet) {
super(filterType, filterSet);
}
async addValueSelector() {
// eslint-disable-line no-empty-function
}
/**
* Get the composed value for this filter.
*
* @returns {Object}
*/
get filterValue() {
return {
name: this.name,
jointype: 1,
values: [parseInt(this.rootNode.dataset.tableCourseId, 10)],
};
}
}

View File

@ -0,0 +1,58 @@
// 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/>.
/**
* Keyword filter.
*
* @module core_user/local/participantsfilter/filtertypes/keyword
* @package core_user
* @copyright 2020 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import Filter from '../filter';
import {get_string as getString} from 'core/str';
export default class extends Filter {
constructor(filterType, filterSet) {
super(filterType, filterSet);
}
/**
* For keywords the final value is an Array of strings.
*
* @returns {Object}
*/
get values() {
return this.rawValues;
}
/**
* Get the placeholder to use when showing the value selector.
*
* @return {Promise} Resolving to a String
*/
get placeholder() {
return getString('placeholdertype', 'core_user');
}
/**
* Whether to show suggestions in the autocomplete.
*
* @return {Boolean}
*/
get showSuggestions() {
return false;
}
}

View File

@ -0,0 +1,67 @@
// 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/>.
/**
* Module containing the selectors for user filters.
*
* @module core_user/local/user_filter/selectors
* @package core_user
* @copyright 2020 Michael Hawkins <michaelh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
const getFilterRegion = region => `[data-filterregion="${region}"]`;
const getFilterAction = action => `[data-filteraction="${action}"]`;
const getFilterField = field => `[data-filterfield="${field}"]`;
export default {
filter: {
region: getFilterRegion('filter'),
actions: {
remove: getFilterAction('remove'),
},
fields: {
join: getFilterField('join'),
type: getFilterField('type'),
},
regions: {
values: getFilterRegion('value'),
},
byName: name => `${getFilterRegion('filter')}[data-filter-type="${name}"]`,
},
filterset: {
region: getFilterRegion('actions'),
actions: {
addRow: getFilterAction('add'),
applyFilters: getFilterAction('apply'),
resetFilters: getFilterAction('reset'),
},
regions: {
filtermatch: getFilterRegion('filtermatch'),
filterlist: getFilterRegion('filters'),
datasource: getFilterRegion('filtertypedata'),
},
fields: {
join: `${getFilterRegion('filtermatch')} ${getFilterField('join')}`,
},
},
data: {
fields: {
byName: name => `[data-field-name="${name}"]`,
all: `${getFilterRegion('filtertypedata')} [data-field-name]`,
},
typeList: getFilterRegion('filtertypelist'),
},
};

View File

@ -0,0 +1,348 @@
// 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/>.
/**
* Participants filter managemnet.
*
* @module core_user/participants_filter
* @package core_user
* @copyright 2020 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import CourseFilter from './local/participantsfilter/filtertypes/courseid';
import * as DynamicTable from 'core_table/dynamic';
import GenericFilter from './local/participantsfilter/filter';
import Notification from 'core/notification';
import Selectors from './local/participantsfilter/selectors';
import Templates from 'core/templates';
/**
* Initialise the participants filter on the element with the given id.
*
* @param {String} participantsRegionId
*/
export const init = participantsRegionId => {
// Keep a reference to the filterset.
const filterSet = document.querySelector(`#${participantsRegionId}`);
// Keep a reference to all of the active filters.
const activeFilters = {
courseid: new CourseFilter('courseid', filterSet),
};
/**
* Get the filter list region.
*
* @return {HTMLElement}
*/
const getFilterRegion = () => filterSet.querySelector(Selectors.filterset.regions.filterlist);
/**
* Add an unselected filter row.
*
* @return {Promise}
*/
const addFilterRow = () => {
return Templates.renderForPromise('core_user/local/participantsfilter/filterrow', {})
.then(({html, js}) => {
const newContentNodes = Templates.appendNodeContents(getFilterRegion(), html, js);
return newContentNodes;
})
.then(filterRow => {
// Note: This is a nasty hack.
// We should try to find a better way of doing this.
// We do not have the list of types in a readily consumable format, so we take the pre-rendered one and copy
// it in place.
const typeList = filterSet.querySelector(Selectors.data.typeList);
filterRow.forEach(contentNode => {
const contentTypeList = contentNode.querySelector(Selectors.filter.fields.type);
if (contentTypeList) {
contentTypeList.innerHTML = typeList.innerHTML;
}
});
return filterRow;
})
.then(filterRow => {
updateFiltersOptions();
return filterRow;
})
.catch(Notification.exception);
};
/**
* Get the filter data source node fro the specified filter type.
*
* @param {String} filterType
* @return {HTMLElement}
*/
const getFilterDataSource = filterType => {
const filterDataNode = filterSet.querySelector(Selectors.filterset.regions.datasource);
return filterDataNode.querySelector(Selectors.data.fields.byName(filterType));
};
/**
* Add a filter to the list of active filters, performing any necessary setup.
*
* @param {HTMLElement} filterRow
* @param {String} filterType
*/
const addFilter = async(filterRow, filterType) => {
// Name the filter on the filter row.
filterRow.dataset.filterType = filterType;
const filterDataNode = getFilterDataSource(filterType);
// Instantiate the Filter class.
let Filter = GenericFilter;
if (filterDataNode.dataset.filterTypeClass) {
Filter = await import(filterDataNode.dataset.filterTypeClass);
}
activeFilters[filterType] = new Filter(filterType, filterSet);
// Disable the select.
const typeField = filterRow.querySelector(Selectors.filter.fields.type);
typeField.disabled = 'disabled';
// Update the list of available filter types.
updateFiltersOptions();
};
/**
* Get the registered filter class for the named filter.
*
* @param {String} name
* @return {Object} See the Filter class.
*/
const getFilterObject = name => {
return activeFilters[name];
};
/**
* Remove or replace the specified filter row and associated class, ensuring that if there is only one filter row,
* that it is replaced instead of being removed.
*
* @param {HTMLElement} filterRow
*/
const removeOrReplaceFilterRow = filterRow => {
const filterCount = getFilterRegion().querySelectorAll(Selectors.filter.region).length;
if (filterCount === 1) {
replaceFilterRow(filterRow);
} else {
removeFilterRow(filterRow);
}
};
/**
* Remove the specified filter row and associated class.
*
* @param {HTMLElement} filterRow
*/
const removeFilterRow = filterRow => {
// Remove the filter object.
removeFilterObject(filterRow.dataset.filterType);
// Remove the actual filter HTML.
filterRow.remove();
// Refresh the table.
updateTableFromFilter();
// Update the list of available filter types.
updateFiltersOptions();
};
/**
* Replace the specified filter row with a new one.
*
* @param {HTMLElement} filterRow
* @return {Promise}
*/
const replaceFilterRow = filterRow => {
// Remove the filter object.
removeFilterObject(filterRow.dataset.filterType);
return Templates.renderForPromise('core_user/local/participantsfilter/filterrow', {})
.then(({html, js}) => {
const newContentNodes = Templates.replaceNode(filterRow, html, js);
return newContentNodes;
})
.then(filterRow => {
// Note: This is a nasty hack.
// We should try to find a better way of doing this.
// We do not have the list of types in a readily consumable format, so we take the pre-rendered one and copy
// it in place.
const typeList = filterSet.querySelector(Selectors.data.typeList);
filterRow.forEach(contentNode => {
const contentTypeList = contentNode.querySelector(Selectors.filter.fields.type);
if (contentTypeList) {
contentTypeList.innerHTML = typeList.innerHTML;
}
});
return filterRow;
})
.then(filterRow => {
updateFiltersOptions();
return filterRow;
})
.then(filterRow => {
// Refresh the table.
updateTableFromFilter();
return filterRow;
})
.catch(Notification.exception);
};
/**
* Remove the Filter Object from the register.
*
* @param {string} filterName The name of the filter to be removed
*/
const removeFilterObject = filterName => {
if (filterName) {
const filter = getFilterObject(filterName);
if (filter) {
filter.tearDown();
// Remove from the list of active filters.
delete activeFilters[filterName];
}
}
};
/**
* Remove all filters.
*/
const removeAllFilters = async() => {
const filters = getFilterRegion().querySelectorAll(Selectors.filter.region);
filters.forEach((filterRow) => {
removeOrReplaceFilterRow(filterRow);
});
// Refresh the table.
updateTableFromFilter();
};
/**
* Update the list of filter types to filter out those already selected.
*/
const updateFiltersOptions = () => {
const filters = getFilterRegion().querySelectorAll(Selectors.filter.region);
filters.forEach(filterRow => {
const options = filterRow.querySelectorAll(Selectors.filter.fields.type + ' option');
options.forEach(option => {
if (option.value === filterRow.dataset.filterType) {
option.classList.remove('hidden');
option.disabled = false;
} else if (activeFilters[option.value]) {
option.classList.add('hidden');
option.disabled = true;
} else {
option.classList.remove('hidden');
option.disabled = false;
}
});
});
// Configure the state of the "Add row" button.
// This button is disabled when there is a filter row available for each condition.
const addRowButton = filterSet.querySelector(Selectors.filterset.actions.addRow);
const filterDataNode = filterSet.querySelectorAll(Selectors.data.fields.all);
if (filterDataNode.length <= filters.length) {
addRowButton.setAttribute('disabled', 'disabled');
} else {
addRowButton.removeAttribute('disabled');
}
if (filters.length === 1) {
filterSet.querySelector(Selectors.filterset.regions.filtermatch).classList.add('hidden');
filterSet.querySelector(Selectors.filterset.fields.join).value = 1;
} else {
filterSet.querySelector(Selectors.filterset.regions.filtermatch).classList.remove('hidden');
}
};
/**
* Update the Dynamic table based upon the current filter.
*
* @return {Promise}
*/
const updateTableFromFilter = () => {
return DynamicTable.setFilters(
DynamicTable.getTableFromId(filterSet.dataset.tableRegion),
{
filters: Object.values(activeFilters).map(filter => filter.filterValue),
jointype: filterSet.querySelector(Selectors.filterset.fields.join).value,
}
);
};
// Add listeners for the main actions.
filterSet.querySelector(Selectors.filterset.region).addEventListener('click', e => {
if (e.target.closest(Selectors.filterset.actions.addRow)) {
e.preventDefault();
addFilterRow();
}
if (e.target.closest(Selectors.filterset.actions.applyFilters)) {
e.preventDefault();
updateTableFromFilter();
}
if (e.target.closest(Selectors.filterset.actions.resetFilters)) {
e.preventDefault();
removeAllFilters();
}
});
// Add the listener to remove a single filter.
filterSet.querySelector(Selectors.filterset.regions.filterlist).addEventListener('click', e => {
if (e.target.closest(Selectors.filter.actions.remove)) {
e.preventDefault();
removeOrReplaceFilterRow(e.target.closest(Selectors.filter.region));
}
});
// Add listeners for the filter type selection.
filterSet.querySelector(Selectors.filterset.regions.filterlist).addEventListener('change', e => {
const typeField = e.target.closest(Selectors.filter.fields.type);
if (typeField && typeField.value) {
const filter = e.target.closest(Selectors.filter.region);
addFilter(filter, typeField.value);
}
});
filterSet.querySelector(Selectors.filterset.fields.join).addEventListener('change', e => {
filterSet.dataset.filterverb = e.target.value;
});
};

View File

@ -0,0 +1,393 @@
<?php
// 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/>.
/**
* Class for rendering user filters on the course participants page.
*
* @package core_user
* @copyright 2020 Michael Hawkins <michaelh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_user\output;
use context_course;
use renderable;
use renderer_base;
use stdClass;
use templatable;
/**
* Class for rendering user filters on the course participants page.
*
* @copyright 2020 Michael Hawkins <michaelh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class participants_filter implements renderable, templatable {
/** @var context_course $context The context where the filters are being rendered. */
protected $context;
/** @var string $tableregionid The table to be updated by this filter */
protected $tableregionid;
/** @var stdClass $course The course shown */
protected $course;
/**
* Participants filter constructor.
*
* @param context_course $context The context where the filters are being rendered.
* @param string $tableregionid The table to be updated by this filter
*/
public function __construct(context_course $context, string $tableregionid) {
$this->context = $context;
$this->tableregionid = $tableregionid;
$this->course = get_course($context->instanceid);
}
/**
* Get data for all filter types.
*
* @return array
*/
protected function get_filtertypes(): array {
$filtertypes = [];
$filtertypes[] = $this->get_keyword_filter();
if ($filtertype = $this->get_enrolmentstatus_filter()) {
$filtertypes[] = $filtertype;
}
if ($filtertype = $this->get_roles_filter()) {
$filtertypes[] = $filtertype;
}
if ($filtertype = $this->get_enrolments_filter()) {
$filtertypes[] = $filtertype;
}
if ($filtertype = $this->get_groups_filter()) {
$filtertypes[] = $filtertype;
}
if ($filtertype = $this->get_accesssince_filter()) {
$filtertypes[] = $filtertype;
}
return $filtertypes;
}
/**
* Get data for the enrolment status filter.
*
* @return stdClass|null
*/
protected function get_enrolmentstatus_filter(): ?stdClass {
if (!has_capability('moodle/course:enrolreview', $this->context)) {
return null;
}
return $this->get_filter_object(
'status',
get_string('participationstatus', 'core_enrol'),
false,
true,
null,
[
(object) [
'value' => ENROL_USER_ACTIVE,
'title' => get_string('active'),
],
(object) [
'value' => ENROL_USER_SUSPENDED,
'title' => get_string('inactive'),
],
]
);
}
/**
* Get data for the roles filter.
*
* @return stdClass|null
*/
protected function get_roles_filter(): ?stdClass {
$roles = [];
$roles += [-1 => get_string('noroles', 'role')];
$roles += get_viewable_roles($this->context);
if (has_capability('moodle/role:assign', $this->context)) {
$roles += get_assignable_roles($this->context, ROLENAME_ALIAS);
}
return $this->get_filter_object(
'roles',
get_string('roles', 'core_role'),
false,
true,
null,
array_map(function($id, $title) {
return (object) [
'value' => $id,
'title' => $title,
];
}, array_keys($roles), array_values($roles))
);
}
/**
* Get data for the roles filter.
*
* @return stdClass|null
*/
protected function get_enrolments_filter(): ?stdClass {
if (!has_capability('moodle/course:enrolreview', $this->context)) {
return null;
}
if ($this->course->id == SITEID) {
// No enrolment methods for the site.
return null;
}
$instances = enrol_get_instances($this->course->id, true);
$plugins = enrol_get_plugins(false);
return $this->get_filter_object(
'enrolments',
get_string('enrolmentinstances', 'core_enrol'),
false,
true,
null,
array_filter(array_map(function($instance) use ($plugins): ?stdClass {
if (!array_key_exists($instance->enrol, $plugins)) {
return null;
}
return (object) [
'value' => $instance->id,
'title' => $plugins[$instance->enrol]->get_instance_name($instance),
];
}, array_values($instances)))
);
}
/**
* Get data for the groups filter.
*
* @return stdClass|null
*/
protected function get_groups_filter(): ?stdClass {
global $USER;
// Filter options for groups, if available.
$seeallgroups = has_capability('moodle/site:accessallgroups', $this->context);
$seeallgroups = $seeallgroups || ($this->course->groupmode != SEPARATEGROUPS);
if ($seeallgroups) {
$groups = [];
$groups += [USERSWITHOUTGROUP => (object) [
'id' => USERSWITHOUTGROUP,
'name' => get_string('nogroup', 'group'),
]];
$groups += groups_get_all_groups($this->course->id);
} else {
// Otherwise, just list the groups the user belongs to.
$groups = groups_get_all_groups($this->course->id, $USER->id);
}
if (empty($groups)) {
return null;
}
return $this->get_filter_object(
'groups',
get_string('groups', 'core_group'),
false,
true,
null,
array_map(function($group) {
return (object) [
'value' => $group->id,
'title' => $group->name,
];
}, array_values($groups))
);
}
/**
* Get data for the accesssince filter.
*
* @return stdClass|null
*/
protected function get_accesssince_filter(): ?stdClass {
global $CFG, $DB;
$hiddenfields = [];
if (!has_capability('moodle/course:viewhiddenuserfields', $this->context)) {
$hiddenfields = array_flip(explode(',', $CFG->hiddenuserfields));
}
if (array_key_exists('lastaccess', $hiddenfields)) {
return null;
}
// Get minimum lastaccess for this course and display a dropbox to filter by lastaccess going back this far.
// We need to make it diferently for normal courses and site course.
if (!$this->course->id == SITEID) {
// Regular course.
$params = [
'courseid' => $this->course->id,
'timeaccess' => 0,
];
$select = 'courseid = :courseid AND timeaccess != :timeaccess';
$minlastaccess = $DB->get_field_select('user_lastaccess', 'MIN(timeaccess)', $select, $params);
$lastaccess0exists = $DB->record_exists('user_lastaccess', $params);
} else {
// Front page.
$params = ['lastaccess' => 0];
$select = 'lastaccess != :lastaccess';
$minlastaccess = $DB->get_field_select('user', 'MIN(lastaccess)', $select, $params);
$lastaccess0exists = $DB->record_exists('user', $params);
}
$now = usergetmidnight(time());
$timeoptions = [];
$criteria = get_string('usersnoaccesssince');
$getoptions = function(int $count, string $singletype, string $type) use ($now, $minlastaccess): array {
$values = [];
for ($i = 1; $i <= $count; $i++) {
$timestamp = strtotime("-{$i} {$type}", $now);
if ($timestamp < $minlastaccess) {
break;
}
if ($i === 1) {
$title = get_string("num{$singletype}", 'moodle', $i);
} else {
$title = get_string("num{$type}", 'moodle', $i);
}
$values[] = [
'value' => $timestamp,
'title' => $title,
];
}
return $values;
};
$values = array_merge(
$getoptions(6, 'day', 'days'),
$getoptions(10, 'week', 'weeks'),
$getoptions(11, 'month', 'months'),
$getoptions(1, 'year', 'years')
);
if ($lastaccess0exists) {
$values[] = [
'value' => time(),
'title' => get_string('never', 'moodle'),
];
}
if (count($values) <= 1) {
// Nothing to show.
return null;
}
return $this->get_filter_object(
'accesssince',
get_string('usersnoaccesssince'),
false,
false,
null,
$values
);
}
/**
* Get data for the keywords filter.
*
* @return stdClass|null
*/
protected function get_keyword_filter(): ?stdClass {
return $this->get_filter_object(
'keywords',
get_string('filterbykeyword', 'core_user'),
true,
true,
'core_user/local/participantsfilter/filtertypes/keyword',
[],
true
);
}
/**
* Export the renderer data in a mustache template friendly format.
*
* @param renderer_base $output Unused.
* @return stdClass Data in a format compatible with a mustache template.
*/
public function export_for_template(renderer_base $output): stdClass {
return (object) [
'tableregionid' => $this->tableregionid,
'courseid' => $this->context->instanceid,
'filtertypes' => $this->get_filtertypes(),
];
return $data;
}
/**
* Get a standardised filter object.
*
* @param string $name
* @param string $title
* @param bool $custom
* @param bool $multiple
* @param string|null $filterclass
* @param array $values
* @param bool $allowempty
* @return stdClass|null
*/
protected function get_filter_object(
string $name,
string $title,
bool $custom,
bool $multiple,
?string $filterclass,
array $values,
bool $allowempty = false
): ?stdClass {
if (!$allowempty && empty($values)) {
// Do not show empty filters.
return null;
}
return (object) [
'name' => $name,
'title' => $title,
'allowcustom' => $custom,
'allowmultiple' => $multiple,
'filtertypeclass' => $filterclass,
'values' => $values,
];
}
}

View File

@ -143,6 +143,8 @@ $lastaccess = 0;
$searchkeywords = [];
$enrolid = 0;
$participanttable = new \core_user\table\participants("user-index-participants-{$course->id}");
$filterset = new \core_user\table\participants_filterset();
$filterset->add_filter(new integer_filter('courseid', filter::JOINTYPE_DEFAULT, [(int)$course->id]));
$enrolfilter = new integer_filter('enrolments');
@ -240,7 +242,7 @@ $enrolbuttonsout = '';
foreach ($enrolbuttons as $enrolbutton) {
$enrolbuttonsout .= $enrolrenderer->render($enrolbutton);
}
echo html_writer::div($enrolbuttonsout, 'float-right', [
echo html_writer::div($enrolbuttonsout, 'd-flex justify-content-end', [
'data-region' => 'wrapper',
'data-table-uniqueid' => $participanttable->uniqueid,
]);
@ -249,6 +251,10 @@ echo html_writer::div($enrolbuttonsout, 'float-right', [
$renderer = $PAGE->get_renderer('core_user');
echo $renderer->unified_filter($course, $context, $filtersapplied, $baseurl);
// Render the user filters.
$userrenderer = $PAGE->get_renderer('core_user');
echo $userrenderer->participants_filter($context, $participanttable->uniqueid);
echo '<div class="userlist">';
// Add filters to the baseurl after creating unified_filter to avoid losing them.

View File

@ -259,6 +259,20 @@ class core_user_renderer extends plugin_renderer_base {
return $this->output->render_from_template('core_user/unified_filter', $context);
}
/**
* Render the data required for the participants filter on the course participants page.
*
* @param context $context The context of the course being displayed
* @param string $tableregionid The table to be updated by this filter
* @return string
*/
public function participants_filter(context $context, string $tableregionid): string {
$renderable = new \core_user\output\participants_filter($context, $tableregionid);
$templatecontext = $renderable->export_for_template($this->output);
return $this->output->render_from_template('core_user/participantsfilter', $templatecontext);
}
/**
* Returns a formatted filter option.
*

View File

@ -0,0 +1,38 @@
{{!
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/>.
}}
{{!
@template core_user/local/participantsfilter/autocomplete_layout
Moodle template for the layout of autocomplete elements.
Classes required for JS:
* none
Data attributes required for JS:
* data-region="form_autocomplete-input"
* data-region="form_autocomplete-suggestions"
* data-region="form_autocomplete-selection"
Context variables required for this template:
* none
Example context (json):
{}
}}
<div data-region="form_autocomplete-input"></div>
<div data-region="form_autocomplete-suggestions"></div>
<div data-region="form_autocomplete-selection"></div>

View File

@ -0,0 +1,49 @@
{{!
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/>.
}}
{{!
@template core_user/local/participantsfilter/autocomplete_selection
Moodle template for the wrapper of currently selected items in an autocomplate form element.
Classes required for JS:
* none
Data attributes required for JS:
* none
Context variables required for this template:
* multiple True if this field allows multiple selections
* selectionId The dom id of the current selection list.
* items List of items with label and value fields (used by the partial).
* noSelectionString String to use when no items are selected (used by the partial).
Example context (json):
{ "multiple": true, "selectionId": 1, "items": [
{ "label": "Item label with <strong>tags</strong>", "value": "5" },
{ "label": "Another item label with <strong>tags</strong>", "value": "4" }
], "noSelectionString": "No selection" }
}}
<div{{!
}} class="d-inline-block mb-0{{#multiple}} form-autocomplete-multiple h5{{/multiple}}"{{!
}} id="{{selectionId}}"{{!
}} role="list"{{!
}} aria-atomic="true"{{!
}}{{#multiple}} tabindex="0" {{/multiple}}{{!
}}>
<span class="accesshide">{{#str}}selecteditems, form{{/str}}</span>
{{> core/form_autocomplete_selection_items }}
</div>

View File

@ -0,0 +1,51 @@
{{!
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/>.
}}
{{!
@template core_user/local/participantsfilter/autocomplete_selection_items
Moodle template for the currently selected items in an autocomplete form element.
Classes required for JS:
* none
Data attributes required for JS:
* data-value
Context variables required for this template:
* items List of items with label and value fields.
* - value Value of the selected item.
* - label HTML representing the value.
* noSelectionString String to use when no items are selected
Example context (json):
{
"items": [
{ "label": "Item label with <strong>tags</strong>", "value": "5" },
{ "label": "Another item label with <strong>tags</strong>", "value": "4" }
],
"noSelectionString": "No selection"
}
}}
{{#items}}
<span role="listitem" data-value="{{value}}" aria-selected="true"
class="badge badge-secondary clickable text-wrap text-break line-height-4 mr-2 my-1">
{{label}}<i class="icon fa fa-times pl-2 mr-0"></i>
</span>
{{/items}}
{{^items}}
<span class="mb-3 mr-1">{{noSelectionString}}</span>
{{/items}}

View File

@ -0,0 +1,65 @@
{{!
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/>.
}}
{{!
@template core_user/local/participantsfilter/filterrow
Template for use by each filter condition.
Context variables required for this template:
* filtertypes - Array of filter types available.
Example context (json):
{
"filtertypes": [
{
"name": "status",
"title": "Status"
}
]
}
}}
<div data-filterregion="filter">
<div class="border-radius my-2 p-2 bg-white border d-flex flex-column flex-md-row align-items-md-start">
<div class="d-flex flex-column flex-md-row align-items-md-center">
<label for="core_user-local-participantsfilter-filterrow-jointype-{{uniqid}}" class="mr-md-2 mb-md-0">{{#str}}match, core_user{{/str}}</label>
<select class="custom-select mb-1 mb-md-0 mr-md-2" data-filterfield="join" id="core_user-local-participantsfilter-filterrow-jointype-{{uniqid}}">
<option value="0">{{#str}}none{{/str}}</option>
<option selected=selected value="1">{{#str}}any{{/str}}</option>
<option value="2">{{#str}}all{{/str}}</option>
</select>
</div>
<label class="sr-only pt-2" for="core_user-local-participantsfilter-filterrow-filtertype-{{uniqid}}">filtertype</label>
<select class="custom-select mb-1 mb-md-0 mr-md-2" data-filterfield="type" id="core_user-local-participantsfilter-filterrow-filtertype-{{uniqid}}">
<option value="">{{#str}}selectfiltertype, core_user{{/str}}</option>
{{#filtertypes}}
<option value="{{name}}">{{title}}</option>
{{/filtertypes}}
</select>
<div data-filterregion="value" class="d-md-flex flex-column align-items-start flex-lg-row"></div>
<button data-filteraction="remove" class="ml-auto icon-no-margin icon-size-4 btn text-reset" aria-label="{{#str}}clearfilterrow, core_user{{/str}}">
<i class="icon fa fa-times-circle"></i>
</button>
</div>
<div data-filterregion="joinadverb" class="pl-1 text-uppercase font-weight-bold">
<div data-filterverbfor="0">{{#str}}adverbfor_andnot, core_user{{/str}}</div>
<div data-filterverbfor="1">{{#str}}adverbfor_or, core_user{{/str}}</div>
<div data-filterverbfor="2">{{#str}}adverbfor_and, core_user{{/str}}</div>
</div>
</div>

View File

@ -0,0 +1,61 @@
{{!
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/>.
}}
{{!
@template core_user/local/participantsfilter/filtertype
Filter type data, not shown to users but used as a source of data for form autocompletion.
Classes required for JS:
* none
Data attributes required for JS:
* none
Context variables required for this template:
* filtertypes
Example context (json):
{
"name": "status",
"title": "Enrolment Status",
"allowcustom": "0",
"allowmultiple" false,
"filtertypeclass": "core_user/local/participantsfilter/filtertypes/courseid",
"values": [
{
"value": "0",
"title": "Inactive"
},
{
"value": "1",
"title": "Active"
}
]
}
}}
<select {{!
}}{{#allowmultiple}}multiple="multiple"{{/allowmultiple}} {{!
}}data-field-name="{{name}}" {{!
}}data-field-title="{{title}}" {{!
}}data-allow-custom="{{allowcustom}}" {{!
}}class="hidden" {{!
}}{{#filtertypeclass}}data-filter-type-class="{{filtertypeclass}}" {{/filtertypeclass}}{{!
}}>
{{#values}}
<option value="{{value}}">{{title}}</option>
{{/values}}
</select>

View File

@ -0,0 +1,64 @@
{{!
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/>.
}}
{{!
@template core_user/local/participantsfilter/filtertypes
Placeholder to fetch all filter types.
Classes required for JS:
* none
Data attributes required for JS:
* data-filterregion="filtertypedata"
Context variables required for this template:
* filtertypes
Example context (json):
{
"filtertypes": [
{
"name": "status",
"title": "Enrolment Status",
"allowcustom": "0",
"values": [
{
"value": "0",
"title": "Inactive"
},
{
"value": "1",
"title": "Active"
}
]
}
]
}
}}
<div class="hidden" data-filterregion="filtertypedata">
{{#filtertypes}}
{{> core_user/local/participantsfilter/filtertype}}
{{/filtertypes}}
</div>
<div class="hidden">
<select disabled="disabled" data-filterfield="type" data-filterregion="filtertypelist">
<option value="">{{#str}}selectfiltertype, core_user{{/str}}</option>
{{#filtertypes}}
<option value="{{name}}">{{title}}</option>
{{/filtertypes}}
</select>
</div>

View File

@ -0,0 +1,77 @@
{{!
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/>.
}}
{{!
@template core_user/participantsfilter
Template for the form containing one or more filter rows.
Example context (json):
{
"filtertypes": [
{
"name": "status",
"title": "Status",
"values": [
{
"value": 1,
"title": "Active"
},
{
"value": 0,
"title": "Suspended"
}
]
}
]
}
}}
<div id="core_user-participantsfilter-{{uniqid}}" class="filter-group my-2 p-2 bg-light border-radius border" data-table-region="{{tableregionid}}" data-table-course-id="{{courseid}}" data-filterverb="1">
<div data-filterregion="filtermatch" class="hidden">
<label for="core_user-local-participantsfilter-jointype-{{uniqid}}" class="my-0" aria-hidden="true">
{{#str}}match, core_user{{/str}}
</label>
<select class="custom-select" data-filterfield="join" id="core_user-local-participantsfilter-jointype-{{uniqid}}"
aria-label="{{#str}}filtersetmatchdescription, core_user{{/str}}">
<option value="0">{{#str}}none{{/str}}</option>
<option selected=selected value="1">{{#str}}any{{/str}}</option>
<option value="2">{{#str}}all{{/str}}</option>
</select>
<span aria-hidden="true">{{#str}}matchofthefollowing, core_user{{/str}}</span>
</div>
<div data-filterregion="filters">
{{> core_user/local/participantsfilter/filterrow }}
</div>
<div class="d-flex" data-filterregion="actions">
&nbsp;
<button type="button" class="btn btn-link text-reset" data-filteraction="add">
<i class="fa fa-plus"></i><span class="pl-3">{{#str}}addcondition, core_user{{/str}}</span>
</button>
<button data-filteraction="reset" type="button" class="btn btn-secondary ml-auto mr-2">{{#str}}clearfilters, core_user{{/str}}</button>
<button data-filteraction="apply" type="button" class="btn btn-primary">{{#str}}applyfilters, core_user{{/str}}</button>
</div>
{{> core_user/local/participantsfilter/filtertypes}}
</div>
{{#js}}
require(['core_user/participantsfilter'], function(ParticipantsFilter) {
ParticipantsFilter.init('core_user-participantsfilter-{{uniqid}}');
});
{{/js}}