diff --git a/lib/amd/build/datafilter.min.js b/lib/amd/build/datafilter.min.js
index 5e798a45c6e..cd799e28f89 100644
--- a/lib/amd/build/datafilter.min.js
+++ b/lib/amd/build/datafilter.min.js
@@ -1,3 +1,3 @@
-define("core/datafilter",["exports","core/datafilter/filtertypes/courseid","core/datafilter/filtertype","core/str","core/notification","core/pending","core/datafilter/selectors","core/templates","core/custom_interaction_events","jquery"],(function(_exports,_courseid,_filtertype,_str,_notification,_pending,_selectors,_templates,_custom_interaction_events,_jquery){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_courseid=_interopRequireDefault(_courseid),_filtertype=_interopRequireDefault(_filtertype),_notification=_interopRequireDefault(_notification),_pending=_interopRequireDefault(_pending),_selectors=_interopRequireDefault(_selectors),_templates=_interopRequireDefault(_templates),_custom_interaction_events=_interopRequireDefault(_custom_interaction_events),_jquery=_interopRequireDefault(_jquery);var _systemImportTransformerGlobalIdentifier="undefined"!=typeof window?window:"undefined"!=typeof self?self:"undefined"!=typeof global?global:{};function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}return _exports.default=class{constructor(filterSet,applyCallback){this.filterSet=filterSet,this.applyCallback=applyCallback,this.activeFilters={courseid:new _courseid.default("courseid",filterSet)}}init(){this.filterSet.querySelector(_selectors.default.filterset.region).addEventListener("click",(e=>{e.target.closest(_selectors.default.filterset.actions.addRow)&&(e.preventDefault(),this.addFilterRow()),e.target.closest(_selectors.default.filterset.actions.applyFilters)&&(e.preventDefault(),this.updateTableFromFilter()),e.target.closest(_selectors.default.filterset.actions.resetFilters)&&(e.preventDefault(),this.removeAllFilters())})),this.filterSet.querySelector(_selectors.default.filterset.regions.filterlist).addEventListener("click",(e=>{e.target.closest(_selectors.default.filter.actions.remove)&&(e.preventDefault(),this.removeOrReplaceFilterRow(e.target.closest(_selectors.default.filter.region),!0))}));let filterRegion=(0,_jquery.default)(this.getFilterRegion());_custom_interaction_events.default.define(filterRegion,[_custom_interaction_events.default.events.accessibleChange]),filterRegion.on(_custom_interaction_events.default.events.accessibleChange,(e=>{const typeField=e.target.closest(_selectors.default.filter.fields.type);if(typeField&&typeField.value){const filter=e.target.closest(_selectors.default.filter.region);this.addFilter(filter,typeField.value)}})),this.filterSet.querySelector(_selectors.default.filterset.fields.join).addEventListener("change",(e=>{this.filterSet.dataset.filterverb=e.target.value}))}getFilterRegion(){return this.filterSet.querySelector(_selectors.default.filterset.regions.filterlist)}addFilterRow(){const pendingPromise=new _pending.default("core/datafilter:addFilterRow"),rownum=1+this.getFilterRegion().querySelectorAll(_selectors.default.filter.region).length;return _templates.default.renderForPromise("core/datafilter/filter_row",{rownumber:rownum}).then((_ref=>{let{html:html,js:js}=_ref;return _templates.default.appendNodeContents(this.getFilterRegion(),html,js)})).then((filterRow=>{const typeList=this.filterSet.querySelector(_selectors.default.data.typeList);return filterRow.forEach((contentNode=>{const contentTypeList=contentNode.querySelector(_selectors.default.filter.fields.type);contentTypeList&&(contentTypeList.innerHTML=typeList.innerHTML)})),filterRow})).then((filterRow=>(this.updateFiltersOptions(),filterRow))).then((result=>(pendingPromise.resolve(),result))).catch(_notification.default.exception)}getFilterDataSource(filterType){return this.filterSet.querySelector(_selectors.default.filterset.regions.datasource).querySelector(_selectors.default.data.fields.byName(filterType))}async addFilter(filterRow,filterType,initialFilterValues){filterRow.dataset.filterType=filterType;const filterDataNode=this.getFilterDataSource(filterType);let Filter=_filtertype.default;filterDataNode.dataset.filterTypeClass&&(Filter=await("function"==typeof _systemImportTransformerGlobalIdentifier.define&&_systemImportTransformerGlobalIdentifier.define.amd?new Promise((function(resolve,reject){_systemImportTransformerGlobalIdentifier.require([filterDataNode.dataset.filterTypeClass],resolve,reject)})):"undefined"!=typeof module&&module.exports&&"undefined"!=typeof require||"undefined"!=typeof module&&module.component&&_systemImportTransformerGlobalIdentifier.require&&"component"===_systemImportTransformerGlobalIdentifier.require.loader?Promise.resolve(require(filterDataNode.dataset.filterTypeClass)):Promise.resolve(_systemImportTransformerGlobalIdentifier[filterDataNode.dataset.filterTypeClass]))),this.activeFilters[filterType]=new Filter(filterType,this.filterSet,initialFilterValues);const typeField=filterRow.querySelector(_selectors.default.filter.fields.type);return typeField.value=filterType,typeField.disabled="disabled",this.updateFiltersOptions(),this.activeFilters[filterType]}getFilterObject(name){return this.activeFilters[name]}removeOrReplaceFilterRow(filterRow,refreshContent){1===this.getFilterRegion().querySelectorAll(_selectors.default.filter.region).length?this.replaceFilterRow(filterRow,refreshContent):this.removeFilterRow(filterRow,refreshContent)}async removeFilterRow(filterRow){let refreshContent=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];if(filterRow.querySelector(_selectors.default.data.required))return;const hasFilterValue=!!filterRow.querySelector(_selectors.default.filter.fields.type).value;this.removeFilterObject(filterRow.dataset.filterType),filterRow.remove(),this.updateFiltersOptions(),hasFilterValue&&refreshContent&&this.updateTableFromFilter();const filterLegends=await this.getAvailableFilterLegends();this.getFilterRegion().querySelectorAll(_selectors.default.filter.region).forEach(((filterRow,index)=>{filterRow.querySelector("legend").innerText=filterLegends[index]}))}replaceFilterRow(filterRow){let refreshContent=!(arguments.length>1&&void 0!==arguments[1])||arguments[1],rowNum=arguments.length>2&&void 0!==arguments[2]?arguments[2]:1;if(!filterRow.querySelector(_selectors.default.data.required))return this.removeFilterObject(filterRow.dataset.filterType),_templates.default.renderForPromise("core/datafilter/filter_row",{rownumber:rowNum}).then((_ref2=>{let{html:html,js:js}=_ref2;return _templates.default.replaceNode(filterRow,html,js)})).then((filterRow=>{const typeList=this.filterSet.querySelector(_selectors.default.data.typeList);return filterRow.forEach((contentNode=>{const contentTypeList=contentNode.querySelector(_selectors.default.filter.fields.type);contentTypeList&&(contentTypeList.innerHTML=typeList.innerHTML)})),filterRow})).then((filterRow=>(this.updateFiltersOptions(),filterRow))).then((filterRow=>refreshContent?this.updateTableFromFilter():filterRow)).catch(_notification.default.exception)}removeFilterObject(filterName){if(filterName){const filter=this.getFilterObject(filterName);filter&&(filter.tearDown(),delete this.activeFilters[filterName])}}removeAllFilters(){return this.getFilterRegion().querySelectorAll(_selectors.default.filter.region).forEach((filterRow=>this.removeOrReplaceFilterRow(filterRow,!1))),this.updateTableFromFilter()}removeEmptyFilters(){this.getFilterRegion().querySelectorAll(_selectors.default.filter.region).forEach((filterRow=>{filterRow.querySelector(_selectors.default.filter.fields.type).value||this.removeOrReplaceFilterRow(filterRow,!1)}))}updateFiltersOptions(){const filters=this.getFilterRegion().querySelectorAll(_selectors.default.filter.region);filters.forEach((filterRow=>{filterRow.querySelectorAll(_selectors.default.filter.fields.type+" option").forEach((option=>{option.value===filterRow.dataset.filterType?(option.classList.remove("hidden"),option.disabled=!1):this.activeFilters[option.value]?(option.classList.add("hidden"),option.disabled=!0):(option.classList.remove("hidden"),option.disabled=!1)}))}));const addRowButton=this.filterSet.querySelector(_selectors.default.filterset.actions.addRow);this.filterSet.querySelectorAll(_selectors.default.data.fields.all).length<=filters.length?addRowButton.setAttribute("disabled","disabled"):addRowButton.removeAttribute("disabled"),1===filters.length?(this.filterSet.querySelector(_selectors.default.filterset.regions.filtermatch).classList.add("hidden"),this.filterSet.querySelector(_selectors.default.filterset.fields.join).value=2,this.filterSet.dataset.filterverb=2):this.filterSet.querySelector(_selectors.default.filterset.regions.filtermatch).classList.remove("hidden")}updateTableFromFilter(){const pendingPromise=new _pending.default("core/datafilter:updateTableFromFilter"),filters={};Object.values(this.activeFilters).forEach((filter=>{filters[filter.filterValue.name]=filter.filterValue})),this.applyCallback&&this.applyCallback(filters,pendingPromise)}async getAvailableFilterLegends(){const maxFilters=document.querySelector(_selectors.default.data.typeListSelect).length-1;let requests=[];[...Array(maxFilters)].forEach(((_,rowIndex)=>{requests.push({key:"filterrowlegend",component:"core",param:rowIndex+1})}));return await(0,_str.get_strings)(requests).then((fetchedStrings=>fetchedStrings)).catch(_notification.default.exception)}},_exports.default}));
+define("core/datafilter",["exports","core/datafilter/filtertypes/courseid","core/datafilter/filtertype","core/str","core/notification","core/pending","core/datafilter/selectors","core/templates","core/custom_interaction_events","jquery"],(function(_exports,_courseid,_filtertype,_str,_notification,_pending,_selectors,_templates,_custom_interaction_events,_jquery){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_courseid=_interopRequireDefault(_courseid),_filtertype=_interopRequireDefault(_filtertype),_notification=_interopRequireDefault(_notification),_pending=_interopRequireDefault(_pending),_selectors=_interopRequireDefault(_selectors),_templates=_interopRequireDefault(_templates),_custom_interaction_events=_interopRequireDefault(_custom_interaction_events),_jquery=_interopRequireDefault(_jquery);var _systemImportTransformerGlobalIdentifier="undefined"!=typeof window?window:"undefined"!=typeof self?self:"undefined"!=typeof global?global:{};function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}return _exports.default=class{constructor(filterSet,applyCallback){this.filterSet=filterSet,this.applyCallback=applyCallback,this.activeFilters={courseid:new _courseid.default("courseid",filterSet)}}init(){this.filterSet.querySelector(_selectors.default.filterset.region).addEventListener("click",(e=>{e.target.closest(_selectors.default.filterset.actions.addRow)&&(e.preventDefault(),this.addFilterRow()),e.target.closest(_selectors.default.filterset.actions.applyFilters)&&(e.preventDefault(),this.updateTableFromFilter()),e.target.closest(_selectors.default.filterset.actions.resetFilters)&&(e.preventDefault(),this.removeAllFilters())})),this.filterSet.querySelector(_selectors.default.filterset.regions.filterlist).addEventListener("click",(e=>{e.target.closest(_selectors.default.filter.actions.remove)&&(e.preventDefault(),this.removeOrReplaceFilterRow(e.target.closest(_selectors.default.filter.region),!0))}));let filterRegion=(0,_jquery.default)(this.getFilterRegion());_custom_interaction_events.default.define(filterRegion,[_custom_interaction_events.default.events.accessibleChange]),filterRegion.on(_custom_interaction_events.default.events.accessibleChange,(e=>{const typeField=e.target.closest(_selectors.default.filter.fields.type);if(typeField&&typeField.value){const filter=e.target.closest(_selectors.default.filter.region);this.addFilter(filter,typeField.value)}})),this.filterSet.querySelector(_selectors.default.filterset.fields.join).addEventListener("change",(e=>{this.filterSet.dataset.filterverb=e.target.value}))}getFilterRegion(){return this.filterSet.querySelector(_selectors.default.filterset.regions.filterlist)}addFilterRow(){var _filterdata$rownum;let filterdata=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};const pendingPromise=new _pending.default("core/datafilter:addFilterRow"),rownum=null!==(_filterdata$rownum=filterdata.rownum)&&void 0!==_filterdata$rownum?_filterdata$rownum:1+this.getFilterRegion().querySelectorAll(_selectors.default.filter.region).length;return _templates.default.renderForPromise("core/datafilter/filter_row",{rownumber:rownum}).then((_ref=>{let{html:html,js:js}=_ref;return _templates.default.appendNodeContents(this.getFilterRegion(),html,js)})).then((filterRow=>{const typeList=this.filterSet.querySelector(_selectors.default.data.typeList);return filterRow.forEach((contentNode=>{const contentTypeList=contentNode.querySelector(_selectors.default.filter.fields.type);contentTypeList&&(contentTypeList.innerHTML=typeList.innerHTML)})),filterRow})).then((filterRow=>(this.updateFiltersOptions(),filterRow))).then((result=>(pendingPromise.resolve(),filterdata.filtertype&&result.forEach((filter=>{this.addFilter(filter,filterdata.filtertype,filterdata.values,filterdata.jointype,filterdata.filteroptions)})),result))).catch(_notification.default.exception)}getFilterDataSource(filterType){return this.filterSet.querySelector(_selectors.default.filterset.regions.datasource).querySelector(_selectors.default.data.fields.byName(filterType))}async addFilter(filterRow,filterType,initialFilterValues,filterJoin,filterOptions){filterRow.dataset.filterType=filterType;const filterDataNode=this.getFilterDataSource(filterType);let Filter=_filtertype.default;filterDataNode.dataset.filterTypeClass&&(Filter=await("function"==typeof _systemImportTransformerGlobalIdentifier.define&&_systemImportTransformerGlobalIdentifier.define.amd?new Promise((function(resolve,reject){_systemImportTransformerGlobalIdentifier.require([filterDataNode.dataset.filterTypeClass],resolve,reject)})):"undefined"!=typeof module&&module.exports&&"undefined"!=typeof require||"undefined"!=typeof module&&module.component&&_systemImportTransformerGlobalIdentifier.require&&"component"===_systemImportTransformerGlobalIdentifier.require.loader?Promise.resolve(require(filterDataNode.dataset.filterTypeClass)):Promise.resolve(_systemImportTransformerGlobalIdentifier[filterDataNode.dataset.filterTypeClass]))),this.activeFilters[filterType]=new Filter(filterType,this.filterSet,initialFilterValues,filterOptions);const typeField=filterRow.querySelector(_selectors.default.filter.fields.type);return typeField.value=filterType,typeField.disabled="disabled",this.updateFiltersOptions(),this.activeFilters[filterType]}getFilterObject(name){return this.activeFilters[name]}removeOrReplaceFilterRow(filterRow,refreshContent){1===this.getFilterRegion().querySelectorAll(_selectors.default.filter.region).length?this.replaceFilterRow(filterRow,refreshContent):this.removeFilterRow(filterRow,refreshContent)}async removeFilterRow(filterRow){let refreshContent=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];if(filterRow.querySelector(_selectors.default.data.required))return;const hasFilterValue=!!filterRow.querySelector(_selectors.default.filter.fields.type).value;this.removeFilterObject(filterRow.dataset.filterType),filterRow.remove(),this.updateFiltersOptions(),hasFilterValue&&refreshContent&&this.updateTableFromFilter();const filterLegends=await this.getAvailableFilterLegends();this.getFilterRegion().querySelectorAll(_selectors.default.filter.region).forEach(((filterRow,index)=>{filterRow.querySelector("legend").innerText=filterLegends[index]}))}replaceFilterRow(filterRow){let refreshContent=!(arguments.length>1&&void 0!==arguments[1])||arguments[1],rowNum=arguments.length>2&&void 0!==arguments[2]?arguments[2]:1;if(!filterRow.querySelector(_selectors.default.data.required))return this.removeFilterObject(filterRow.dataset.filterType),_templates.default.renderForPromise("core/datafilter/filter_row",{rownumber:rowNum}).then((_ref2=>{let{html:html,js:js}=_ref2;return _templates.default.replaceNode(filterRow,html,js)})).then((filterRow=>{const typeList=this.filterSet.querySelector(_selectors.default.data.typeList);return filterRow.forEach((contentNode=>{const contentTypeList=contentNode.querySelector(_selectors.default.filter.fields.type);contentTypeList&&(contentTypeList.innerHTML=typeList.innerHTML)})),filterRow})).then((filterRow=>(this.updateFiltersOptions(),filterRow))).then((filterRow=>refreshContent?this.updateTableFromFilter():filterRow)).catch(_notification.default.exception)}removeFilterObject(filterName){if(filterName){const filter=this.getFilterObject(filterName);filter&&(filter.tearDown(),delete this.activeFilters[filterName])}}removeAllFilters(){return this.getFilterRegion().querySelectorAll(_selectors.default.filter.region).forEach((filterRow=>this.removeOrReplaceFilterRow(filterRow,!1))),this.updateTableFromFilter()}removeEmptyFilters(){this.getFilterRegion().querySelectorAll(_selectors.default.filter.region).forEach((filterRow=>{filterRow.querySelector(_selectors.default.filter.fields.type).value||this.removeOrReplaceFilterRow(filterRow,!1)}))}updateFiltersOptions(){const filters=this.getFilterRegion().querySelectorAll(_selectors.default.filter.region);filters.forEach((filterRow=>{filterRow.querySelectorAll(_selectors.default.filter.fields.type+" option").forEach((option=>{option.value===filterRow.dataset.filterType?(option.classList.remove("hidden"),option.disabled=!1):this.activeFilters[option.value]?(option.classList.add("hidden"),option.disabled=!0):(option.classList.remove("hidden"),option.disabled=!1)}))}));const addRowButton=this.filterSet.querySelector(_selectors.default.filterset.actions.addRow);this.filterSet.querySelectorAll(_selectors.default.data.fields.all).length<=filters.length?addRowButton.setAttribute("disabled","disabled"):addRowButton.removeAttribute("disabled"),1===filters.length?(this.filterSet.querySelector(_selectors.default.filterset.regions.filtermatch).classList.add("hidden"),this.filterSet.querySelector(_selectors.default.filterset.fields.join).value=2,this.filterSet.dataset.filterverb=2):this.filterSet.querySelector(_selectors.default.filterset.regions.filtermatch).classList.remove("hidden")}updateTableFromFilter(){const pendingPromise=new _pending.default("core/datafilter:updateTableFromFilter"),filters={};Object.values(this.activeFilters).forEach((filter=>{filters[filter.filterValue.name]=filter.filterValue})),this.applyCallback&&this.applyCallback(filters,pendingPromise)}async getAvailableFilterLegends(){const maxFilters=document.querySelector(_selectors.default.data.typeListSelect).length-1;let requests=[];[...Array(maxFilters)].forEach(((_,rowIndex)=>{requests.push({key:"filterrowlegend",component:"core",param:rowIndex+1})}));return await(0,_str.get_strings)(requests).then((fetchedStrings=>fetchedStrings)).catch(_notification.default.exception)}},_exports.default}));
//# sourceMappingURL=datafilter.min.js.map
\ No newline at end of file
diff --git a/lib/amd/build/datafilter.min.js.map b/lib/amd/build/datafilter.min.js.map
index d203944e8cb..c953a4f92c5 100644
--- a/lib/amd/build/datafilter.min.js.map
+++ b/lib/amd/build/datafilter.min.js.map
@@ -1 +1 @@
-{"version":3,"file":"datafilter.min.js","sources":["../src/datafilter.js"],"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 .\n\n/**\n * Data filter management.\n *\n * @module core/datafilter\n * @copyright 2020 Andrew Nicols \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport CourseFilter from 'core/datafilter/filtertypes/courseid';\nimport GenericFilter from 'core/datafilter/filtertype';\nimport {get_strings as getStrings} from 'core/str';\nimport Notification from 'core/notification';\nimport Pending from 'core/pending';\nimport Selectors from 'core/datafilter/selectors';\nimport Templates from 'core/templates';\nimport CustomEvents from 'core/custom_interaction_events';\nimport jQuery from 'jquery';\n\nexport default class {\n\n /**\n * Initialise the filter on the element with the given filterSet and callback.\n *\n * @param {HTMLElement} filterSet The filter element.\n * @param {Function} applyCallback Callback function when updateTableFromFilter\n */\n constructor(filterSet, applyCallback) {\n\n this.filterSet = filterSet;\n this.applyCallback = applyCallback;\n // Keep a reference to all of the active filters.\n this.activeFilters = {\n courseid: new CourseFilter('courseid', filterSet),\n };\n }\n\n /**\n * Initialise event listeners to the filter.\n */\n init() {\n // Add listeners for the main actions.\n this.filterSet.querySelector(Selectors.filterset.region).addEventListener('click', e => {\n if (e.target.closest(Selectors.filterset.actions.addRow)) {\n e.preventDefault();\n\n this.addFilterRow();\n }\n\n if (e.target.closest(Selectors.filterset.actions.applyFilters)) {\n e.preventDefault();\n\n this.updateTableFromFilter();\n }\n\n if (e.target.closest(Selectors.filterset.actions.resetFilters)) {\n e.preventDefault();\n\n this.removeAllFilters();\n }\n });\n\n // Add the listener to remove a single filter.\n this.filterSet.querySelector(Selectors.filterset.regions.filterlist).addEventListener('click', e => {\n if (e.target.closest(Selectors.filter.actions.remove)) {\n e.preventDefault();\n\n this.removeOrReplaceFilterRow(e.target.closest(Selectors.filter.region), true);\n }\n });\n\n // Add listeners for the filter type selection.\n let filterRegion = jQuery(this.getFilterRegion());\n CustomEvents.define(filterRegion, [CustomEvents.events.accessibleChange]);\n filterRegion.on(CustomEvents.events.accessibleChange, e => {\n const typeField = e.target.closest(Selectors.filter.fields.type);\n if (typeField && typeField.value) {\n const filter = e.target.closest(Selectors.filter.region);\n\n this.addFilter(filter, typeField.value);\n }\n });\n\n this.filterSet.querySelector(Selectors.filterset.fields.join).addEventListener('change', e => {\n this.filterSet.dataset.filterverb = e.target.value;\n });\n }\n\n /**\n * Get the filter list region.\n *\n * @return {HTMLElement}\n */\n getFilterRegion() {\n return this.filterSet.querySelector(Selectors.filterset.regions.filterlist);\n }\n\n /**\n * Add an unselected filter row.\n *\n * @return {Promise}\n */\n addFilterRow() {\n const pendingPromise = new Pending('core/datafilter:addFilterRow');\n const rownum = 1 + this.getFilterRegion().querySelectorAll(Selectors.filter.region).length;\n return Templates.renderForPromise('core/datafilter/filter_row', {\"rownumber\": rownum})\n .then(({html, js}) => {\n const newContentNodes = Templates.appendNodeContents(this.getFilterRegion(), html, js);\n\n return newContentNodes;\n })\n .then(filterRow => {\n // Note: This is a nasty hack.\n // We should try to find a better way of doing this.\n // We do not have the list of types in a readily consumable format, so we take the pre-rendered one and copy\n // it in place.\n const typeList = this.filterSet.querySelector(Selectors.data.typeList);\n\n filterRow.forEach(contentNode => {\n const contentTypeList = contentNode.querySelector(Selectors.filter.fields.type);\n\n if (contentTypeList) {\n contentTypeList.innerHTML = typeList.innerHTML;\n }\n });\n\n return filterRow;\n })\n .then(filterRow => {\n this.updateFiltersOptions();\n\n return filterRow;\n })\n .then(result => {\n pendingPromise.resolve();\n\n return result;\n })\n .catch(Notification.exception);\n }\n\n /**\n * Get the filter data source node fro the specified filter type.\n *\n * @param {String} filterType\n * @return {HTMLElement}\n */\n getFilterDataSource(filterType) {\n const filterDataNode = this.filterSet.querySelector(Selectors.filterset.regions.datasource);\n\n return filterDataNode.querySelector(Selectors.data.fields.byName(filterType));\n }\n\n /**\n * Add a filter to the list of active filters, performing any necessary setup.\n *\n * @param {HTMLElement} filterRow\n * @param {String} filterType\n * @param {Array} initialFilterValues The initially selected values for the filter\n * @returns {Filter}\n */\n async addFilter(filterRow, filterType, initialFilterValues) {\n // Name the filter on the filter row.\n filterRow.dataset.filterType = filterType;\n\n const filterDataNode = this.getFilterDataSource(filterType);\n\n // Instantiate the Filter class.\n let Filter = GenericFilter;\n if (filterDataNode.dataset.filterTypeClass) {\n Filter = await import(filterDataNode.dataset.filterTypeClass);\n }\n this.activeFilters[filterType] = new Filter(filterType, this.filterSet, initialFilterValues);\n\n // Disable the select.\n const typeField = filterRow.querySelector(Selectors.filter.fields.type);\n typeField.value = filterType;\n typeField.disabled = 'disabled';\n\n // Update the list of available filter types.\n this.updateFiltersOptions();\n\n return this.activeFilters[filterType];\n }\n\n /**\n * Get the registered filter class for the named filter.\n *\n * @param {String} name\n * @return {Object} See the Filter class.\n */\n getFilterObject(name) {\n return this.activeFilters[name];\n }\n\n /**\n * Remove or replace the specified filter row and associated class, ensuring that if there is only one filter row,\n * that it is replaced instead of being removed.\n *\n * @param {HTMLElement} filterRow\n * @param {Bool} refreshContent Whether to refresh the table content when removing\n */\n removeOrReplaceFilterRow(filterRow, refreshContent) {\n const filterCount = this.getFilterRegion().querySelectorAll(Selectors.filter.region).length;\n if (filterCount === 1) {\n this.replaceFilterRow(filterRow, refreshContent);\n } else {\n this.removeFilterRow(filterRow, refreshContent);\n }\n }\n\n /**\n * Remove the specified filter row and associated class.\n *\n * @param {HTMLElement} filterRow\n * @param {Bool} refreshContent Whether to refresh the table content when removing\n */\n async removeFilterRow(filterRow, refreshContent = true) {\n if (filterRow.querySelector(Selectors.data.required)) {\n return;\n }\n const filterType = filterRow.querySelector(Selectors.filter.fields.type);\n const hasFilterValue = !!filterType.value;\n\n // Remove the filter object.\n this.removeFilterObject(filterRow.dataset.filterType);\n\n // Remove the actual filter HTML.\n filterRow.remove();\n\n // Update the list of available filter types.\n this.updateFiltersOptions();\n\n if (hasFilterValue && refreshContent) {\n // Refresh the table if there was any content in this row.\n this.updateTableFromFilter();\n }\n\n // Update filter fieldset legends.\n const filterLegends = await this.getAvailableFilterLegends();\n\n this.getFilterRegion().querySelectorAll(Selectors.filter.region).forEach((filterRow, index) => {\n filterRow.querySelector('legend').innerText = filterLegends[index];\n });\n\n }\n\n /**\n * Replace the specified filter row with a new one.\n *\n * @param {HTMLElement} filterRow\n * @param {Bool} refreshContent Whether to refresh the table content when removing\n * @param {Number} rowNum The number used to label the filter fieldset legend (eg Row 1). Defaults to 1 (the first filter).\n * @return {Promise}\n */\n replaceFilterRow(filterRow, refreshContent = true, rowNum = 1) {\n if (filterRow.querySelector(Selectors.data.required)) {\n return;\n }\n // Remove the filter object.\n this.removeFilterObject(filterRow.dataset.filterType);\n\n return Templates.renderForPromise('core/datafilter/filter_row', {\"rownumber\": rowNum})\n .then(({html, js}) => {\n const newContentNodes = Templates.replaceNode(filterRow, html, js);\n\n return newContentNodes;\n })\n .then(filterRow => {\n // Note: This is a nasty hack.\n // We should try to find a better way of doing this.\n // We do not have the list of types in a readily consumable format, so we take the pre-rendered one and copy\n // it in place.\n const typeList = this.filterSet.querySelector(Selectors.data.typeList);\n\n filterRow.forEach(contentNode => {\n const contentTypeList = contentNode.querySelector(Selectors.filter.fields.type);\n\n if (contentTypeList) {\n contentTypeList.innerHTML = typeList.innerHTML;\n }\n });\n\n return filterRow;\n })\n .then(filterRow => {\n this.updateFiltersOptions();\n\n return filterRow;\n })\n .then(filterRow => {\n // Refresh the table.\n if (refreshContent) {\n return this.updateTableFromFilter();\n } else {\n return filterRow;\n }\n })\n .catch(Notification.exception);\n }\n\n /**\n * Remove the Filter Object from the register.\n *\n * @param {string} filterName The name of the filter to be removed\n */\n removeFilterObject(filterName) {\n if (filterName) {\n const filter = this.getFilterObject(filterName);\n if (filter) {\n filter.tearDown();\n\n // Remove from the list of active filters.\n delete this.activeFilters[filterName];\n }\n }\n }\n\n /**\n * Remove all filters.\n *\n * @returns {Promise}\n */\n removeAllFilters() {\n const filters = this.getFilterRegion().querySelectorAll(Selectors.filter.region);\n filters.forEach(filterRow => this.removeOrReplaceFilterRow(filterRow, false));\n\n // Refresh the table.\n return this.updateTableFromFilter();\n }\n\n /**\n * Remove any empty filters.\n */\n removeEmptyFilters() {\n const filters = this.getFilterRegion().querySelectorAll(Selectors.filter.region);\n filters.forEach(filterRow => {\n const filterType = filterRow.querySelector(Selectors.filter.fields.type);\n if (!filterType.value) {\n this.removeOrReplaceFilterRow(filterRow, false);\n }\n });\n }\n\n /**\n * Update the list of filter types to filter out those already selected.\n */\n updateFiltersOptions() {\n const filters = this.getFilterRegion().querySelectorAll(Selectors.filter.region);\n filters.forEach(filterRow => {\n const options = filterRow.querySelectorAll(Selectors.filter.fields.type + ' option');\n options.forEach(option => {\n if (option.value === filterRow.dataset.filterType) {\n option.classList.remove('hidden');\n option.disabled = false;\n } else if (this.activeFilters[option.value]) {\n option.classList.add('hidden');\n option.disabled = true;\n } else {\n option.classList.remove('hidden');\n option.disabled = false;\n }\n });\n });\n\n // Configure the state of the \"Add row\" button.\n // This button is disabled when there is a filter row available for each condition.\n const addRowButton = this.filterSet.querySelector(Selectors.filterset.actions.addRow);\n const filterDataNode = this.filterSet.querySelectorAll(Selectors.data.fields.all);\n if (filterDataNode.length <= filters.length) {\n addRowButton.setAttribute('disabled', 'disabled');\n } else {\n addRowButton.removeAttribute('disabled');\n }\n\n if (filters.length === 1) {\n this.filterSet.querySelector(Selectors.filterset.regions.filtermatch).classList.add('hidden');\n this.filterSet.querySelector(Selectors.filterset.fields.join).value = 2;\n this.filterSet.dataset.filterverb = 2;\n } else {\n this.filterSet.querySelector(Selectors.filterset.regions.filtermatch).classList.remove('hidden');\n }\n }\n\n /**\n * Update the Dynamic table based upon the current filter.\n */\n updateTableFromFilter() {\n const pendingPromise = new Pending('core/datafilter:updateTableFromFilter');\n\n const filters = {};\n Object.values(this.activeFilters).forEach(filter => {\n filters[filter.filterValue.name] = filter.filterValue;\n });\n\n if (this.applyCallback) {\n this.applyCallback(filters, pendingPromise);\n }\n }\n\n /**\n * Fetch the strings used to populate the fieldset legends for the maximum number of filters possible.\n *\n * @return {array}\n */\n async getAvailableFilterLegends() {\n const maxFilters = document.querySelector(Selectors.data.typeListSelect).length - 1;\n let requests = [];\n\n [...Array(maxFilters)].forEach((_, rowIndex) => {\n requests.push({\n \"key\": \"filterrowlegend\",\n \"component\": \"core\",\n // Add 1 since rows begin at 1 (index begins at zero).\n \"param\": rowIndex + 1\n });\n });\n\n const legendStrings = await getStrings(requests)\n .then(fetchedStrings => {\n return fetchedStrings;\n })\n .catch(Notification.exception);\n\n return legendStrings;\n }\n\n}\n"],"names":["constructor","filterSet","applyCallback","activeFilters","courseid","CourseFilter","init","querySelector","Selectors","filterset","region","addEventListener","e","target","closest","actions","addRow","preventDefault","addFilterRow","applyFilters","updateTableFromFilter","resetFilters","removeAllFilters","regions","filterlist","filter","remove","removeOrReplaceFilterRow","filterRegion","this","getFilterRegion","define","CustomEvents","events","accessibleChange","on","typeField","fields","type","value","addFilter","join","dataset","filterverb","pendingPromise","Pending","rownum","querySelectorAll","length","Templates","renderForPromise","then","_ref","html","js","appendNodeContents","filterRow","typeList","data","forEach","contentNode","contentTypeList","innerHTML","updateFiltersOptions","result","resolve","catch","Notification","exception","getFilterDataSource","filterType","datasource","byName","initialFilterValues","filterDataNode","Filter","GenericFilter","filterTypeClass","disabled","getFilterObject","name","refreshContent","replaceFilterRow","removeFilterRow","required","hasFilterValue","removeFilterObject","filterLegends","getAvailableFilterLegends","index","innerText","rowNum","_ref2","replaceNode","filterName","tearDown","removeEmptyFilters","filters","option","classList","add","addRowButton","all","setAttribute","removeAttribute","filtermatch","Object","values","filterValue","maxFilters","document","typeListSelect","requests","Array","_","rowIndex","push","fetchedStrings"],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"datafilter.min.js","sources":["../src/datafilter.js"],"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 .\n\n/**\n * Data filter management.\n *\n * @module core/datafilter\n * @copyright 2020 Andrew Nicols \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport CourseFilter from 'core/datafilter/filtertypes/courseid';\nimport GenericFilter from 'core/datafilter/filtertype';\nimport {get_strings as getStrings} from 'core/str';\nimport Notification from 'core/notification';\nimport Pending from 'core/pending';\nimport Selectors from 'core/datafilter/selectors';\nimport Templates from 'core/templates';\nimport CustomEvents from 'core/custom_interaction_events';\nimport jQuery from 'jquery';\n\nexport default class {\n\n /**\n * Initialise the filter on the element with the given filterSet and callback.\n *\n * @param {HTMLElement} filterSet The filter element.\n * @param {Function} applyCallback Callback function when updateTableFromFilter\n */\n constructor(filterSet, applyCallback) {\n\n this.filterSet = filterSet;\n this.applyCallback = applyCallback;\n // Keep a reference to all of the active filters.\n this.activeFilters = {\n courseid: new CourseFilter('courseid', filterSet),\n };\n }\n\n /**\n * Initialise event listeners to the filter.\n */\n init() {\n // Add listeners for the main actions.\n this.filterSet.querySelector(Selectors.filterset.region).addEventListener('click', e => {\n if (e.target.closest(Selectors.filterset.actions.addRow)) {\n e.preventDefault();\n\n this.addFilterRow();\n }\n\n if (e.target.closest(Selectors.filterset.actions.applyFilters)) {\n e.preventDefault();\n\n this.updateTableFromFilter();\n }\n\n if (e.target.closest(Selectors.filterset.actions.resetFilters)) {\n e.preventDefault();\n\n this.removeAllFilters();\n }\n });\n\n // Add the listener to remove a single filter.\n this.filterSet.querySelector(Selectors.filterset.regions.filterlist).addEventListener('click', e => {\n if (e.target.closest(Selectors.filter.actions.remove)) {\n e.preventDefault();\n\n this.removeOrReplaceFilterRow(e.target.closest(Selectors.filter.region), true);\n }\n });\n\n // Add listeners for the filter type selection.\n let filterRegion = jQuery(this.getFilterRegion());\n CustomEvents.define(filterRegion, [CustomEvents.events.accessibleChange]);\n filterRegion.on(CustomEvents.events.accessibleChange, e => {\n const typeField = e.target.closest(Selectors.filter.fields.type);\n if (typeField && typeField.value) {\n const filter = e.target.closest(Selectors.filter.region);\n\n this.addFilter(filter, typeField.value);\n }\n });\n\n this.filterSet.querySelector(Selectors.filterset.fields.join).addEventListener('change', e => {\n this.filterSet.dataset.filterverb = e.target.value;\n });\n }\n\n /**\n * Get the filter list region.\n *\n * @return {HTMLElement}\n */\n getFilterRegion() {\n return this.filterSet.querySelector(Selectors.filterset.regions.filterlist);\n }\n\n /**\n * Add a filter row.\n *\n * @param {Object} filterdata Optional, data for adding for row with an existing filter.\n * @return {Promise}\n */\n addFilterRow(filterdata = {}) {\n const pendingPromise = new Pending('core/datafilter:addFilterRow');\n const rownum = filterdata.rownum ?? 1 + this.getFilterRegion().querySelectorAll(Selectors.filter.region).length;\n return Templates.renderForPromise('core/datafilter/filter_row', {\"rownumber\": rownum})\n .then(({html, js}) => {\n const newContentNodes = Templates.appendNodeContents(this.getFilterRegion(), html, js);\n\n return newContentNodes;\n })\n .then(filterRow => {\n // Note: This is a nasty hack.\n // We should try to find a better way of doing this.\n // We do not have the list of types in a readily consumable format, so we take the pre-rendered one and copy\n // it in place.\n const typeList = this.filterSet.querySelector(Selectors.data.typeList);\n\n filterRow.forEach(contentNode => {\n const contentTypeList = contentNode.querySelector(Selectors.filter.fields.type);\n\n if (contentTypeList) {\n contentTypeList.innerHTML = typeList.innerHTML;\n }\n });\n\n return filterRow;\n })\n .then(filterRow => {\n this.updateFiltersOptions();\n\n return filterRow;\n })\n .then(result => {\n pendingPromise.resolve();\n\n // If an existing filter is passed in, add it. Otherwise, leave the row empty.\n if (filterdata.filtertype) {\n result.forEach(filter => {\n this.addFilter(filter, filterdata.filtertype, filterdata.values,\n filterdata.jointype, filterdata.filteroptions);\n });\n }\n return result;\n })\n .catch(Notification.exception);\n }\n\n /**\n * Get the filter data source node fro the specified filter type.\n *\n * @param {String} filterType\n * @return {HTMLElement}\n */\n getFilterDataSource(filterType) {\n const filterDataNode = this.filterSet.querySelector(Selectors.filterset.regions.datasource);\n\n return filterDataNode.querySelector(Selectors.data.fields.byName(filterType));\n }\n\n /**\n * Add a filter to the list of active filters, performing any necessary setup.\n *\n * @param {HTMLElement} filterRow\n * @param {String} filterType\n * @param {Array} initialFilterValues The initially selected values for the filter\n * @param {String} filterJoin\n * @param {Object} filterOptions\n * @returns {Filter}\n */\n async addFilter(filterRow, filterType, initialFilterValues, filterJoin, filterOptions) {\n // Name the filter on the filter row.\n filterRow.dataset.filterType = filterType;\n\n const filterDataNode = this.getFilterDataSource(filterType);\n\n // Instantiate the Filter class.\n let Filter = GenericFilter;\n if (filterDataNode.dataset.filterTypeClass) {\n Filter = await import(filterDataNode.dataset.filterTypeClass);\n }\n this.activeFilters[filterType] = new Filter(filterType, this.filterSet, initialFilterValues, filterOptions);\n\n // Disable the select.\n const typeField = filterRow.querySelector(Selectors.filter.fields.type);\n typeField.value = filterType;\n typeField.disabled = 'disabled';\n // Update the list of available filter types.\n this.updateFiltersOptions();\n\n return this.activeFilters[filterType];\n }\n\n /**\n * Get the registered filter class for the named filter.\n *\n * @param {String} name\n * @return {Object} See the Filter class.\n */\n getFilterObject(name) {\n return this.activeFilters[name];\n }\n\n /**\n * Remove or replace the specified filter row and associated class, ensuring that if there is only one filter row,\n * that it is replaced instead of being removed.\n *\n * @param {HTMLElement} filterRow\n * @param {Bool} refreshContent Whether to refresh the table content when removing\n */\n removeOrReplaceFilterRow(filterRow, refreshContent) {\n const filterCount = this.getFilterRegion().querySelectorAll(Selectors.filter.region).length;\n if (filterCount === 1) {\n this.replaceFilterRow(filterRow, refreshContent);\n } else {\n this.removeFilterRow(filterRow, refreshContent);\n }\n }\n\n /**\n * Remove the specified filter row and associated class.\n *\n * @param {HTMLElement} filterRow\n * @param {Bool} refreshContent Whether to refresh the table content when removing\n */\n async removeFilterRow(filterRow, refreshContent = true) {\n if (filterRow.querySelector(Selectors.data.required)) {\n return;\n }\n const filterType = filterRow.querySelector(Selectors.filter.fields.type);\n const hasFilterValue = !!filterType.value;\n\n // Remove the filter object.\n this.removeFilterObject(filterRow.dataset.filterType);\n\n // Remove the actual filter HTML.\n filterRow.remove();\n\n // Update the list of available filter types.\n this.updateFiltersOptions();\n\n if (hasFilterValue && refreshContent) {\n // Refresh the table if there was any content in this row.\n this.updateTableFromFilter();\n }\n\n // Update filter fieldset legends.\n const filterLegends = await this.getAvailableFilterLegends();\n\n this.getFilterRegion().querySelectorAll(Selectors.filter.region).forEach((filterRow, index) => {\n filterRow.querySelector('legend').innerText = filterLegends[index];\n });\n\n }\n\n /**\n * Replace the specified filter row with a new one.\n *\n * @param {HTMLElement} filterRow\n * @param {Bool} refreshContent Whether to refresh the table content when removing\n * @param {Number} rowNum The number used to label the filter fieldset legend (eg Row 1). Defaults to 1 (the first filter).\n * @return {Promise}\n */\n replaceFilterRow(filterRow, refreshContent = true, rowNum = 1) {\n if (filterRow.querySelector(Selectors.data.required)) {\n return;\n }\n // Remove the filter object.\n this.removeFilterObject(filterRow.dataset.filterType);\n\n return Templates.renderForPromise('core/datafilter/filter_row', {\"rownumber\": rowNum})\n .then(({html, js}) => {\n const newContentNodes = Templates.replaceNode(filterRow, html, js);\n\n return newContentNodes;\n })\n .then(filterRow => {\n // Note: This is a nasty hack.\n // We should try to find a better way of doing this.\n // We do not have the list of types in a readily consumable format, so we take the pre-rendered one and copy\n // it in place.\n const typeList = this.filterSet.querySelector(Selectors.data.typeList);\n\n filterRow.forEach(contentNode => {\n const contentTypeList = contentNode.querySelector(Selectors.filter.fields.type);\n\n if (contentTypeList) {\n contentTypeList.innerHTML = typeList.innerHTML;\n }\n });\n\n return filterRow;\n })\n .then(filterRow => {\n this.updateFiltersOptions();\n\n return filterRow;\n })\n .then(filterRow => {\n // Refresh the table.\n if (refreshContent) {\n return this.updateTableFromFilter();\n } else {\n return filterRow;\n }\n })\n .catch(Notification.exception);\n }\n\n /**\n * Remove the Filter Object from the register.\n *\n * @param {string} filterName The name of the filter to be removed\n */\n removeFilterObject(filterName) {\n if (filterName) {\n const filter = this.getFilterObject(filterName);\n if (filter) {\n filter.tearDown();\n\n // Remove from the list of active filters.\n delete this.activeFilters[filterName];\n }\n }\n }\n\n /**\n * Remove all filters.\n *\n * @returns {Promise}\n */\n removeAllFilters() {\n const filters = this.getFilterRegion().querySelectorAll(Selectors.filter.region);\n filters.forEach(filterRow => this.removeOrReplaceFilterRow(filterRow, false));\n\n // Refresh the table.\n return this.updateTableFromFilter();\n }\n\n /**\n * Remove any empty filters.\n */\n removeEmptyFilters() {\n const filters = this.getFilterRegion().querySelectorAll(Selectors.filter.region);\n filters.forEach(filterRow => {\n const filterType = filterRow.querySelector(Selectors.filter.fields.type);\n if (!filterType.value) {\n this.removeOrReplaceFilterRow(filterRow, false);\n }\n });\n }\n\n /**\n * Update the list of filter types to filter out those already selected.\n */\n updateFiltersOptions() {\n const filters = this.getFilterRegion().querySelectorAll(Selectors.filter.region);\n filters.forEach(filterRow => {\n const options = filterRow.querySelectorAll(Selectors.filter.fields.type + ' option');\n options.forEach(option => {\n if (option.value === filterRow.dataset.filterType) {\n option.classList.remove('hidden');\n option.disabled = false;\n } else if (this.activeFilters[option.value]) {\n option.classList.add('hidden');\n option.disabled = true;\n } else {\n option.classList.remove('hidden');\n option.disabled = false;\n }\n });\n });\n\n // Configure the state of the \"Add row\" button.\n // This button is disabled when there is a filter row available for each condition.\n const addRowButton = this.filterSet.querySelector(Selectors.filterset.actions.addRow);\n const filterDataNode = this.filterSet.querySelectorAll(Selectors.data.fields.all);\n if (filterDataNode.length <= filters.length) {\n addRowButton.setAttribute('disabled', 'disabled');\n } else {\n addRowButton.removeAttribute('disabled');\n }\n\n if (filters.length === 1) {\n this.filterSet.querySelector(Selectors.filterset.regions.filtermatch).classList.add('hidden');\n this.filterSet.querySelector(Selectors.filterset.fields.join).value = 2;\n this.filterSet.dataset.filterverb = 2;\n } else {\n this.filterSet.querySelector(Selectors.filterset.regions.filtermatch).classList.remove('hidden');\n }\n }\n\n /**\n * Update the Dynamic table based upon the current filter.\n */\n updateTableFromFilter() {\n const pendingPromise = new Pending('core/datafilter:updateTableFromFilter');\n\n const filters = {};\n Object.values(this.activeFilters).forEach(filter => {\n filters[filter.filterValue.name] = filter.filterValue;\n });\n\n if (this.applyCallback) {\n this.applyCallback(filters, pendingPromise);\n }\n }\n\n /**\n * Fetch the strings used to populate the fieldset legends for the maximum number of filters possible.\n *\n * @return {array}\n */\n async getAvailableFilterLegends() {\n const maxFilters = document.querySelector(Selectors.data.typeListSelect).length - 1;\n let requests = [];\n\n [...Array(maxFilters)].forEach((_, rowIndex) => {\n requests.push({\n \"key\": \"filterrowlegend\",\n \"component\": \"core\",\n // Add 1 since rows begin at 1 (index begins at zero).\n \"param\": rowIndex + 1\n });\n });\n\n const legendStrings = await getStrings(requests)\n .then(fetchedStrings => {\n return fetchedStrings;\n })\n .catch(Notification.exception);\n\n return legendStrings;\n }\n\n}\n"],"names":["constructor","filterSet","applyCallback","activeFilters","courseid","CourseFilter","init","querySelector","Selectors","filterset","region","addEventListener","e","target","closest","actions","addRow","preventDefault","addFilterRow","applyFilters","updateTableFromFilter","resetFilters","removeAllFilters","regions","filterlist","filter","remove","removeOrReplaceFilterRow","filterRegion","this","getFilterRegion","define","CustomEvents","events","accessibleChange","on","typeField","fields","type","value","addFilter","join","dataset","filterverb","filterdata","pendingPromise","Pending","rownum","querySelectorAll","length","Templates","renderForPromise","then","_ref","html","js","appendNodeContents","filterRow","typeList","data","forEach","contentNode","contentTypeList","innerHTML","updateFiltersOptions","result","resolve","filtertype","values","jointype","filteroptions","catch","Notification","exception","getFilterDataSource","filterType","datasource","byName","initialFilterValues","filterJoin","filterOptions","filterDataNode","Filter","GenericFilter","filterTypeClass","disabled","getFilterObject","name","refreshContent","replaceFilterRow","removeFilterRow","required","hasFilterValue","removeFilterObject","filterLegends","getAvailableFilterLegends","index","innerText","rowNum","_ref2","replaceNode","filterName","tearDown","removeEmptyFilters","filters","option","classList","add","addRowButton","all","setAttribute","removeAttribute","filtermatch","Object","filterValue","maxFilters","document","typeListSelect","requests","Array","_","rowIndex","push","fetchedStrings"],"mappings":"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"}
\ No newline at end of file
diff --git a/lib/amd/build/datafilter/filtertypes/binary.min.js b/lib/amd/build/datafilter/filtertypes/binary.min.js
new file mode 100644
index 00000000000..98fc1526147
--- /dev/null
+++ b/lib/amd/build/datafilter/filtertypes/binary.min.js
@@ -0,0 +1,3 @@
+define("core/datafilter/filtertypes/binary",["exports","core/datafilter/filtertype","core/datafilter/selectors","core/templates","core/str"],(function(_exports,_filtertype,_selectors,_templates,_str){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_filtertype=_interopRequireDefault(_filtertype),_selectors=_interopRequireDefault(_selectors),_templates=_interopRequireDefault(_templates);class _default extends _filtertype.default{constructor(){super(...arguments),_defineProperty(this,"optionOne",void 0),_defineProperty(this,"optionTwo",void 0)}async addValueSelector(initialValues){return[this.optionOne,this.optionTwo]=await this.getTextValues(),this.displayBinarySelection(initialValues)}getTextValues(){return(0,_str.get_strings)([{key:"no"},{key:"yes"}])}async displayBinarySelection(){let initialValues=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];const specificFilterSet=this.rootNode.querySelector(_selectors.default.filter.byName(this.filterType)),sourceDataNode=this.getSourceDataForFilter(),context={filtertype:this.filterType,title:sourceDataNode.getAttribute("data-field-title"),required:sourceDataNode.dataset.required,options:[{text:this.optionOne,value:0,selected:0===initialValues[0]},{text:this.optionTwo,value:1,selected:1===initialValues[0]}]};return _templates.default.render("core/datafilter/filtertypes/binary_selector",context).then(((binaryUi,js)=>_templates.default.replaceNodeContents(specificFilterSet.querySelector(_selectors.default.filter.regions.values),binaryUi,js)))}get values(){return this.filterRoot.querySelector('[data-filterfield="'.concat(this.name,'"]')).value}}return _exports.default=_default,_exports.default}));
+
+//# sourceMappingURL=binary.min.js.map
\ No newline at end of file
diff --git a/lib/amd/build/datafilter/filtertypes/binary.min.js.map b/lib/amd/build/datafilter/filtertypes/binary.min.js.map
new file mode 100644
index 00000000000..b41d8295796
--- /dev/null
+++ b/lib/amd/build/datafilter/filtertypes/binary.min.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"binary.min.js","sources":["../../../src/datafilter/filtertypes/binary.js"],"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 .\n\n/**\n * Base filter for binary selector ie: (Yes / No).\n *\n * @module core/datafilter/filtertypes/binary\n * @author 2022 Ghaly Marc-Alexandre \n * @copyright 2022 Catalyst IT Australia Pty Ltd\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Filter from 'core/datafilter/filtertype';\nimport Selectors from 'core/datafilter/selectors';\nimport Templates from 'core/templates';\nimport {get_strings as getStrings} from 'core/str';\n\nexport default class extends Filter {\n\n /**\n * Text string for the first binary option.\n *\n * This option (and {@see optionTwo}) are set by {@see getTextValues()}. The base class will set default values,\n * a subclass can override the method to define its own option.\n *\n * @type {String}\n */\n optionOne;\n\n /**\n * Text string for the second binary option.\n *\n * @type {String}\n */\n optionTwo;\n\n /**\n * Add the value selector to the filter row.\n *\n * @param {Array} initialValues The default value for the filter.\n */\n async addValueSelector(initialValues) {\n [this.optionOne, this.optionTwo] = await this.getTextValues();\n return this.displayBinarySelection(initialValues);\n }\n\n /**\n * Fetch text values for select options.\n *\n * Subclasses should override this method to set their own options.\n *\n * @returns {Promise}\n */\n getTextValues() {\n return getStrings([{key: 'no'}, {key: 'yes'}]);\n }\n\n /**\n * Renders yes/no select input with proper selection.\n *\n * @param {Array} initialValues The default value for the filter.\n */\n async displayBinarySelection(initialValues = []) {\n // We specify a specific filterset in case there are multiple filtering condition - avoiding glitches.\n const specificFilterSet = this.rootNode.querySelector(Selectors.filter.byName(this.filterType));\n const sourceDataNode = this.getSourceDataForFilter();\n const context = {\n filtertype: this.filterType,\n title: sourceDataNode.getAttribute('data-field-title'),\n required: sourceDataNode.dataset.required,\n options: [\n {\n text: this.optionOne,\n value: 0,\n selected: initialValues[0] === 0,\n },\n {\n text: this.optionTwo,\n value: 1,\n selected: initialValues[0] === 1,\n },\n ]\n };\n return Templates.render('core/datafilter/filtertypes/binary_selector', context)\n .then((binaryUi, js) => {\n return Templates.replaceNodeContents(specificFilterSet.querySelector(Selectors.filter.regions.values), binaryUi, js);\n });\n }\n\n /**\n * Get the list of raw values for this filter type.\n *\n * @returns {Array}\n */\n get values() {\n return this.filterRoot.querySelector(`[data-filterfield=\"${this.name}\"]`).value;\n }\n\n}\n"],"names":["Filter","initialValues","this","optionOne","optionTwo","getTextValues","displayBinarySelection","key","specificFilterSet","rootNode","querySelector","Selectors","filter","byName","filterType","sourceDataNode","getSourceDataForFilter","context","filtertype","title","getAttribute","required","dataset","options","text","value","selected","Templates","render","then","binaryUi","js","replaceNodeContents","regions","values","filterRoot","name"],"mappings":"irBA6B6BA,+JAwBFC,sBAClBC,KAAKC,UAAWD,KAAKE,iBAAmBF,KAAKG,gBACvCH,KAAKI,uBAAuBL,eAUvCI,uBACW,oBAAW,CAAC,CAACE,IAAK,MAAO,CAACA,IAAK,4CAQbN,qEAAgB,SAEnCO,kBAAoBN,KAAKO,SAASC,cAAcC,mBAAUC,OAAOC,OAAOX,KAAKY,aAC7EC,eAAiBb,KAAKc,yBACtBC,QAAU,CACZC,WAAYhB,KAAKY,WACjBK,MAAOJ,eAAeK,aAAa,oBACnCC,SAAUN,eAAeO,QAAQD,SACjCE,QAAS,CACL,CACIC,KAAMtB,KAAKC,UACXsB,MAAO,EACPC,SAA+B,IAArBzB,cAAc,IAE5B,CACIuB,KAAMtB,KAAKE,UACXqB,MAAO,EACPC,SAA+B,IAArBzB,cAAc,aAI7B0B,mBAAUC,OAAO,8CAA+CX,SACtEY,MAAK,CAACC,SAAUC,KACNJ,mBAAUK,oBAAoBxB,kBAAkBE,cAAcC,mBAAUC,OAAOqB,QAAQC,QAASJ,SAAUC,MASrHG,oBACOhC,KAAKiC,WAAWzB,2CAAoCR,KAAKkC,YAAUX"}
\ No newline at end of file
diff --git a/lib/amd/build/datafilter/selectors.min.js b/lib/amd/build/datafilter/selectors.min.js
index 8dab23d2b98..7e318337318 100644
--- a/lib/amd/build/datafilter/selectors.min.js
+++ b/lib/amd/build/datafilter/selectors.min.js
@@ -6,6 +6,6 @@ define("core/datafilter/selectors",["exports"],(function(_exports){Object.define
* @copyright 2020 Michael Hawkins
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-const getFilterRegion=region=>'[data-filterregion="'.concat(region,'"]'),getFilterAction=action=>'[data-filteraction="'.concat(action,'"]'),getFilterField=field=>'[data-filterfield="'.concat(field,'"]');var _default={filter:{region:getFilterRegion("filter"),actions:{remove:getFilterAction("remove")},fields:{join:getFilterField("join"),type:getFilterField("type")},regions:{values:getFilterRegion("value")},byName:name=>"".concat(getFilterRegion("filter"),'[data-filter-type="').concat(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:"".concat(getFilterRegion("filtermatch")," ").concat(getFilterField("join"))}},data:{fields:{byName:name=>'[data-field-name="'.concat(name,'"]'),all:"".concat(getFilterRegion("filtertypedata")," [data-field-name]")},typeList:getFilterRegion("filtertypelist"),typeListSelect:"select".concat(getFilterRegion("filtertypelist")),required:"".concat(getFilterRegion("value"),' > [data-required="1"]')}};return _exports.default=_default,_exports.default}));
+const getFilterRegion=region=>'[data-filterregion="'.concat(region,'"]'),getFilterAction=action=>'[data-filteraction="'.concat(action,'"]'),getFilterField=field=>'[data-filterfield="'.concat(field,'"]');var _default={filter:{region:getFilterRegion("filter"),actions:{remove:getFilterAction("remove")},fields:{join:getFilterField("join"),type:getFilterField("type")},regions:{values:getFilterRegion("value")},byName:name=>"".concat(getFilterRegion("filter"),'[data-filter-type="').concat(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"),emptyFilterRow:"".concat(getFilterRegion("filter"),'[data-filter-type=""]')},fields:{join:"".concat(getFilterRegion("filtermatch")," ").concat(getFilterField("join"))}},data:{fields:{byName:name=>'[data-field-name="'.concat(name,'"]'),all:"".concat(getFilterRegion("filtertypedata")," [data-field-name]")},typeList:getFilterRegion("filtertypelist"),typeListSelect:"select".concat(getFilterRegion("filtertypelist")),required:"".concat(getFilterRegion("value"),' > [data-required="1"]')}};return _exports.default=_default,_exports.default}));
//# sourceMappingURL=selectors.min.js.map
\ No newline at end of file
diff --git a/lib/amd/build/datafilter/selectors.min.js.map b/lib/amd/build/datafilter/selectors.min.js.map
index 238a9e30421..6a51d33dfd8 100644
--- a/lib/amd/build/datafilter/selectors.min.js.map
+++ b/lib/amd/build/datafilter/selectors.min.js.map
@@ -1 +1 @@
-{"version":3,"file":"selectors.min.js","sources":["../../src/datafilter/selectors.js"],"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 .\n\n/**\n * Module containing the selectors for user filters.\n *\n * @module core/datafilter/selectors\n * @copyright 2020 Michael Hawkins \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 typeListSelect: `select${getFilterRegion('filtertypelist')}`,\n required: `${getFilterRegion('value')} > [data-required=\"1\"]`,\n },\n};\n"],"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","typeListSelect","required"],"mappings":";;;;;;;;MAuBMA,gBAAkBC,sCAAiCA,aACnDC,gBAAkBC,sCAAiCA,aACnDC,eAAiBC,oCAA+BA,yBAEvC,CACXC,OAAQ,CACJL,OAAQD,gBAAgB,UACxBO,QAAS,CACLC,OAAQN,gBAAgB,WAE5BO,OAAQ,CACJC,KAAMN,eAAe,QACrBO,KAAMP,eAAe,SAEzBQ,QAAS,CACLC,OAAQb,gBAAgB,UAE5Bc,OAAQC,gBAAWf,gBAAgB,wCAA+Be,YAEtEC,UAAW,CACPf,OAAQD,gBAAgB,WACxBO,QAAS,CACLU,OAAQf,gBAAgB,OACxBgB,aAAchB,gBAAgB,SAC9BiB,aAAcjB,gBAAgB,UAElCU,QAAS,CACLQ,YAAapB,gBAAgB,eAC7BqB,WAAYrB,gBAAgB,WAC5BsB,WAAYtB,gBAAgB,mBAEhCS,OAAQ,CACJC,eAASV,gBAAgB,2BAAkBI,eAAe,WAGlEmB,KAAM,CACFd,OAAQ,CACJK,OAAQC,kCAA6BA,WACrCS,cAAQxB,gBAAgB,yCAE5ByB,SAAUzB,gBAAgB,kBAC1B0B,+BAAyB1B,gBAAgB,mBACzC2B,mBAAa3B,gBAAgB"}
\ No newline at end of file
+{"version":3,"file":"selectors.min.js","sources":["../../src/datafilter/selectors.js"],"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 .\n\n/**\n * Module containing the selectors for user filters.\n *\n * @module core/datafilter/selectors\n * @copyright 2020 Michael Hawkins \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 emptyFilterRow: `${getFilterRegion('filter')}[data-filter-type=\"\"]`,\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 typeListSelect: `select${getFilterRegion('filtertypelist')}`,\n required: `${getFilterRegion('value')} > [data-required=\"1\"]`,\n },\n};\n"],"names":["getFilterRegion","region","getFilterAction","action","getFilterField","field","filter","actions","remove","fields","join","type","regions","values","byName","name","filterset","addRow","applyFilters","resetFilters","filtermatch","filterlist","datasource","emptyFilterRow","data","all","typeList","typeListSelect","required"],"mappings":";;;;;;;;MAuBMA,gBAAkBC,sCAAiCA,aACnDC,gBAAkBC,sCAAiCA,aACnDC,eAAiBC,oCAA+BA,yBAEvC,CACXC,OAAQ,CACJL,OAAQD,gBAAgB,UACxBO,QAAS,CACLC,OAAQN,gBAAgB,WAE5BO,OAAQ,CACJC,KAAMN,eAAe,QACrBO,KAAMP,eAAe,SAEzBQ,QAAS,CACLC,OAAQb,gBAAgB,UAE5Bc,OAAQC,gBAAWf,gBAAgB,wCAA+Be,YAEtEC,UAAW,CACPf,OAAQD,gBAAgB,WACxBO,QAAS,CACLU,OAAQf,gBAAgB,OACxBgB,aAAchB,gBAAgB,SAC9BiB,aAAcjB,gBAAgB,UAElCU,QAAS,CACLQ,YAAapB,gBAAgB,eAC7BqB,WAAYrB,gBAAgB,WAC5BsB,WAAYtB,gBAAgB,kBAC5BuB,yBAAmBvB,gBAAgB,oCAEvCS,OAAQ,CACJC,eAASV,gBAAgB,2BAAkBI,eAAe,WAGlEoB,KAAM,CACFf,OAAQ,CACJK,OAAQC,kCAA6BA,WACrCU,cAAQzB,gBAAgB,yCAE5B0B,SAAU1B,gBAAgB,kBAC1B2B,+BAAyB3B,gBAAgB,mBACzC4B,mBAAa5B,gBAAgB"}
\ No newline at end of file
diff --git a/lib/amd/src/datafilter.js b/lib/amd/src/datafilter.js
index c069f656678..a36e209eff9 100644
--- a/lib/amd/src/datafilter.js
+++ b/lib/amd/src/datafilter.js
@@ -110,13 +110,14 @@ export default class {
}
/**
- * Add an unselected filter row.
+ * Add a filter row.
*
+ * @param {Object} filterdata Optional, data for adding for row with an existing filter.
* @return {Promise}
*/
- addFilterRow() {
+ addFilterRow(filterdata = {}) {
const pendingPromise = new Pending('core/datafilter:addFilterRow');
- const rownum = 1 + this.getFilterRegion().querySelectorAll(Selectors.filter.region).length;
+ const rownum = filterdata.rownum ?? 1 + this.getFilterRegion().querySelectorAll(Selectors.filter.region).length;
return Templates.renderForPromise('core/datafilter/filter_row', {"rownumber": rownum})
.then(({html, js}) => {
const newContentNodes = Templates.appendNodeContents(this.getFilterRegion(), html, js);
@@ -148,6 +149,13 @@ export default class {
.then(result => {
pendingPromise.resolve();
+ // If an existing filter is passed in, add it. Otherwise, leave the row empty.
+ if (filterdata.filtertype) {
+ result.forEach(filter => {
+ this.addFilter(filter, filterdata.filtertype, filterdata.values,
+ filterdata.jointype, filterdata.filteroptions);
+ });
+ }
return result;
})
.catch(Notification.exception);
@@ -171,9 +179,11 @@ export default class {
* @param {HTMLElement} filterRow
* @param {String} filterType
* @param {Array} initialFilterValues The initially selected values for the filter
+ * @param {String} filterJoin
+ * @param {Object} filterOptions
* @returns {Filter}
*/
- async addFilter(filterRow, filterType, initialFilterValues) {
+ async addFilter(filterRow, filterType, initialFilterValues, filterJoin, filterOptions) {
// Name the filter on the filter row.
filterRow.dataset.filterType = filterType;
@@ -184,13 +194,12 @@ export default class {
if (filterDataNode.dataset.filterTypeClass) {
Filter = await import(filterDataNode.dataset.filterTypeClass);
}
- this.activeFilters[filterType] = new Filter(filterType, this.filterSet, initialFilterValues);
+ this.activeFilters[filterType] = new Filter(filterType, this.filterSet, initialFilterValues, filterOptions);
// Disable the select.
const typeField = filterRow.querySelector(Selectors.filter.fields.type);
typeField.value = filterType;
typeField.disabled = 'disabled';
-
// Update the list of available filter types.
this.updateFiltersOptions();
diff --git a/lib/amd/src/datafilter/filtertypes/binary.js b/lib/amd/src/datafilter/filtertypes/binary.js
new file mode 100644
index 00000000000..6f577d13806
--- /dev/null
+++ b/lib/amd/src/datafilter/filtertypes/binary.js
@@ -0,0 +1,111 @@
+// 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 .
+
+/**
+ * Base filter for binary selector ie: (Yes / No).
+ *
+ * @module core/datafilter/filtertypes/binary
+ * @author 2022 Ghaly Marc-Alexandre
+ * @copyright 2022 Catalyst IT Australia Pty Ltd
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+import Filter from 'core/datafilter/filtertype';
+import Selectors from 'core/datafilter/selectors';
+import Templates from 'core/templates';
+import {get_strings as getStrings} from 'core/str';
+
+export default class extends Filter {
+
+ /**
+ * Text string for the first binary option.
+ *
+ * This option (and {@see optionTwo}) are set by {@see getTextValues()}. The base class will set default values,
+ * a subclass can override the method to define its own option.
+ *
+ * @type {String}
+ */
+ optionOne;
+
+ /**
+ * Text string for the second binary option.
+ *
+ * @type {String}
+ */
+ optionTwo;
+
+ /**
+ * Add the value selector to the filter row.
+ *
+ * @param {Array} initialValues The default value for the filter.
+ */
+ async addValueSelector(initialValues) {
+ [this.optionOne, this.optionTwo] = await this.getTextValues();
+ return this.displayBinarySelection(initialValues);
+ }
+
+ /**
+ * Fetch text values for select options.
+ *
+ * Subclasses should override this method to set their own options.
+ *
+ * @returns {Promise}
+ */
+ getTextValues() {
+ return getStrings([{key: 'no'}, {key: 'yes'}]);
+ }
+
+ /**
+ * Renders yes/no select input with proper selection.
+ *
+ * @param {Array} initialValues The default value for the filter.
+ */
+ async displayBinarySelection(initialValues = []) {
+ // We specify a specific filterset in case there are multiple filtering condition - avoiding glitches.
+ const specificFilterSet = this.rootNode.querySelector(Selectors.filter.byName(this.filterType));
+ const sourceDataNode = this.getSourceDataForFilter();
+ const context = {
+ filtertype: this.filterType,
+ title: sourceDataNode.getAttribute('data-field-title'),
+ required: sourceDataNode.dataset.required,
+ options: [
+ {
+ text: this.optionOne,
+ value: 0,
+ selected: initialValues[0] === 0,
+ },
+ {
+ text: this.optionTwo,
+ value: 1,
+ selected: initialValues[0] === 1,
+ },
+ ]
+ };
+ return Templates.render('core/datafilter/filtertypes/binary_selector', context)
+ .then((binaryUi, js) => {
+ return Templates.replaceNodeContents(specificFilterSet.querySelector(Selectors.filter.regions.values), binaryUi, js);
+ });
+ }
+
+ /**
+ * Get the list of raw values for this filter type.
+ *
+ * @returns {Array}
+ */
+ get values() {
+ return this.filterRoot.querySelector(`[data-filterfield="${this.name}"]`).value;
+ }
+
+}
diff --git a/lib/amd/src/datafilter/selectors.js b/lib/amd/src/datafilter/selectors.js
index 0272a8b74f7..a78dcfa9ef4 100644
--- a/lib/amd/src/datafilter/selectors.js
+++ b/lib/amd/src/datafilter/selectors.js
@@ -51,6 +51,7 @@ export default {
filtermatch: getFilterRegion('filtermatch'),
filterlist: getFilterRegion('filters'),
datasource: getFilterRegion('filtertypedata'),
+ emptyFilterRow: `${getFilterRegion('filter')}[data-filter-type=""]`,
},
fields: {
join: `${getFilterRegion('filtermatch')} ${getFilterField('join')}`,
diff --git a/lib/templates/datafilter/filter_row.mustache b/lib/templates/datafilter/filter_row.mustache
index f7ea7e03f08..52d4656957c 100644
--- a/lib/templates/datafilter/filter_row.mustache
+++ b/lib/templates/datafilter/filter_row.mustache
@@ -33,11 +33,11 @@
"rownumber": 1
}
}}
-