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
40 changed files with 1717 additions and 29 deletions
+14
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
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+31 -15
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);
+27 -6
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);
},
};
});
+1
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'],
+1 -1
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'];
}
}
}
@@ -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}}
@@ -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>
+6
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;
+11
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;
}
+9
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; }
+12
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; }
+2
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
@@ -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
@@ -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 <[email protected]>\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"}
@@ -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
@@ -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 <[email protected]>\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"}
@@ -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
@@ -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 <[email protected]>\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
@@ -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 <[email protected]>
* @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,
};
}
}
@@ -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 <[email protected]>
* @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)],
};
}
}
@@ -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 <[email protected]>
* @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;
}
}
@@ -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 <[email protected]>
* @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'),
},
};
+348
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 <[email protected]>
* @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;
});
};
+393
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 <[email protected]>
* @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 <[email protected]>
* @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,
];
}
}
+7 -1
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.
+14
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.
*
@@ -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>
@@ -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>
@@ -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}}
@@ -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>
@@ -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>
@@ -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>
@@ -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}}