MDL-67917 user: Add skeleton for new participants filter

Part of MDL-67743

AMOS BEGIN
  CPY [select,core],[selectfiltertype,core_user]
AMOS END
This commit is contained in:
Andrew Nicols 2020-03-25 15:22:00 +08:00
parent f456195599
commit 77ba77f10a
20 changed files with 1056 additions and 0 deletions

View File

@ -22,7 +22,12 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['addcondition'] = 'Add condition';
$string['applyfilters'] = 'Apply filters';
$string['clearfilterrow'] = 'Remove filter row';
$string['clearfilters'] = 'Clear filters';
$string['countparticipantsfound'] = '{$a} participants found';
$string['match'] = 'Match';
$string['privacy:courserequestpath'] = 'Requested courses';
$string['privacy:descriptionpath'] = 'Profile description';
$string['privacy:devicespath'] = 'User devices';
@ -126,6 +131,8 @@ $string['privacy:passwordresetpath'] = 'Password resets';
$string['privacy:profileimagespath'] = 'Profile images';
$string['privacy:privatefilespath'] = 'Private files';
$string['privacy:sessionpath'] = 'Session data';
$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.';
$string['typeorselect'] = 'Type or select...';

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,e;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:c=this.getFilterValueNode();c.innerHTML=this.getSourceDataForFilter().outerHTML;e=c.querySelector("select");a.t0=b.default;a.t1=e;a.t2="1"==e.dataset.allowCustom;a.next=8;return(0,d.get_string)("typeorselect","core_user");case 8:a.t3=a.sent;a.t4=!e.multiple;a.t0.enhance.call(a.t0,a.t1,a.t2,null,a.t3,!1,!0,null,a.t4);case 11: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:"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/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:{filterlist:b("filters"),datasource:b("filtertypedata")}},data:{fields:{byName:function byName(a){return"[data-field-name=\"".concat(a,"\"]")}},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","filterlist","datasource","data","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,UAAU,CAAEpB,CAAe,CAAC,SAAD,CADtB,CAELqB,UAAU,CAAErB,CAAe,CAAC,gBAAD,CAFtB,CAPF,CAfA,CA2BXsB,IAAI,CAAE,CACFb,MAAM,CAAE,CACJK,MAAM,CAAE,gBAAAC,CAAI,qCAAyBA,CAAzB,QADR,CADN,CAIFQ,QAAQ,CAAEvB,CAAe,CAAC,gBAAD,CAJvB,CA3BK,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 filterlist: getFilterRegion('filters'),\n datasource: getFilterRegion('filtertypedata'),\n },\n },\n data: {\n fields: {\n byName: name => `[data-field-name=\"${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,180 @@
// 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
}
/**
* 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 getString('typeorselect', 'core_user'),
// Disable case sensitivity on searches.
false,
// Show suggestions.
true,
// Do not override the 'no suggestions' string.
null,
// Close the suggestions if this is not a multi-select.
!dataSource.multiple
);
}
/**
* 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,62 @@
// 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: {
filterlist: getFilterRegion('filters'),
datasource: getFilterRegion('filtertypedata'),
},
},
data: {
fields: {
byName: name => `[data-field-name="${name}"]`,
},
typeList: getFilterRegion('filtertypelist'),
},
};

View File

@ -0,0 +1,330 @@
// 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;
}
});
});
};
/**
* Update the Dynamic table based upon the current filter.
*
* @return {Promise}
*/
const updateTableFromFilter = () => {
// TODO The main join type does not exist yet.
const joinType = 1;
return DynamicTable.setFilters(
DynamicTable.getTableFromId(filterSet.dataset.tableRegion),
{
filters: Object.values(activeFilters).map(filter => filter.filterValue),
jointype: joinType,
}
);
};
// 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);
}
});
};

View File

@ -0,0 +1,150 @@
<?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;
/**
* 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;
}
/**
* Get data for all filter types.
*
* @return array
*/
protected function get_filtertypes(): array {
$filtertypes = [];
if ($filtertype = $this->get_enrolmentstatus_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'),
],
]
);
}
/**
* 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
* @return stdClass|null
*/
protected function get_filter_object(
string $name,
string $title,
bool $custom,
bool $multiple,
?string $filterclass,
array $values
): ?stdClass {
if (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');
@ -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,56 @@
{{!
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" class="rounded mb-3 p-2 bg-white border border-secondary d-flex align-items-center">
<label for="core_user-local-participantsfilter-filterrow-jointype-{{uniqid}}" class="pt-2">{{#str}}match, core_user{{/str}}</label>
<select class="custom-select" 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>
<label class="sr-only pt-2" for="core_user-local-participantsfilter-filterrow-filtertype-{{uniqid}}">filtertype</label>
<select class="custom-select" 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"></div>
<button data-filteraction="remove" class="ml-auto btn btn-link text-reset" aria-label="{{#str}}clearfilterrow, core_user{{/str}}">
<i class="icon fa fa-times-circle pt-2"></i>
</button>
</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,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/>.
}}
{{!
@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 mt-5 p-3 rounded border border-secondary" data-table-region="{{tableregionid}}" data-table-course-id="{{courseid}}">
<div data-filterregion="filters">
{{> core_user/local/participantsfilter/filterrow }}
</div>
<div class="display-block" data-filterregion="actions">
&nbsp;
<button type="button" class="btn btn-link d-inline-block float-left" data-filteraction="add">
<i class="fa fa-plus"></i><span class="pl-3">{{#str}}addcondition, core_user{{/str}}</span>
</button>
<div class="float-right">
<button data-filteraction="reset" type="button" class="btn btn-light d-inline-block">{{#str}}clearfilters, core_user{{/str}}</button>
<button data-filteraction="apply" type="button" class="btn btn-primary d-inline-block">{{#str}}applyfilters, core_user{{/str}}</button>
</div>
</div>
{{> core_user/local/participantsfilter/filtertypes}}
</div>
{{#js}}
require(['core_user/participantsfilter'], function(ParticipantsFilter) {
ParticipantsFilter.init('core_user-participantsfilter-{{uniqid}}');
});
{{/js}}