From 8a126b75e558a3db4e851fe4622647ddfdcc77f5 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Sun, 19 Dec 2021 08:30:17 +0800 Subject: [PATCH] MDL-73255 user: Ensure that invalid filters are not left When there is no group but a default groupmode is set, the group configuration is provided but the group data is not present. This resulted in a JS error and an invalid filter line was left in place. This change conditionally checks the data-filter-type-class value of the filterDataNode so that no JS error is produced if the filterDataNode does not exist and therefore removes the empty filter row. --- user/amd/build/local/participantsfilter/filter.min.js | 2 +- user/amd/build/local/participantsfilter/filter.min.js.map | 2 +- user/amd/build/participantsfilter.min.js | 2 +- user/amd/build/participantsfilter.min.js.map | 2 +- user/amd/src/local/participantsfilter/filter.js | 6 +++++- user/amd/src/participantsfilter.js | 2 +- 6 files changed, 10 insertions(+), 6 deletions(-) diff --git a/user/amd/build/local/participantsfilter/filter.min.js b/user/amd/build/local/participantsfilter/filter.min.js index 039e3385404..281e24c3c66 100644 --- a/user/amd/build/local/participantsfilter/filter.min.js +++ b/user/amd/build/local/participantsfilter/filter.min.js @@ -1,2 +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.\n\n/**\n * Base Filter class for a filter type in the participants filter UI.\n *\n * @module core_user/local/participantsfilter/filter\n * @copyright 2020 Andrew Nicols \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport Autocomplete from 'core/form-autocomplete';\nimport Selectors from './selectors';\nimport {get_string as getString} from 'core/str';\n\n/**\n * Fetch all checked options in the select.\n *\n * This is a poor-man's polyfill for select.selectedOptions, which is not available in IE11.\n *\n * @param {HTMLSelectElement} select\n * @returns {HTMLOptionElement[]} All selected options\n */\nconst getOptionsForSelect = select => {\n return select.querySelectorAll(':checked');\n};\n\nexport default class {\n\n /**\n * Constructor for a new filter.\n *\n * @param {String} filterType The type of filter that this relates to\n * @param {HTMLElement} rootNode The root node for the participants filterset\n * @param {Array} initialValues The initial values for the selector\n */\n constructor(filterType, rootNode, initialValues) {\n this.filterType = filterType;\n this.rootNode = rootNode;\n\n this.addValueSelector(initialValues);\n }\n\n /**\n * Perform any tear-down for this filter type.\n */\n tearDown() {\n // eslint-disable-line no-empty-function\n }\n\n /**\n * Get the placeholder to use when showing the value selector.\n *\n * @return {Promise} Resolving to a String\n */\n get placeholder() {\n return getString('placeholdertypeorselect', 'core_user');\n }\n\n /**\n * Whether to show suggestions in the autocomplete.\n *\n * @return {Boolean}\n */\n get showSuggestions() {\n return true;\n }\n\n /**\n * Add the value selector to the filter row.\n *\n * @param {Array} initialValues\n */\n async addValueSelector(initialValues = []) {\n const filterValueNode = this.getFilterValueNode();\n\n // Copy the data in place.\n filterValueNode.innerHTML = this.getSourceDataForFilter().outerHTML;\n\n const dataSource = filterValueNode.querySelector('select');\n\n // Set an ID for this filter value element.\n dataSource.id = 'filter-value-' + dataSource.getAttribute('data-field-name');\n\n // Create a hidden label for the filter value.\n const filterValueLabel = document.createElement('label');\n filterValueLabel.setAttribute('for', dataSource.id);\n filterValueLabel.classList.add('sr-only');\n filterValueLabel.innerText = dataSource.getAttribute('data-field-title');\n\n // Append this label to the filter value container.\n filterValueNode.appendChild(filterValueLabel);\n\n // If there are any initial values then attempt to apply them.\n initialValues.forEach(filterValue => {\n let selectedOption = dataSource.querySelector(`option[value=\"${filterValue}\"]`);\n if (selectedOption) {\n selectedOption.selected = true;\n } else if (!this.showSuggestions) {\n selectedOption = document.createElement('option');\n selectedOption.value = filterValue;\n selectedOption.innerHTML = filterValue;\n selectedOption.selected = true;\n\n dataSource.append(selectedOption);\n }\n });\n\n Autocomplete.enhance(\n // The source select element.\n dataSource,\n\n // Whether to allow 'tags' (custom entries).\n dataSource.dataset.allowCustom == \"1\",\n\n // We do not require AJAX at all as standard.\n null,\n\n // The string to use as a placeholder.\n await this.placeholder,\n\n // Disable case sensitivity on searches.\n false,\n\n // Show suggestions.\n this.showSuggestions,\n\n // Do not override the 'no suggestions' string.\n null,\n\n // Close the suggestions if this is not a multi-select.\n !dataSource.multiple,\n\n // Template overrides.\n {\n items: 'core_user/local/participantsfilter/autocomplete_selection_items',\n layout: 'core_user/local/participantsfilter/autocomplete_layout',\n selection: 'core_user/local/participantsfilter/autocomplete_selection',\n }\n );\n }\n\n /**\n * Get the root node for this filter.\n *\n * @returns {HTMLElement}\n */\n get filterRoot() {\n return this.rootNode.querySelector(Selectors.filter.byName(this.filterType));\n }\n\n /**\n * Get the possible data for this filter type.\n *\n * @returns {Array}\n */\n getSourceDataForFilter() {\n const filterDataNode = this.rootNode.querySelector(Selectors.filterset.regions.datasource);\n\n return filterDataNode.querySelector(Selectors.data.fields.byName(this.filterType));\n }\n\n /**\n * Get the HTMLElement which contains the value selector.\n *\n * @returns {HTMLElement}\n */\n getFilterValueNode() {\n return this.filterRoot.querySelector(Selectors.filter.regions.values);\n }\n\n /**\n * Get the name of this filter.\n *\n * @returns {String}\n */\n get name() {\n return this.filterType;\n }\n\n /**\n * Get the type of join specified.\n *\n * @returns {Number}\n */\n get jointype() {\n return parseInt(this.filterRoot.querySelector(Selectors.filter.fields.join).value, 10);\n }\n\n /**\n * Get the list of raw values for this filter type.\n *\n * @returns {Array}\n */\n get rawValues() {\n const filterValueNode = this.getFilterValueNode();\n const filterValueSelect = filterValueNode.querySelector('select');\n\n return Object.values(getOptionsForSelect(filterValueSelect)).map(option => option.value);\n }\n\n /**\n * Get the list of values for this filter type.\n *\n * @returns {Array}\n */\n get values() {\n return this.rawValues.map(option => parseInt(option, 10));\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: this.jointype,\n values: this.values,\n };\n }\n}\n"],"file":"filter.min.js"} \ No newline at end of file +{"version":3,"sources":["../../../src/local/participantsfilter/filter.js"],"names":["getOptionsForSelect","select","querySelectorAll","filterType","rootNode","initialValues","addValueSelector","filterValueNode","getFilterValueNode","sourceDataNode","getSourceDataForFilter","innerHTML","outerHTML","dataSource","querySelector","id","getAttribute","filterValueLabel","document","createElement","setAttribute","classList","add","innerText","appendChild","forEach","filterValue","selectedOption","selected","showSuggestions","value","append","Autocomplete","dataset","allowCustom","placeholder","multiple","items","layout","selection","enhance","filterDataNode","Selectors","filterset","regions","datasource","data","fields","byName","filterRoot","filter","values","parseInt","join","filterValueSelect","Object","map","option","rawValues","name","jointype"],"mappings":"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","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 class for a filter type in the participants filter UI.\n *\n * @module core_user/local/participantsfilter/filter\n * @copyright 2020 Andrew Nicols \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport Autocomplete from 'core/form-autocomplete';\nimport Selectors from './selectors';\nimport {get_string as getString} from 'core/str';\n\n/**\n * Fetch all checked options in the select.\n *\n * This is a poor-man's polyfill for select.selectedOptions, which is not available in IE11.\n *\n * @param {HTMLSelectElement} select\n * @returns {HTMLOptionElement[]} All selected options\n */\nconst getOptionsForSelect = select => {\n return select.querySelectorAll(':checked');\n};\n\nexport default class {\n\n /**\n * Constructor for a new filter.\n *\n * @param {String} filterType The type of filter that this relates to\n * @param {HTMLElement} rootNode The root node for the participants filterset\n * @param {Array} initialValues The initial values for the selector\n */\n constructor(filterType, rootNode, initialValues) {\n this.filterType = filterType;\n this.rootNode = rootNode;\n\n this.addValueSelector(initialValues);\n }\n\n /**\n * Perform any tear-down for this filter type.\n */\n tearDown() {\n // eslint-disable-line no-empty-function\n }\n\n /**\n * Get the placeholder to use when showing the value selector.\n *\n * @return {Promise} Resolving to a String\n */\n get placeholder() {\n return getString('placeholdertypeorselect', 'core_user');\n }\n\n /**\n * Whether to show suggestions in the autocomplete.\n *\n * @return {Boolean}\n */\n get showSuggestions() {\n return true;\n }\n\n /**\n * Add the value selector to the filter row.\n *\n * @param {Array} initialValues\n */\n async addValueSelector(initialValues = []) {\n const filterValueNode = this.getFilterValueNode();\n\n // Copy the data in place.\n const sourceDataNode = this.getSourceDataForFilter();\n if (!sourceDataNode) {\n return;\n }\n filterValueNode.innerHTML = sourceDataNode.outerHTML;\n\n const dataSource = filterValueNode.querySelector('select');\n\n // Set an ID for this filter value element.\n dataSource.id = 'filter-value-' + dataSource.getAttribute('data-field-name');\n\n // Create a hidden label for the filter value.\n const filterValueLabel = document.createElement('label');\n filterValueLabel.setAttribute('for', dataSource.id);\n filterValueLabel.classList.add('sr-only');\n filterValueLabel.innerText = dataSource.getAttribute('data-field-title');\n\n // Append this label to the filter value container.\n filterValueNode.appendChild(filterValueLabel);\n\n // If there are any initial values then attempt to apply them.\n initialValues.forEach(filterValue => {\n let selectedOption = dataSource.querySelector(`option[value=\"${filterValue}\"]`);\n if (selectedOption) {\n selectedOption.selected = true;\n } else if (!this.showSuggestions) {\n selectedOption = document.createElement('option');\n selectedOption.value = filterValue;\n selectedOption.innerHTML = filterValue;\n selectedOption.selected = true;\n\n dataSource.append(selectedOption);\n }\n });\n\n Autocomplete.enhance(\n // The source select element.\n dataSource,\n\n // Whether to allow 'tags' (custom entries).\n dataSource.dataset.allowCustom == \"1\",\n\n // We do not require AJAX at all as standard.\n null,\n\n // The string to use as a placeholder.\n await this.placeholder,\n\n // Disable case sensitivity on searches.\n false,\n\n // Show suggestions.\n this.showSuggestions,\n\n // Do not override the 'no suggestions' string.\n null,\n\n // Close the suggestions if this is not a multi-select.\n !dataSource.multiple,\n\n // Template overrides.\n {\n items: 'core_user/local/participantsfilter/autocomplete_selection_items',\n layout: 'core_user/local/participantsfilter/autocomplete_layout',\n selection: 'core_user/local/participantsfilter/autocomplete_selection',\n }\n );\n }\n\n /**\n * Get the root node for this filter.\n *\n * @returns {HTMLElement}\n */\n get filterRoot() {\n return this.rootNode.querySelector(Selectors.filter.byName(this.filterType));\n }\n\n /**\n * Get the possible data for this filter type.\n *\n * @returns {Array}\n */\n getSourceDataForFilter() {\n const filterDataNode = this.rootNode.querySelector(Selectors.filterset.regions.datasource);\n\n return filterDataNode.querySelector(Selectors.data.fields.byName(this.filterType));\n }\n\n /**\n * Get the HTMLElement which contains the value selector.\n *\n * @returns {HTMLElement}\n */\n getFilterValueNode() {\n return this.filterRoot.querySelector(Selectors.filter.regions.values);\n }\n\n /**\n * Get the name of this filter.\n *\n * @returns {String}\n */\n get name() {\n return this.filterType;\n }\n\n /**\n * Get the type of join specified.\n *\n * @returns {Number}\n */\n get jointype() {\n return parseInt(this.filterRoot.querySelector(Selectors.filter.fields.join).value, 10);\n }\n\n /**\n * Get the list of raw values for this filter type.\n *\n * @returns {Array}\n */\n get rawValues() {\n const filterValueNode = this.getFilterValueNode();\n const filterValueSelect = filterValueNode.querySelector('select');\n\n return Object.values(getOptionsForSelect(filterValueSelect)).map(option => option.value);\n }\n\n /**\n * Get the list of values for this filter type.\n *\n * @returns {Array}\n */\n get values() {\n return this.rawValues.map(option => parseInt(option, 10));\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: this.jointype,\n values: this.values,\n };\n }\n}\n"],"file":"filter.min.js"} \ No newline at end of file diff --git a/user/amd/build/participantsfilter.min.js b/user/amd/build/participantsfilter.min.js index d99bc1b6cb0..ba198a89885 100644 --- a/user/amd/build/participantsfilter.min.js +++ b/user/amd/build/participantsfilter.min.js @@ -1,2 +1,2 @@ -function _typeof(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){_typeof=function(a){return typeof a}}else{_typeof=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return _typeof(a)}define ("core_user/participantsfilter",["exports","./local/participantsfilter/filtertypes/courseid","core_table/dynamic","./local/participantsfilter/filter","core/str","core/notification","core/pending","./local/participantsfilter/selectors","core/templates","core/custom_interaction_events","jquery"],function(a,b,c,d,e,f,g,h,i,j,k){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=n(b);c=m(c);d=n(d);f=n(f);g=n(g);h=n(h);i=n(i);j=n(j);k=n(k);var A="undefined"!=typeof window?window:"undefined"!=typeof self?self:"undefined"!=typeof global?global:{};function l(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;l=function(){return a};return a}function m(a){if(a&&a.__esModule){return a}if(null===a||"object"!==_typeof(a)&&"function"!=typeof a){return{default:a}}var b=l();if(b&&b.has(a)){return b.get(a)}var c={},d=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var e in a){if(Object.prototype.hasOwnProperty.call(a,e)){var f=d?Object.getOwnPropertyDescriptor(a,e):null;if(f&&(f.get||f.set)){Object.defineProperty(c,e,f)}else{c[e]=a[e]}}}c.default=a;if(b){b.set(a,c)}return c}function n(a){return a&&a.__esModule?a:{default:a}}function o(a){return r(a)||q(a)||u(a)||p()}function p(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function q(a){if("undefined"!=typeof Symbol&&Symbol.iterator in Object(a))return Array.from(a)}function r(a){if(Array.isArray(a))return v(a)}function s(a,b){return x(a)||w(a,b)||u(a,b)||t()}function t(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function u(a,b){if(!a)return;if("string"==typeof a)return v(a,b);var c=Object.prototype.toString.call(a).slice(8,-1);if("Object"===c&&a.constructor)c=a.constructor.name;if("Map"===c||"Set"===c)return Array.from(c);if("Arguments"===c||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(c))return v(a,b)}function v(a,b){if(null==b||b>a.length)b=a.length;for(var c=0,d=Array(b);ca.length)b=a.length;for(var c=0,d=Array(b);c.\n\n/**\n * Participants filter managemnet.\n *\n * @module core_user/participants_filter\n * @copyright 2020 Andrew Nicols \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport CourseFilter from './local/participantsfilter/filtertypes/courseid';\nimport * as DynamicTable from 'core_table/dynamic';\nimport GenericFilter from './local/participantsfilter/filter';\nimport {get_strings as getStrings} from 'core/str';\nimport Notification from 'core/notification';\nimport Pending from 'core/pending';\nimport Selectors from './local/participantsfilter/selectors';\nimport Templates from 'core/templates';\nimport CustomEvents from 'core/custom_interaction_events';\nimport jQuery from 'jquery';\n\n/**\n * Initialise the participants filter on the element with the given id.\n *\n * @param {String} participantsRegionId\n */\nexport const init = participantsRegionId => {\n // Keep a reference to the filterset.\n const filterSet = document.querySelector(`#${participantsRegionId}`);\n\n // Keep a reference to all of the active filters.\n const activeFilters = {\n courseid: new CourseFilter('courseid', filterSet),\n };\n\n /**\n * Get the filter list region.\n *\n * @return {HTMLElement}\n */\n const getFilterRegion = () => filterSet.querySelector(Selectors.filterset.regions.filterlist);\n\n /**\n * Add an unselected filter row.\n *\n * @return {Promise}\n */\n const addFilterRow = () => {\n const pendingPromise = new Pending('core_user/participantsfilter:addFilterRow');\n\n const rownum = 1 + getFilterRegion().querySelectorAll(Selectors.filter.region).length;\n return Templates.renderForPromise('core_user/local/participantsfilter/filterrow', {\"rownumber\": rownum})\n .then(({html, js}) => {\n const newContentNodes = Templates.appendNodeContents(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 = 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 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 const getFilterDataSource = filterType => {\n const filterDataNode = 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 const addFilter = async(filterRow, filterType, initialFilterValues) => {\n // Name the filter on the filter row.\n filterRow.dataset.filterType = filterType;\n\n const filterDataNode = 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 activeFilters[filterType] = new Filter(filterType, 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 updateFiltersOptions();\n\n return 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 const getFilterObject = name => {\n return 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 const removeOrReplaceFilterRow = (filterRow, refreshContent) => {\n const filterCount = getFilterRegion().querySelectorAll(Selectors.filter.region).length;\n\n if (filterCount === 1) {\n replaceFilterRow(filterRow, refreshContent);\n } else {\n 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 const removeFilterRow = async(filterRow, refreshContent = true) => {\n const filterType = filterRow.querySelector(Selectors.filter.fields.type);\n const hasFilterValue = !!filterType.value;\n\n // Remove the filter object.\n removeFilterObject(filterRow.dataset.filterType);\n\n // Remove the actual filter HTML.\n filterRow.remove();\n\n // Update the list of available filter types.\n updateFiltersOptions();\n\n if (hasFilterValue && refreshContent) {\n // Refresh the table if there was any content in this row.\n updateTableFromFilter();\n }\n\n // Update filter fieldset legends.\n const filterLegends = await getAvailableFilterLegends();\n\n 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 const replaceFilterRow = (filterRow, refreshContent = true, rowNum = 1) => {\n // Remove the filter object.\n removeFilterObject(filterRow.dataset.filterType);\n\n return Templates.renderForPromise('core_user/local/participantsfilter/filterrow', {\"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 = 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 updateFiltersOptions();\n\n return filterRow;\n })\n .then(filterRow => {\n // Refresh the table.\n if (refreshContent) {\n return 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 const removeFilterObject = filterName => {\n if (filterName) {\n const filter = getFilterObject(filterName);\n if (filter) {\n filter.tearDown();\n\n // Remove from the list of active filters.\n delete activeFilters[filterName];\n }\n }\n };\n\n /**\n * Remove all filters.\n *\n * @returns {Promise}\n */\n const removeAllFilters = () => {\n const pendingPromise = new Pending('core_user/participantsfilter:setFilterFromConfig');\n\n const filters = getFilterRegion().querySelectorAll(Selectors.filter.region);\n filters.forEach(filterRow => removeOrReplaceFilterRow(filterRow, false));\n\n // Refresh the table.\n return updateTableFromFilter()\n .then(result => {\n pendingPromise.resolve();\n\n return result;\n });\n };\n\n /**\n * Remove any empty filters.\n */\n const removeEmptyFilters = () => {\n const filters = getFilterRegion().querySelectorAll(Selectors.filter.region);\n filters.forEach(filterRow => {\n const filterType = filterRow.querySelector(Selectors.filter.fields.type);\n if (!filterType.value) {\n removeOrReplaceFilterRow(filterRow, false);\n }\n });\n };\n\n /**\n * Update the list of filter types to filter out those already selected.\n */\n const updateFiltersOptions = () => {\n const filters = 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 (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 = filterSet.querySelector(Selectors.filterset.actions.addRow);\n const filterDataNode = 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 filterSet.querySelector(Selectors.filterset.regions.filtermatch).classList.add('hidden');\n filterSet.querySelector(Selectors.filterset.fields.join).value = 2;\n filterSet.dataset.filterverb = 2;\n } else {\n filterSet.querySelector(Selectors.filterset.regions.filtermatch).classList.remove('hidden');\n }\n };\n\n /**\n * Set the current filter options based on a provided configuration.\n *\n * @param {Object} config\n * @param {Number} config.jointype\n * @param {Object} config.filters\n * @returns {Promise}\n */\n const setFilterFromConfig = config => {\n const filterConfig = Object.entries(config.filters);\n\n if (!filterConfig.length) {\n // There are no filters to set from.\n return Promise.resolve();\n }\n\n // Set the main join type.\n filterSet.querySelector(Selectors.filterset.fields.join).value = config.jointype;\n\n const filterPromises = filterConfig.map(([filterType, filterData]) => {\n if (filterType === 'courseid') {\n // The courseid is a special case.\n return false;\n }\n\n const filterValues = filterData.values;\n\n if (!filterValues.length) {\n // There are no values for this filter.\n // Skip it.\n return false;\n }\n\n return addFilterRow().then(([filterRow]) => addFilter(filterRow, filterType, filterValues));\n }).filter(promise => promise);\n\n if (!filterPromises.length) {\n return Promise.resolve();\n }\n\n return Promise.all(filterPromises).then(() => {\n return removeEmptyFilters();\n })\n .then(updateFiltersOptions)\n .then(updateTableFromFilter);\n };\n\n /**\n * Update the Dynamic table based upon the current filter.\n *\n * @return {Promise}\n */\n const updateTableFromFilter = () => {\n const pendingPromise = new Pending('core_user/participantsfilter:updateTableFromFilter');\n\n const filters = {};\n Object.values(activeFilters).forEach(filter => {\n filters[filter.filterValue.name] = filter.filterValue;\n });\n\n return DynamicTable.setFilters(\n DynamicTable.getTableFromId(filterSet.dataset.tableRegion),\n {\n jointype: parseInt(filterSet.querySelector(Selectors.filterset.fields.join).value, 10),\n filters,\n }\n )\n .then(result => {\n pendingPromise.resolve();\n\n return result;\n })\n .catch(Notification.exception);\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 const getAvailableFilterLegends = async() => {\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_user\",\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 // Add listeners for the main actions.\n filterSet.querySelector(Selectors.filterset.region).addEventListener('click', e => {\n if (e.target.closest(Selectors.filterset.actions.addRow)) {\n e.preventDefault();\n\n addFilterRow();\n }\n\n if (e.target.closest(Selectors.filterset.actions.applyFilters)) {\n e.preventDefault();\n\n updateTableFromFilter();\n }\n\n if (e.target.closest(Selectors.filterset.actions.resetFilters)) {\n e.preventDefault();\n\n removeAllFilters();\n }\n });\n\n // Add the listener to remove a single filter.\n filterSet.querySelector(Selectors.filterset.regions.filterlist).addEventListener('click', e => {\n if (e.target.closest(Selectors.filter.actions.remove)) {\n e.preventDefault();\n\n removeOrReplaceFilterRow(e.target.closest(Selectors.filter.region), true);\n }\n });\n\n // Add listeners for the filter type selection.\n let filterRegion = jQuery(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 addFilter(filter, typeField.value);\n }\n });\n\n filterSet.querySelector(Selectors.filterset.fields.join).addEventListener('change', e => {\n filterSet.dataset.filterverb = e.target.value;\n });\n\n const tableRoot = DynamicTable.getTableFromId(filterSet.dataset.tableRegion);\n const initialFilters = DynamicTable.getFilters(tableRoot);\n if (initialFilters) {\n const initialFilterPromise = new Pending('core_user/participantsfilter:setFilterFromConfig');\n // Apply the initial filter configuration.\n setFilterFromConfig(initialFilters)\n .then(() => initialFilterPromise.resolve())\n .catch();\n }\n};\n"],"file":"participantsfilter.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/participantsfilter.js"],"names":["init","participantsRegionId","filterSet","document","querySelector","activeFilters","courseid","CourseFilter","getFilterRegion","Selectors","filterset","regions","filterlist","addFilterRow","pendingPromise","Pending","rownum","querySelectorAll","filter","region","length","Templates","renderForPromise","then","html","js","newContentNodes","appendNodeContents","filterRow","typeList","data","forEach","contentNode","contentTypeList","fields","type","innerHTML","updateFiltersOptions","result","resolve","catch","Notification","exception","getFilterDataSource","filterType","filterDataNode","datasource","byName","addFilter","initialFilterValues","dataset","Filter","GenericFilter","filterTypeClass","typeField","value","disabled","getFilterObject","name","removeOrReplaceFilterRow","refreshContent","filterCount","replaceFilterRow","removeFilterRow","hasFilterValue","removeFilterObject","remove","updateTableFromFilter","getAvailableFilterLegends","filterLegends","index","innerText","rowNum","replaceNode","filterName","tearDown","removeAllFilters","filters","removeEmptyFilters","options","option","classList","add","addRowButton","actions","addRow","all","setAttribute","removeAttribute","filtermatch","join","filterverb","setFilterFromConfig","config","filterConfig","Object","entries","Promise","jointype","filterPromises","map","filterData","filterValues","values","promise","filterValue","DynamicTable","setFilters","getTableFromId","tableRegion","parseInt","maxFilters","typeListSelect","requests","Array","_","rowIndex","push","fetchedStrings","legendStrings","addEventListener","e","target","closest","preventDefault","applyFilters","resetFilters","filterRegion","CustomEvents","define","events","accessibleChange","on","tableRoot","initialFilters","getFilters","initialFilterPromise"],"mappings":"6rBAuBA,OACA,OACA,OAEA,OACA,OACA,OACA,OACA,OACA,O,g0EAOO,GAAMA,CAAAA,CAAI,CAAG,SAAAC,CAAoB,CAAI,IAElCC,CAAAA,CAAS,CAAGC,QAAQ,CAACC,aAAT,YAA2BH,CAA3B,EAFsB,CAKlCI,CAAa,CAAG,CAClBC,QAAQ,CAAE,GAAIC,UAAJ,CAAiB,UAAjB,CAA6BL,CAA7B,CADQ,CALkB,CAclCM,CAAe,CAAG,iBAAMN,CAAAA,CAAS,CAACE,aAAV,CAAwBK,UAAUC,SAAV,CAAoBC,OAApB,CAA4BC,UAApD,CAAN,CAdgB,CAqBlCC,CAAY,CAAG,UAAM,IACjBC,CAAAA,CAAc,CAAG,GAAIC,UAAJ,CAAY,2CAAZ,CADA,CAGjBC,CAAM,CAAG,EAAIR,CAAe,GAAGS,gBAAlB,CAAmCR,UAAUS,MAAV,CAAiBC,MAApD,EAA4DC,MAHxD,CAIvB,MAAOC,WAAUC,gBAAV,CAA2B,8CAA3B,CAA2E,CAAC,UAAaN,CAAd,CAA3E,EACNO,IADM,CACD,WAAgB,IAAdC,CAAAA,CAAc,GAAdA,IAAc,CAARC,CAAQ,GAARA,EAAQ,CACZC,CAAe,CAAGL,UAAUM,kBAAV,CAA6BnB,CAAe,EAA5C,CAAgDgB,CAAhD,CAAsDC,CAAtD,CADN,CAGlB,MAAOC,CAAAA,CACV,CALM,EAMNH,IANM,CAMD,SAAAK,CAAS,CAAI,CAKf,GAAMC,CAAAA,CAAQ,CAAG3B,CAAS,CAACE,aAAV,CAAwBK,UAAUqB,IAAV,CAAeD,QAAvC,CAAjB,CAEAD,CAAS,CAACG,OAAV,CAAkB,SAAAC,CAAW,CAAI,CAC7B,GAAMC,CAAAA,CAAe,CAAGD,CAAW,CAAC5B,aAAZ,CAA0BK,UAAUS,MAAV,CAAiBgB,MAAjB,CAAwBC,IAAlD,CAAxB,CAEA,GAAIF,CAAJ,CAAqB,CACjBA,CAAe,CAACG,SAAhB,CAA4BP,CAAQ,CAACO,SACxC,CACJ,CAND,EAQA,MAAOR,CAAAA,CACV,CAtBM,EAuBNL,IAvBM,CAuBD,SAAAK,CAAS,CAAI,CACfS,CAAoB,GAEpB,MAAOT,CAAAA,CACV,CA3BM,EA4BNL,IA5BM,CA4BD,SAAAe,CAAM,CAAI,CACZxB,CAAc,CAACyB,OAAf,GAEA,MAAOD,CAAAA,CACV,CAhCM,EAiCNE,KAjCM,CAiCAC,UAAaC,SAjCb,CAkCV,CA3DuC,CAmElCC,CAAmB,CAAG,SAAAC,CAAU,CAAI,CACtC,GAAMC,CAAAA,CAAc,CAAG3C,CAAS,CAACE,aAAV,CAAwBK,UAAUC,SAAV,CAAoBC,OAApB,CAA4BmC,UAApD,CAAvB,CAEA,MAAOD,CAAAA,CAAc,CAACzC,aAAf,CAA6BK,UAAUqB,IAAV,CAAeI,MAAf,CAAsBa,MAAtB,CAA6BH,CAA7B,CAA7B,CACV,CAvEuC,CAiFlCI,CAAS,4CAAG,WAAMpB,CAAN,CAAiBgB,CAAjB,CAA6BK,CAA7B,6FAEdrB,CAAS,CAACsB,OAAV,CAAkBN,UAAlB,CAA+BA,CAA/B,CAEMC,CAJQ,CAISF,CAAmB,CAACC,CAAD,CAJ5B,CAOVO,CAPU,CAODC,SAPC,aAQVP,CARU,WAQVA,CARU,QAQVA,CAAc,CAAEK,OAAhB,CAAwBG,eARd,gHASYR,CAAc,CAACK,OAAf,CAAuBG,eATnC,mMASYR,CAAc,CAACK,OAAf,CAAuBG,eATnC,sBASYR,CAAc,CAACK,OAAf,CAAuBG,eATnC,UASVF,CATU,eAWd9C,CAAa,CAACuC,CAAD,CAAb,CAA4B,GAAIO,CAAAA,CAAJ,CAAWP,CAAX,CAAuB1C,CAAvB,CAAkC+C,CAAlC,CAA5B,CAGMK,CAdQ,CAcI1B,CAAS,CAACxB,aAAV,CAAwBK,UAAUS,MAAV,CAAiBgB,MAAjB,CAAwBC,IAAhD,CAdJ,CAedmB,CAAS,CAACC,KAAV,CAAkBX,CAAlB,CACAU,CAAS,CAACE,QAAV,CAAqB,UAArB,CAGAnB,CAAoB,GAnBN,yBAqBPhC,CAAa,CAACuC,CAAD,CArBN,2CAAH,uDAjFyB,CA+GlCa,CAAe,CAAG,SAAAC,CAAI,CAAI,CAC5B,MAAOrD,CAAAA,CAAa,CAACqD,CAAD,CACvB,CAjHuC,CA0HlCC,CAAwB,CAAG,SAAC/B,CAAD,CAAYgC,CAAZ,CAA+B,CAC5D,GAAMC,CAAAA,CAAW,CAAGrD,CAAe,GAAGS,gBAAlB,CAAmCR,UAAUS,MAAV,CAAiBC,MAApD,EAA4DC,MAAhF,CAEA,GAAoB,CAAhB,GAAAyC,CAAJ,CAAuB,CACnBC,CAAgB,CAAClC,CAAD,CAAYgC,CAAZ,CACnB,CAFD,IAEO,CACHG,CAAe,CAACnC,CAAD,CAAYgC,CAAZ,CAClB,CACJ,CAlIuC,CA0IlCG,CAAe,4CAAG,WAAMnC,CAAN,2GAAiBgC,CAAjB,mCACdhB,CADc,CACDhB,CAAS,CAACxB,aAAV,CAAwBK,UAAUS,MAAV,CAAiBgB,MAAjB,CAAwBC,IAAhD,CADC,CAEd6B,CAFc,CAEG,CAAC,CAACpB,CAAU,CAACW,KAFhB,CAKpBU,CAAkB,CAACrC,CAAS,CAACsB,OAAV,CAAkBN,UAAnB,CAAlB,CAGAhB,CAAS,CAACsC,MAAV,GAGA7B,CAAoB,GAEpB,GAAI2B,CAAc,EAAIJ,CAAtB,CAAsC,CAElCO,CAAqB,EACxB,CAhBmB,eAmBQC,CAAAA,CAAyB,EAnBjC,QAmBdC,CAnBc,QAqBpB7D,CAAe,GAAGS,gBAAlB,CAAmCR,UAAUS,MAAV,CAAiBC,MAApD,EAA4DY,OAA5D,CAAoE,SAACH,CAAD,CAAY0C,CAAZ,CAAsB,CACtF1C,CAAS,CAACxB,aAAV,CAAwB,QAAxB,EAAkCmE,SAAlC,CAA8CF,CAAa,CAACC,CAAD,CAC9D,CAFD,EArBoB,yCAAH,uDA1ImB,CA6KlCR,CAAgB,CAAG,SAAClC,CAAD,CAAkD,IAAtCgC,CAAAA,CAAsC,2DAAfY,CAAe,wDAAN,CAAM,CAEvEP,CAAkB,CAACrC,CAAS,CAACsB,OAAV,CAAkBN,UAAnB,CAAlB,CAEA,MAAOvB,WAAUC,gBAAV,CAA2B,8CAA3B,CAA2E,CAAC,UAAakD,CAAd,CAA3E,EACNjD,IADM,CACD,WAAgB,IAAdC,CAAAA,CAAc,GAAdA,IAAc,CAARC,CAAQ,GAARA,EAAQ,CACZC,CAAe,CAAGL,UAAUoD,WAAV,CAAsB7C,CAAtB,CAAiCJ,CAAjC,CAAuCC,CAAvC,CADN,CAGlB,MAAOC,CAAAA,CACV,CALM,EAMNH,IANM,CAMD,SAAAK,CAAS,CAAI,CAKf,GAAMC,CAAAA,CAAQ,CAAG3B,CAAS,CAACE,aAAV,CAAwBK,UAAUqB,IAAV,CAAeD,QAAvC,CAAjB,CAEAD,CAAS,CAACG,OAAV,CAAkB,SAAAC,CAAW,CAAI,CAC7B,GAAMC,CAAAA,CAAe,CAAGD,CAAW,CAAC5B,aAAZ,CAA0BK,UAAUS,MAAV,CAAiBgB,MAAjB,CAAwBC,IAAlD,CAAxB,CAEA,GAAIF,CAAJ,CAAqB,CACjBA,CAAe,CAACG,SAAhB,CAA4BP,CAAQ,CAACO,SACxC,CACJ,CAND,EAQA,MAAOR,CAAAA,CACV,CAtBM,EAuBNL,IAvBM,CAuBD,SAAAK,CAAS,CAAI,CACfS,CAAoB,GAEpB,MAAOT,CAAAA,CACV,CA3BM,EA4BNL,IA5BM,CA4BD,SAAAK,CAAS,CAAI,CAEf,GAAIgC,CAAJ,CAAoB,CAChB,MAAOO,CAAAA,CAAqB,EAC/B,CAFD,IAEO,CACH,MAAOvC,CAAAA,CACV,CACJ,CAnCM,EAoCNY,KApCM,CAoCAC,UAAaC,SApCb,CAqCV,CAtNuC,CA6NlCuB,CAAkB,CAAG,SAAAS,CAAU,CAAI,CACrC,GAAIA,CAAJ,CAAgB,CACZ,GAAMxD,CAAAA,CAAM,CAAGuC,CAAe,CAACiB,CAAD,CAA9B,CACA,GAAIxD,CAAJ,CAAY,CACRA,CAAM,CAACyD,QAAP,GAGA,MAAOtE,CAAAA,CAAa,CAACqE,CAAD,CACvB,CACJ,CACJ,CAvOuC,CA8OlCE,CAAgB,CAAG,UAAM,IACrB9D,CAAAA,CAAc,CAAG,GAAIC,UAAJ,CAAY,kDAAZ,CADI,CAGrB8D,CAAO,CAAGrE,CAAe,GAAGS,gBAAlB,CAAmCR,UAAUS,MAAV,CAAiBC,MAApD,CAHW,CAI3B0D,CAAO,CAAC9C,OAAR,CAAgB,SAAAH,CAAS,QAAI+B,CAAAA,CAAwB,CAAC/B,CAAD,IAA5B,CAAzB,EAGA,MAAOuC,CAAAA,CAAqB,GAC3B5C,IADM,CACD,SAAAe,CAAM,CAAI,CACZxB,CAAc,CAACyB,OAAf,GAEA,MAAOD,CAAAA,CACV,CALM,CAMV,CA3PuC,CAgQlCwC,CAAkB,CAAG,UAAM,CAC7B,GAAMD,CAAAA,CAAO,CAAGrE,CAAe,GAAGS,gBAAlB,CAAmCR,UAAUS,MAAV,CAAiBC,MAApD,CAAhB,CACA0D,CAAO,CAAC9C,OAAR,CAAgB,SAAAH,CAAS,CAAI,CACzB,GAAMgB,CAAAA,CAAU,CAAGhB,CAAS,CAACxB,aAAV,CAAwBK,UAAUS,MAAV,CAAiBgB,MAAjB,CAAwBC,IAAhD,CAAnB,CACA,GAAI,CAACS,CAAU,CAACW,KAAhB,CAAuB,CACnBI,CAAwB,CAAC/B,CAAD,IAC3B,CACJ,CALD,CAMH,CAxQuC,CA6QlCS,CAAoB,CAAG,UAAM,CAC/B,GAAMwC,CAAAA,CAAO,CAAGrE,CAAe,GAAGS,gBAAlB,CAAmCR,UAAUS,MAAV,CAAiBC,MAApD,CAAhB,CACA0D,CAAO,CAAC9C,OAAR,CAAgB,SAAAH,CAAS,CAAI,CACzB,GAAMmD,CAAAA,CAAO,CAAGnD,CAAS,CAACX,gBAAV,CAA2BR,UAAUS,MAAV,CAAiBgB,MAAjB,CAAwBC,IAAxB,CAA+B,SAA1D,CAAhB,CACA4C,CAAO,CAAChD,OAAR,CAAgB,SAAAiD,CAAM,CAAI,CACtB,GAAIA,CAAM,CAACzB,KAAP,GAAiB3B,CAAS,CAACsB,OAAV,CAAkBN,UAAvC,CAAmD,CAC/CoC,CAAM,CAACC,SAAP,CAAiBf,MAAjB,CAAwB,QAAxB,EACAc,CAAM,CAACxB,QAAP,GACH,CAHD,IAGO,IAAInD,CAAa,CAAC2E,CAAM,CAACzB,KAAR,CAAjB,CAAiC,CACpCyB,CAAM,CAACC,SAAP,CAAiBC,GAAjB,CAAqB,QAArB,EACAF,CAAM,CAACxB,QAAP,GACH,CAHM,IAGA,CACHwB,CAAM,CAACC,SAAP,CAAiBf,MAAjB,CAAwB,QAAxB,EACAc,CAAM,CAACxB,QAAP,GACH,CACJ,CAXD,CAYH,CAdD,EAF+B,GAoBzB2B,CAAAA,CAAY,CAAGjF,CAAS,CAACE,aAAV,CAAwBK,UAAUC,SAAV,CAAoB0E,OAApB,CAA4BC,MAApD,CApBU,CAqBzBxC,CAAc,CAAG3C,CAAS,CAACe,gBAAV,CAA2BR,UAAUqB,IAAV,CAAeI,MAAf,CAAsBoD,GAAjD,CArBQ,CAsB/B,GAAIzC,CAAc,CAACzB,MAAf,EAAyByD,CAAO,CAACzD,MAArC,CAA6C,CACzC+D,CAAY,CAACI,YAAb,CAA0B,UAA1B,CAAsC,UAAtC,CACH,CAFD,IAEO,CACHJ,CAAY,CAACK,eAAb,CAA6B,UAA7B,CACH,CAED,GAAuB,CAAnB,GAAAX,CAAO,CAACzD,MAAZ,CAA0B,CACtBlB,CAAS,CAACE,aAAV,CAAwBK,UAAUC,SAAV,CAAoBC,OAApB,CAA4B8E,WAApD,EAAiER,SAAjE,CAA2EC,GAA3E,CAA+E,QAA/E,EACAhF,CAAS,CAACE,aAAV,CAAwBK,UAAUC,SAAV,CAAoBwB,MAApB,CAA2BwD,IAAnD,EAAyDnC,KAAzD,CAAiE,CAAjE,CACArD,CAAS,CAACgD,OAAV,CAAkByC,UAAlB,CAA+B,CAClC,CAJD,IAIO,CACHzF,CAAS,CAACE,aAAV,CAAwBK,UAAUC,SAAV,CAAoBC,OAApB,CAA4B8E,WAApD,EAAiER,SAAjE,CAA2Ef,MAA3E,CAAkF,QAAlF,CACH,CACJ,CAhTuC,CA0TlC0B,CAAmB,CAAG,SAAAC,CAAM,CAAI,CAClC,GAAMC,CAAAA,CAAY,CAAGC,MAAM,CAACC,OAAP,CAAeH,CAAM,CAAChB,OAAtB,CAArB,CAEA,GAAI,CAACiB,CAAY,CAAC1E,MAAlB,CAA0B,CAEtB,MAAO6E,CAAAA,OAAO,CAAC1D,OAAR,EACV,CAGDrC,CAAS,CAACE,aAAV,CAAwBK,UAAUC,SAAV,CAAoBwB,MAApB,CAA2BwD,IAAnD,EAAyDnC,KAAzD,CAAiEsC,CAAM,CAACK,QAAxE,CAEA,GAAMC,CAAAA,CAAc,CAAGL,CAAY,CAACM,GAAb,CAAiB,WAA8B,cAA5BxD,CAA4B,MAAhByD,CAAgB,MAClE,GAAmB,UAAf,GAAAzD,CAAJ,CAA+B,CAE3B,QACH,CAED,GAAM0D,CAAAA,CAAY,CAAGD,CAAU,CAACE,MAAhC,CAEA,GAAI,CAACD,CAAY,CAAClF,MAAlB,CAA0B,CAGtB,QACH,CAED,MAAOP,CAAAA,CAAY,GAAGU,IAAf,CAAoB,yBAAEK,CAAF,YAAiBoB,CAAAA,CAAS,CAACpB,CAAD,CAAYgB,CAAZ,CAAwB0D,CAAxB,CAA1B,CAApB,CACV,CAfsB,EAepBpF,MAfoB,CAeb,SAAAsF,CAAO,QAAIA,CAAAA,CAAJ,CAfM,CAAvB,CAiBA,GAAI,CAACL,CAAc,CAAC/E,MAApB,CAA4B,CACxB,MAAO6E,CAAAA,OAAO,CAAC1D,OAAR,EACV,CAED,MAAO0D,CAAAA,OAAO,CAACX,GAAR,CAAYa,CAAZ,EAA4B5E,IAA5B,CAAiC,UAAM,CAC1C,MAAOuD,CAAAA,CAAkB,EAC5B,CAFM,EAGNvD,IAHM,CAGDc,CAHC,EAINd,IAJM,CAID4C,CAJC,CAKV,CA/VuC,CAsWlCA,CAAqB,CAAG,UAAM,IAC1BrD,CAAAA,CAAc,CAAG,GAAIC,UAAJ,CAAY,oDAAZ,CADS,CAG1B8D,CAAO,CAAG,EAHgB,CAIhCkB,MAAM,CAACQ,MAAP,CAAclG,CAAd,EAA6B0B,OAA7B,CAAqC,SAAAb,CAAM,CAAI,CAC3C2D,CAAO,CAAC3D,CAAM,CAACuF,WAAP,CAAmB/C,IAApB,CAAP,CAAmCxC,CAAM,CAACuF,WAC7C,CAFD,EAIA,MAAOC,CAAAA,CAAY,CAACC,UAAb,CACHD,CAAY,CAACE,cAAb,CAA4B1G,CAAS,CAACgD,OAAV,CAAkB2D,WAA9C,CADG,CAEH,CACIX,QAAQ,CAAEY,QAAQ,CAAC5G,CAAS,CAACE,aAAV,CAAwBK,UAAUC,SAAV,CAAoBwB,MAApB,CAA2BwD,IAAnD,EAAyDnC,KAA1D,CAAiE,EAAjE,CADtB,CAEIsB,OAAO,CAAPA,CAFJ,CAFG,EAONtD,IAPM,CAOD,SAAAe,CAAM,CAAI,CACZxB,CAAc,CAACyB,OAAf,GAEA,MAAOD,CAAAA,CACV,CAXM,EAYNE,KAZM,CAYAC,UAAaC,SAZb,CAaV,CA3XuC,CAkYlC0B,CAAyB,4CAAG,wGACxB2C,CADwB,CACX5G,QAAQ,CAACC,aAAT,CAAuBK,UAAUqB,IAAV,CAAekF,cAAtC,EAAsD5F,MAAtD,CAA+D,CADpD,CAE1B6F,CAF0B,CAEf,EAFe,CAI9B,EAAIC,KAAK,CAACH,CAAD,CAAT,EAAuBhF,OAAvB,CAA+B,SAACoF,CAAD,CAAIC,CAAJ,CAAiB,CAC5CH,CAAQ,CAACI,IAAT,CAAc,CACV,IAAO,iBADG,CAEV,UAAa,WAFH,CAIV,MAASD,CAAQ,CAAG,CAJV,CAAd,CAMH,CAPD,EAJ8B,eAaF,kBAAWH,CAAX,EAC3B1F,IAD2B,CACtB,SAAA+F,CAAc,CAAI,CACpB,MAAOA,CAAAA,CACV,CAH2B,EAI3B9E,KAJ2B,CAIrBC,UAAaC,SAJQ,CAbE,QAaxB6E,CAbwB,iCAmBvBA,CAnBuB,0CAAH,uDAlYS,CAyZxCrH,CAAS,CAACE,aAAV,CAAwBK,UAAUC,SAAV,CAAoBS,MAA5C,EAAoDqG,gBAApD,CAAqE,OAArE,CAA8E,SAAAC,CAAC,CAAI,CAC/E,GAAIA,CAAC,CAACC,MAAF,CAASC,OAAT,CAAiBlH,UAAUC,SAAV,CAAoB0E,OAApB,CAA4BC,MAA7C,CAAJ,CAA0D,CACtDoC,CAAC,CAACG,cAAF,GAEA/G,CAAY,EACf,CAED,GAAI4G,CAAC,CAACC,MAAF,CAASC,OAAT,CAAiBlH,UAAUC,SAAV,CAAoB0E,OAApB,CAA4ByC,YAA7C,CAAJ,CAAgE,CAC5DJ,CAAC,CAACG,cAAF,GAEAzD,CAAqB,EACxB,CAED,GAAIsD,CAAC,CAACC,MAAF,CAASC,OAAT,CAAiBlH,UAAUC,SAAV,CAAoB0E,OAApB,CAA4B0C,YAA7C,CAAJ,CAAgE,CAC5DL,CAAC,CAACG,cAAF,GAEAhD,CAAgB,EACnB,CACJ,CAlBD,EAqBA1E,CAAS,CAACE,aAAV,CAAwBK,UAAUC,SAAV,CAAoBC,OAApB,CAA4BC,UAApD,EAAgE4G,gBAAhE,CAAiF,OAAjF,CAA0F,SAAAC,CAAC,CAAI,CAC3F,GAAIA,CAAC,CAACC,MAAF,CAASC,OAAT,CAAiBlH,UAAUS,MAAV,CAAiBkE,OAAjB,CAAyBlB,MAA1C,CAAJ,CAAuD,CACnDuD,CAAC,CAACG,cAAF,GAEAjE,CAAwB,CAAC8D,CAAC,CAACC,MAAF,CAASC,OAAT,CAAiBlH,UAAUS,MAAV,CAAiBC,MAAlC,CAAD,IAC3B,CACJ,CAND,EASA,GAAI4G,CAAAA,CAAY,CAAG,cAAOvH,CAAe,EAAtB,CAAnB,CACAwH,UAAaC,MAAb,CAAoBF,CAApB,CAAkC,CAACC,UAAaE,MAAb,CAAoBC,gBAArB,CAAlC,EACAJ,CAAY,CAACK,EAAb,CAAgBJ,UAAaE,MAAb,CAAoBC,gBAApC,CAAsD,SAAAV,CAAC,CAAI,CACvD,GAAMnE,CAAAA,CAAS,CAAGmE,CAAC,CAACC,MAAF,CAASC,OAAT,CAAiBlH,UAAUS,MAAV,CAAiBgB,MAAjB,CAAwBC,IAAzC,CAAlB,CACA,GAAImB,CAAS,EAAIA,CAAS,CAACC,KAA3B,CAAkC,CAC9B,GAAMrC,CAAAA,CAAM,CAAGuG,CAAC,CAACC,MAAF,CAASC,OAAT,CAAiBlH,UAAUS,MAAV,CAAiBC,MAAlC,CAAf,CAEA6B,CAAS,CAAC9B,CAAD,CAASoC,CAAS,CAACC,KAAnB,CACZ,CACJ,CAPD,EASArD,CAAS,CAACE,aAAV,CAAwBK,UAAUC,SAAV,CAAoBwB,MAApB,CAA2BwD,IAAnD,EAAyD8B,gBAAzD,CAA0E,QAA1E,CAAoF,SAAAC,CAAC,CAAI,CACrFvH,CAAS,CAACgD,OAAV,CAAkByC,UAAlB,CAA+B8B,CAAC,CAACC,MAAF,CAASnE,KAC3C,CAFD,EAlcwC,GAsclC8E,CAAAA,CAAS,CAAG3B,CAAY,CAACE,cAAb,CAA4B1G,CAAS,CAACgD,OAAV,CAAkB2D,WAA9C,CAtcsB,CAuclCyB,CAAc,CAAG5B,CAAY,CAAC6B,UAAb,CAAwBF,CAAxB,CAvciB,CAwcxC,GAAIC,CAAJ,CAAoB,CAChB,GAAME,CAAAA,CAAoB,CAAG,GAAIzH,UAAJ,CAAY,kDAAZ,CAA7B,CAEA6E,CAAmB,CAAC0C,CAAD,CAAnB,CACC/G,IADD,CACM,iBAAMiH,CAAAA,CAAoB,CAACjG,OAArB,EAAN,CADN,EAECC,KAFD,EAGH,CACJ,CA/cM,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 .\n\n/**\n * Participants filter managemnet.\n *\n * @module core_user/participants_filter\n * @copyright 2020 Andrew Nicols \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport CourseFilter from './local/participantsfilter/filtertypes/courseid';\nimport * as DynamicTable from 'core_table/dynamic';\nimport GenericFilter from './local/participantsfilter/filter';\nimport {get_strings as getStrings} from 'core/str';\nimport Notification from 'core/notification';\nimport Pending from 'core/pending';\nimport Selectors from './local/participantsfilter/selectors';\nimport Templates from 'core/templates';\nimport CustomEvents from 'core/custom_interaction_events';\nimport jQuery from 'jquery';\n\n/**\n * Initialise the participants filter on the element with the given id.\n *\n * @param {String} participantsRegionId\n */\nexport const init = participantsRegionId => {\n // Keep a reference to the filterset.\n const filterSet = document.querySelector(`#${participantsRegionId}`);\n\n // Keep a reference to all of the active filters.\n const activeFilters = {\n courseid: new CourseFilter('courseid', filterSet),\n };\n\n /**\n * Get the filter list region.\n *\n * @return {HTMLElement}\n */\n const getFilterRegion = () => filterSet.querySelector(Selectors.filterset.regions.filterlist);\n\n /**\n * Add an unselected filter row.\n *\n * @return {Promise}\n */\n const addFilterRow = () => {\n const pendingPromise = new Pending('core_user/participantsfilter:addFilterRow');\n\n const rownum = 1 + getFilterRegion().querySelectorAll(Selectors.filter.region).length;\n return Templates.renderForPromise('core_user/local/participantsfilter/filterrow', {\"rownumber\": rownum})\n .then(({html, js}) => {\n const newContentNodes = Templates.appendNodeContents(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 = 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 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 const getFilterDataSource = filterType => {\n const filterDataNode = 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 const addFilter = async(filterRow, filterType, initialFilterValues) => {\n // Name the filter on the filter row.\n filterRow.dataset.filterType = filterType;\n\n const filterDataNode = 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 activeFilters[filterType] = new Filter(filterType, 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 updateFiltersOptions();\n\n return 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 const getFilterObject = name => {\n return 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 const removeOrReplaceFilterRow = (filterRow, refreshContent) => {\n const filterCount = getFilterRegion().querySelectorAll(Selectors.filter.region).length;\n\n if (filterCount === 1) {\n replaceFilterRow(filterRow, refreshContent);\n } else {\n 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 const removeFilterRow = async(filterRow, refreshContent = true) => {\n const filterType = filterRow.querySelector(Selectors.filter.fields.type);\n const hasFilterValue = !!filterType.value;\n\n // Remove the filter object.\n removeFilterObject(filterRow.dataset.filterType);\n\n // Remove the actual filter HTML.\n filterRow.remove();\n\n // Update the list of available filter types.\n updateFiltersOptions();\n\n if (hasFilterValue && refreshContent) {\n // Refresh the table if there was any content in this row.\n updateTableFromFilter();\n }\n\n // Update filter fieldset legends.\n const filterLegends = await getAvailableFilterLegends();\n\n 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 const replaceFilterRow = (filterRow, refreshContent = true, rowNum = 1) => {\n // Remove the filter object.\n removeFilterObject(filterRow.dataset.filterType);\n\n return Templates.renderForPromise('core_user/local/participantsfilter/filterrow', {\"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 = 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 updateFiltersOptions();\n\n return filterRow;\n })\n .then(filterRow => {\n // Refresh the table.\n if (refreshContent) {\n return 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 const removeFilterObject = filterName => {\n if (filterName) {\n const filter = getFilterObject(filterName);\n if (filter) {\n filter.tearDown();\n\n // Remove from the list of active filters.\n delete activeFilters[filterName];\n }\n }\n };\n\n /**\n * Remove all filters.\n *\n * @returns {Promise}\n */\n const removeAllFilters = () => {\n const pendingPromise = new Pending('core_user/participantsfilter:setFilterFromConfig');\n\n const filters = getFilterRegion().querySelectorAll(Selectors.filter.region);\n filters.forEach(filterRow => removeOrReplaceFilterRow(filterRow, false));\n\n // Refresh the table.\n return updateTableFromFilter()\n .then(result => {\n pendingPromise.resolve();\n\n return result;\n });\n };\n\n /**\n * Remove any empty filters.\n */\n const removeEmptyFilters = () => {\n const filters = getFilterRegion().querySelectorAll(Selectors.filter.region);\n filters.forEach(filterRow => {\n const filterType = filterRow.querySelector(Selectors.filter.fields.type);\n if (!filterType.value) {\n removeOrReplaceFilterRow(filterRow, false);\n }\n });\n };\n\n /**\n * Update the list of filter types to filter out those already selected.\n */\n const updateFiltersOptions = () => {\n const filters = 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 (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 = filterSet.querySelector(Selectors.filterset.actions.addRow);\n const filterDataNode = 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 filterSet.querySelector(Selectors.filterset.regions.filtermatch).classList.add('hidden');\n filterSet.querySelector(Selectors.filterset.fields.join).value = 2;\n filterSet.dataset.filterverb = 2;\n } else {\n filterSet.querySelector(Selectors.filterset.regions.filtermatch).classList.remove('hidden');\n }\n };\n\n /**\n * Set the current filter options based on a provided configuration.\n *\n * @param {Object} config\n * @param {Number} config.jointype\n * @param {Object} config.filters\n * @returns {Promise}\n */\n const setFilterFromConfig = config => {\n const filterConfig = Object.entries(config.filters);\n\n if (!filterConfig.length) {\n // There are no filters to set from.\n return Promise.resolve();\n }\n\n // Set the main join type.\n filterSet.querySelector(Selectors.filterset.fields.join).value = config.jointype;\n\n const filterPromises = filterConfig.map(([filterType, filterData]) => {\n if (filterType === 'courseid') {\n // The courseid is a special case.\n return false;\n }\n\n const filterValues = filterData.values;\n\n if (!filterValues.length) {\n // There are no values for this filter.\n // Skip it.\n return false;\n }\n\n return addFilterRow().then(([filterRow]) => addFilter(filterRow, filterType, filterValues));\n }).filter(promise => promise);\n\n if (!filterPromises.length) {\n return Promise.resolve();\n }\n\n return Promise.all(filterPromises).then(() => {\n return removeEmptyFilters();\n })\n .then(updateFiltersOptions)\n .then(updateTableFromFilter);\n };\n\n /**\n * Update the Dynamic table based upon the current filter.\n *\n * @return {Promise}\n */\n const updateTableFromFilter = () => {\n const pendingPromise = new Pending('core_user/participantsfilter:updateTableFromFilter');\n\n const filters = {};\n Object.values(activeFilters).forEach(filter => {\n filters[filter.filterValue.name] = filter.filterValue;\n });\n\n return DynamicTable.setFilters(\n DynamicTable.getTableFromId(filterSet.dataset.tableRegion),\n {\n jointype: parseInt(filterSet.querySelector(Selectors.filterset.fields.join).value, 10),\n filters,\n }\n )\n .then(result => {\n pendingPromise.resolve();\n\n return result;\n })\n .catch(Notification.exception);\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 const getAvailableFilterLegends = async() => {\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_user\",\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 // Add listeners for the main actions.\n filterSet.querySelector(Selectors.filterset.region).addEventListener('click', e => {\n if (e.target.closest(Selectors.filterset.actions.addRow)) {\n e.preventDefault();\n\n addFilterRow();\n }\n\n if (e.target.closest(Selectors.filterset.actions.applyFilters)) {\n e.preventDefault();\n\n updateTableFromFilter();\n }\n\n if (e.target.closest(Selectors.filterset.actions.resetFilters)) {\n e.preventDefault();\n\n removeAllFilters();\n }\n });\n\n // Add the listener to remove a single filter.\n filterSet.querySelector(Selectors.filterset.regions.filterlist).addEventListener('click', e => {\n if (e.target.closest(Selectors.filter.actions.remove)) {\n e.preventDefault();\n\n removeOrReplaceFilterRow(e.target.closest(Selectors.filter.region), true);\n }\n });\n\n // Add listeners for the filter type selection.\n let filterRegion = jQuery(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 addFilter(filter, typeField.value);\n }\n });\n\n filterSet.querySelector(Selectors.filterset.fields.join).addEventListener('change', e => {\n filterSet.dataset.filterverb = e.target.value;\n });\n\n const tableRoot = DynamicTable.getTableFromId(filterSet.dataset.tableRegion);\n const initialFilters = DynamicTable.getFilters(tableRoot);\n if (initialFilters) {\n const initialFilterPromise = new Pending('core_user/participantsfilter:setFilterFromConfig');\n // Apply the initial filter configuration.\n setFilterFromConfig(initialFilters)\n .then(() => initialFilterPromise.resolve())\n .catch();\n }\n};\n"],"file":"participantsfilter.min.js"} \ No newline at end of file diff --git a/user/amd/src/local/participantsfilter/filter.js b/user/amd/src/local/participantsfilter/filter.js index 74e749ca44a..7e485d8ae65 100644 --- a/user/amd/src/local/participantsfilter/filter.js +++ b/user/amd/src/local/participantsfilter/filter.js @@ -86,7 +86,11 @@ export default class { const filterValueNode = this.getFilterValueNode(); // Copy the data in place. - filterValueNode.innerHTML = this.getSourceDataForFilter().outerHTML; + const sourceDataNode = this.getSourceDataForFilter(); + if (!sourceDataNode) { + return; + } + filterValueNode.innerHTML = sourceDataNode.outerHTML; const dataSource = filterValueNode.querySelector('select'); diff --git a/user/amd/src/participantsfilter.js b/user/amd/src/participantsfilter.js index 5ac8255f1d2..08ea027333a 100644 --- a/user/amd/src/participantsfilter.js +++ b/user/amd/src/participantsfilter.js @@ -126,7 +126,7 @@ export const init = participantsRegionId => { // Instantiate the Filter class. let Filter = GenericFilter; - if (filterDataNode.dataset.filterTypeClass) { + if (filterDataNode?.dataset.filterTypeClass) { Filter = await import(filterDataNode.dataset.filterTypeClass); } activeFilters[filterType] = new Filter(filterType, filterSet, initialFilterValues);