From efc3dfff47c707f470d239df69da742750712c95 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Tue, 9 Jun 2020 10:59:08 +0800 Subject: [PATCH 1/6] MDL-68977 core_table: Do not update dynamic tables on nullop When a setting is added/removed (i.e. an empty filter is removed) and there is no change, then we should treat this is a null-op and not refresh the table unnecessarily. --- lib/table/amd/build/dynamic.min.js | 2 +- lib/table/amd/build/dynamic.min.js.map | 2 +- lib/table/amd/src/dynamic.js | 39 ++++++++++++++++++++++++-- 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/lib/table/amd/build/dynamic.min.js b/lib/table/amd/build/dynamic.min.js index 0060edcd1c8..11ddc73bc96 100644 --- a/lib/table/amd/build/dynamic.min.js +++ b/lib/table/amd/build/dynamic.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_table/dynamic",["exports","core_table/local/dynamic/selectors","./local/dynamic/events","core/pending","core/loadingicon","core_table/local/dynamic/repository","core/notification"],function(a,b,c,d,e,f,g){"use strict";Object.defineProperty(a,"__esModule",{value:!0});Object.defineProperty(a,"Events",{enumerable:!0,get:function get(){return c.default}});a.getTableFromId=a.init=a.showColumn=a.hideColumn=a.getLastInitial=a.setLastInitial=a.getFirstInitial=a.setFirstInitial=a.getPageSize=a.setPageSize=a.getPageNumber=a.setPageNumber=a.setSortOrder=a.getFilters=a.setFilters=a.updateTable=a.refreshTableContent=void 0;b=j(b);c=h(c);d=h(d);g=h(g);function h(a){return a&&a.__esModule?a:{default:a}}function i(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;i=function(){return a};return a}function j(a){if(a&&a.__esModule){return a}if(null===a||"object"!==_typeof(a)&&"function"!=typeof a){return{default:a}}var b=i();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 k(a){return o(a)||n(a)||m(a)||l()}function l(){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 m(a,b){if(!a)return;if("string"==typeof a)return p(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 p(a,b)}function n(a){if("undefined"!=typeof Symbol&&Symbol.iterator in Object(a))return Array.from(a)}function o(a){if(Array.isArray(a))return p(a)}function p(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 * Module to handle dynamic table features.\n *\n * @module core_table/dynamic\n * @package core_table\n * @copyright 2020 Simey Lameze \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport * as Selectors from 'core_table/local/dynamic/selectors';\nimport Events from './local/dynamic/events';\nimport Pending from 'core/pending';\nimport {addIconToContainer} from 'core/loadingicon';\nimport {fetch as fetchTableData} from 'core_table/local/dynamic/repository';\nimport Notification from 'core/notification';\n\nlet watching = false;\n\n/**\n * Ensure that a table is a dynamic table.\n *\n * @param {HTMLElement} tableRoot\n * @returns {Bool}\n */\nconst checkTableIsDynamic = tableRoot => {\n if (!tableRoot) {\n // The table is not a dynamic table.\n throw new Error(\"The table specified is not a dynamic table and cannot be updated\");\n }\n\n if (!tableRoot.matches(Selectors.main.region)) {\n // The table is not a dynamic table.\n throw new Error(\"The table specified is not a dynamic table and cannot be updated\");\n }\n\n return true;\n};\n\n/**\n * Get the filterset data from a known dynamic table.\n *\n * @param {HTMLElement} tableRoot\n * @returns {Object}\n */\nconst getFiltersetFromTable = tableRoot => {\n return JSON.parse(tableRoot.dataset.tableFilters);\n};\n\n/**\n * Update the specified table based on its current values.\n *\n * @param {HTMLElement} tableRoot\n * @param {Bool} resetContent\n * @returns {Promise}\n */\nexport const refreshTableContent = (tableRoot, resetContent = false) => {\n const filterset = getFiltersetFromTable(tableRoot);\n addIconToContainer(tableRoot);\n\n const pendingPromise = new Pending('core_table/dynamic:refreshTableContent');\n\n return fetchTableData(\n tableRoot.dataset.tableComponent,\n tableRoot.dataset.tableHandler,\n tableRoot.dataset.tableUniqueid,\n {\n sortData: JSON.parse(tableRoot.dataset.tableSortData),\n joinType: filterset.jointype,\n filters: filterset.filters,\n firstinitial: tableRoot.dataset.tableFirstInitial,\n lastinitial: tableRoot.dataset.tableLastInitial,\n pageNumber: tableRoot.dataset.tablePageNumber,\n pageSize: tableRoot.dataset.tablePageSize,\n hiddenColumns: JSON.parse(tableRoot.dataset.tableHiddenColumns),\n },\n resetContent,\n )\n .then(data => {\n const placeholder = document.createElement('div');\n placeholder.innerHTML = data.html;\n tableRoot.replaceWith(...placeholder.childNodes);\n\n // Update the tableRoot.\n return getTableFromId(tableRoot.dataset.tableUniqueid);\n }).then(tableRoot => {\n tableRoot.dispatchEvent(new CustomEvent(Events.tableContentRefreshed, {\n bubbles: true,\n }));\n\n return tableRoot;\n })\n .then(tableRoot => {\n pendingPromise.resolve();\n\n return tableRoot;\n });\n};\n\nexport const updateTable = (tableRoot, {\n sortBy = null,\n sortOrder = null,\n filters = null,\n firstInitial = null,\n lastInitial = null,\n pageNumber = null,\n pageSize = null,\n hiddenColumns = null,\n} = {}, refreshContent = true) => {\n checkTableIsDynamic(tableRoot);\n\n const pendingPromise = new Pending('core_table/dynamic:updateTable');\n\n // Update sort fields.\n if (sortBy && sortOrder) {\n const sortData = JSON.parse(tableRoot.dataset.tableSortData);\n sortData.unshift({\n sortby: sortBy,\n sortorder: parseInt(sortOrder, 10),\n });\n tableRoot.dataset.tableSortData = JSON.stringify(sortData);\n }\n\n // Update initials.\n if (firstInitial !== null) {\n tableRoot.dataset.tableFirstInitial = firstInitial;\n }\n\n if (lastInitial !== null) {\n tableRoot.dataset.tableLastInitial = lastInitial;\n }\n\n if (pageNumber !== null) {\n tableRoot.dataset.tablePageNumber = pageNumber;\n }\n\n if (pageSize !== null) {\n tableRoot.dataset.tablePageSize = pageSize;\n }\n\n // Update filters.\n if (filters) {\n tableRoot.dataset.tableFilters = JSON.stringify(filters);\n }\n\n // Update hidden columns.\n if (hiddenColumns) {\n tableRoot.dataset.tableHiddenColumns = JSON.stringify(hiddenColumns);\n }\n\n // Refresh.\n if (refreshContent) {\n return refreshTableContent(tableRoot)\n .then(tableRoot => {\n pendingPromise.resolve();\n return tableRoot;\n });\n } else {\n pendingPromise.resolve();\n return Promise.resolve(tableRoot);\n }\n};\n\n/**\n * Get the table dataset for the specified tableRoot, ensuring that the provided table is a dynamic table.\n *\n * @param {HTMLElement} tableRoot\n * @returns {DOMStringMap}\n */\nconst getTableData = tableRoot => {\n checkTableIsDynamic(tableRoot);\n\n return tableRoot.dataset;\n};\n\n/**\n * Update the specified table using the new filters.\n *\n * @param {HTMLElement} tableRoot\n * @param {Object} filters\n * @param {Bool} refreshContent\n * @returns {Promise}\n */\nexport const setFilters = (tableRoot, filters, refreshContent = true) =>\n updateTable(tableRoot, {filters}, refreshContent);\n\n/**\n * Get the filter data for the specified table.\n *\n * @param {HTMLElement} tableRoot\n * @returns {Object}\n */\nexport const getFilters = tableRoot => {\n checkTableIsDynamic(tableRoot);\n\n return getFiltersetFromTable(tableRoot);\n};\n\n/**\n * Update the sort order.\n *\n * @param {HTMLElement} tableRoot\n * @param {String} sortBy\n * @param {Number} sortOrder\n * @param {Bool} refreshContent\n * @returns {Promise}\n */\nexport const setSortOrder = (tableRoot, sortBy, sortOrder, refreshContent = true) =>\n updateTable(tableRoot, {sortBy, sortOrder}, refreshContent);\n\n/**\n * Set the page number.\n *\n * @param {HTMLElement} tableRoot\n * @param {String} pageNumber\n * @param {Bool} refreshContent\n * @returns {Promise}\n */\nexport const setPageNumber = (tableRoot, pageNumber, refreshContent = true) =>\n updateTable(tableRoot, {pageNumber}, refreshContent);\n\n/**\n * Get the current page number.\n *\n * @param {HTMLElement} tableRoot\n * @returns {Number}\n */\nexport const getPageNumber = tableRoot => getTableData(tableRoot).tablePageNumber;\n\n/**\n * Set the page size.\n *\n * @param {HTMLElement} tableRoot\n * @param {Number} pageSize\n * @param {Bool} refreshContent\n * @returns {Promise}\n */\nexport const setPageSize = (tableRoot, pageSize, refreshContent = true) =>\n updateTable(tableRoot, {pageSize, pageNumber: 1}, refreshContent);\n\n/**\n * Get the current page size.\n *\n * @param {HTMLElement} tableRoot\n * @returns {Number}\n */\nexport const getPageSize = tableRoot => getTableData(tableRoot).tablePageSize;\n\n/**\n * Update the first initial to show.\n *\n * @param {HTMLElement} tableRoot\n * @param {String} firstInitial\n * @param {Bool} refreshContent\n * @returns {Promise}\n */\nexport const setFirstInitial = (tableRoot, firstInitial, refreshContent = true) =>\n updateTable(tableRoot, {firstInitial}, refreshContent);\n\n/**\n * Get the current first initial filter.\n *\n * @param {HTMLElement} tableRoot\n * @returns {String}\n */\nexport const getFirstInitial = tableRoot => getTableData(tableRoot).tableFirstInitial;\n\n/**\n * Update the last initial to show.\n *\n * @param {HTMLElement} tableRoot\n * @param {String} lastInitial\n * @param {Bool} refreshContent\n * @returns {Promise}\n */\nexport const setLastInitial = (tableRoot, lastInitial, refreshContent = true) =>\n updateTable(tableRoot, {lastInitial}, refreshContent);\n\n/**\n * Get the current last initial filter.\n *\n * @param {HTMLElement} tableRoot\n * @returns {String}\n */\nexport const getLastInitial = tableRoot => getTableData(tableRoot).tableLastInitial;\n\n/**\n * Hide a column in the participants table.\n *\n * @param {HTMLElement} tableRoot\n * @param {String} columnToHide\n * @param {Bool} refreshContent\n */\nexport const hideColumn = (tableRoot, columnToHide, refreshContent = true) => {\n const hiddenColumns = JSON.parse(tableRoot.dataset.tableHiddenColumns);\n hiddenColumns.push(columnToHide);\n\n updateTable(tableRoot, {hiddenColumns}, refreshContent);\n};\n\n/**\n * Make a hidden column visible in the participants table.\n *\n * @param {HTMLElement} tableRoot\n * @param {String} columnToShow\n * @param {Bool} refreshContent\n */\nexport const showColumn = (tableRoot, columnToShow, refreshContent = true) => {\n let hiddenColumns = JSON.parse(tableRoot.dataset.tableHiddenColumns);\n hiddenColumns = hiddenColumns.filter(columnName => columnName !== columnToShow);\n\n updateTable(tableRoot, {hiddenColumns}, refreshContent);\n};\n\n/**\n * Reset table preferences.\n *\n * @param {HTMLElement} tableRoot\n * @returns {Promise}\n */\nconst resetTablePreferences = tableRoot => refreshTableContent(tableRoot, true);\n\n/**\n * Set up listeners to handle table updates.\n */\nexport const init = () => {\n if (watching) {\n // Already watching.\n return;\n }\n watching = true;\n\n document.addEventListener('click', e => {\n const tableRoot = e.target.closest(Selectors.main.region);\n\n if (!tableRoot) {\n return;\n }\n\n const sortableLink = e.target.closest(Selectors.table.links.sortableColumn);\n if (sortableLink) {\n e.preventDefault();\n\n setSortOrder(tableRoot, sortableLink.dataset.sortby, sortableLink.dataset.sortorder)\n .catch(Notification.exception);\n }\n\n const firstInitialLink = e.target.closest(Selectors.initialsBar.links.firstInitial);\n if (firstInitialLink !== null) {\n e.preventDefault();\n\n setFirstInitial(tableRoot, firstInitialLink.dataset.initial).catch(Notification.exception);\n }\n\n const lastInitialLink = e.target.closest(Selectors.initialsBar.links.lastInitial);\n if (lastInitialLink !== null) {\n e.preventDefault();\n\n setLastInitial(tableRoot, lastInitialLink.dataset.initial).catch(Notification.exception);\n }\n\n const pageItem = e.target.closest(Selectors.paginationBar.links.pageItem);\n if (pageItem) {\n e.preventDefault();\n\n setPageNumber(tableRoot, pageItem.dataset.pageNumber).catch(Notification.exception);\n }\n\n const hide = e.target.closest(Selectors.table.links.hide);\n if (hide) {\n e.preventDefault();\n\n hideColumn(tableRoot, hide.dataset.column).catch(Notification.exception);\n }\n\n const show = e.target.closest(Selectors.table.links.show);\n if (show) {\n e.preventDefault();\n\n showColumn(tableRoot, show.dataset.column).catch(Notification.exception);\n }\n\n const resetTablePreferencesLink = e.target.closest('.resettable a');\n if (resetTablePreferencesLink) {\n e.preventDefault();\n\n resetTablePreferences(tableRoot).catch(Notification.exception);\n }\n });\n};\n\n/**\n * Fetch the table via its table region id.\n *\n * @param {String} tableRegionId\n * @returns {HTMLElement}\n */\nexport const getTableFromId = tableRegionId => {\n const tableRoot = document.querySelector(Selectors.main.fromRegionId(tableRegionId));\n\n\n if (!tableRoot) {\n // The table is not a dynamic table.\n throw new Error(\"The table specified is not a dynamic table and cannot be updated\");\n }\n\n return tableRoot;\n};\n\nexport {\n Events\n};\n"],"file":"dynamic.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/dynamic.js"],"names":["watching","checkTableIsDynamic","tableRoot","Error","matches","Selectors","main","region","getFiltersetFromTable","JSON","parse","dataset","tableFilters","refreshTableContent","resetContent","filterset","pendingPromise","Pending","tableComponent","tableHandler","tableUniqueid","sortData","tableSortData","joinType","jointype","filters","firstinitial","tableFirstInitial","lastinitial","tableLastInitial","pageNumber","tablePageNumber","pageSize","tablePageSize","hiddenColumns","tableHiddenColumns","then","data","placeholder","document","createElement","innerHTML","html","replaceWith","childNodes","getTableFromId","dispatchEvent","CustomEvent","Events","tableContentRefreshed","bubbles","resolve","updateTable","sortBy","sortOrder","firstInitial","lastInitial","refreshContent","tableConfigChanged","unshift","sortby","sortorder","parseInt","stringify","filterJson","columnJson","Promise","getTableData","setFilters","getFilters","setSortOrder","setPageNumber","getPageNumber","setPageSize","getPageSize","setFirstInitial","getFirstInitial","setLastInitial","getLastInitial","hideColumn","columnToHide","push","showColumn","columnToShow","filter","columnName","resetTablePreferences","init","addEventListener","e","target","closest","sortableLink","table","links","sortableColumn","preventDefault","catch","Notification","exception","firstInitialLink","initialsBar","initial","lastInitialLink","pageItem","paginationBar","hide","column","show","resetTablePreferencesLink","tableRegionId","querySelector","fromRegionId"],"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 * Module to handle dynamic table features.\n *\n * @module core_table/dynamic\n * @package core_table\n * @copyright 2020 Simey Lameze \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport * as Selectors from 'core_table/local/dynamic/selectors';\nimport Events from './local/dynamic/events';\nimport Pending from 'core/pending';\nimport {addIconToContainer} from 'core/loadingicon';\nimport {fetch as fetchTableData} from 'core_table/local/dynamic/repository';\nimport Notification from 'core/notification';\n\nlet watching = false;\n\n/**\n * Ensure that a table is a dynamic table.\n *\n * @param {HTMLElement} tableRoot\n * @returns {Bool}\n */\nconst checkTableIsDynamic = tableRoot => {\n if (!tableRoot) {\n // The table is not a dynamic table.\n throw new Error(\"The table specified is not a dynamic table and cannot be updated\");\n }\n\n if (!tableRoot.matches(Selectors.main.region)) {\n // The table is not a dynamic table.\n throw new Error(\"The table specified is not a dynamic table and cannot be updated\");\n }\n\n return true;\n};\n\n/**\n * Get the filterset data from a known dynamic table.\n *\n * @param {HTMLElement} tableRoot\n * @returns {Object}\n */\nconst getFiltersetFromTable = tableRoot => {\n return JSON.parse(tableRoot.dataset.tableFilters);\n};\n\n/**\n * Update the specified table based on its current values.\n *\n * @param {HTMLElement} tableRoot\n * @param {Bool} resetContent\n * @returns {Promise}\n */\nexport const refreshTableContent = (tableRoot, resetContent = false) => {\n const filterset = getFiltersetFromTable(tableRoot);\n addIconToContainer(tableRoot);\n\n const pendingPromise = new Pending('core_table/dynamic:refreshTableContent');\n\n return fetchTableData(\n tableRoot.dataset.tableComponent,\n tableRoot.dataset.tableHandler,\n tableRoot.dataset.tableUniqueid,\n {\n sortData: JSON.parse(tableRoot.dataset.tableSortData),\n joinType: filterset.jointype,\n filters: filterset.filters,\n firstinitial: tableRoot.dataset.tableFirstInitial,\n lastinitial: tableRoot.dataset.tableLastInitial,\n pageNumber: tableRoot.dataset.tablePageNumber,\n pageSize: tableRoot.dataset.tablePageSize,\n hiddenColumns: JSON.parse(tableRoot.dataset.tableHiddenColumns),\n },\n resetContent,\n )\n .then(data => {\n const placeholder = document.createElement('div');\n placeholder.innerHTML = data.html;\n tableRoot.replaceWith(...placeholder.childNodes);\n\n // Update the tableRoot.\n return getTableFromId(tableRoot.dataset.tableUniqueid);\n }).then(tableRoot => {\n tableRoot.dispatchEvent(new CustomEvent(Events.tableContentRefreshed, {\n bubbles: true,\n }));\n\n return tableRoot;\n })\n .then(tableRoot => {\n pendingPromise.resolve();\n\n return tableRoot;\n });\n};\n\nexport const updateTable = (tableRoot, {\n sortBy = null,\n sortOrder = null,\n filters = null,\n firstInitial = null,\n lastInitial = null,\n pageNumber = null,\n pageSize = null,\n hiddenColumns = null,\n} = {}, refreshContent = true) => {\n checkTableIsDynamic(tableRoot);\n\n const pendingPromise = new Pending('core_table/dynamic:updateTable');\n let tableConfigChanged = false;\n\n // Update sort fields.\n if (sortBy && sortOrder) {\n // Always update the table if requested and there were sort fields.\n // These fields are only ever normalised in the backend.\n tableConfigChanged = true;\n\n const sortData = JSON.parse(tableRoot.dataset.tableSortData);\n sortData.unshift({\n sortby: sortBy,\n sortorder: parseInt(sortOrder, 10),\n });\n tableRoot.dataset.tableSortData = JSON.stringify(sortData);\n }\n\n // Update initials.\n if (firstInitial !== null) {\n if (tableRoot.dataset.tableFirstInitial !== firstInitial) {\n tableConfigChanged = true;\n }\n\n tableRoot.dataset.tableFirstInitial = firstInitial;\n }\n\n if (lastInitial !== null) {\n if (tableRoot.dataset.tableLastInitial !== lastInitial) {\n tableConfigChanged = true;\n }\n\n tableRoot.dataset.tableLastInitial = lastInitial;\n }\n\n if (pageNumber !== null) {\n if (tableRoot.dataset.tablePageNumber != pageNumber) {\n tableConfigChanged = true;\n }\n\n tableRoot.dataset.tablePageNumber = pageNumber;\n }\n\n if (pageSize !== null) {\n if (tableRoot.dataset.tablePageSize != pageSize) {\n tableConfigChanged = true;\n }\n\n tableRoot.dataset.tablePageSize = pageSize;\n }\n\n // Update filters.\n if (filters) {\n const filterJson = JSON.stringify(filters);\n\n if (tableRoot.dataset.tableFilters !== filterJson) {\n tableConfigChanged = true;\n }\n\n tableRoot.dataset.tableFilters = filterJson;\n }\n\n // Update hidden columns.\n if (hiddenColumns) {\n const columnJson = JSON.stringify(hiddenColumns);\n\n if (tableRoot.dataset.tableHiddenColumns !== columnJson) {\n tableConfigChanged = true;\n }\n\n tableRoot.dataset.tableHiddenColumns = columnJson;\n }\n\n // Refresh.\n if (refreshContent && tableConfigChanged) {\n return refreshTableContent(tableRoot)\n .then(tableRoot => {\n pendingPromise.resolve();\n return tableRoot;\n });\n } else {\n pendingPromise.resolve();\n return Promise.resolve(tableRoot);\n }\n};\n\n/**\n * Get the table dataset for the specified tableRoot, ensuring that the provided table is a dynamic table.\n *\n * @param {HTMLElement} tableRoot\n * @returns {DOMStringMap}\n */\nconst getTableData = tableRoot => {\n checkTableIsDynamic(tableRoot);\n\n return tableRoot.dataset;\n};\n\n/**\n * Update the specified table using the new filters.\n *\n * @param {HTMLElement} tableRoot\n * @param {Object} filters\n * @param {Bool} refreshContent\n * @returns {Promise}\n */\nexport const setFilters = (tableRoot, filters, refreshContent = true) =>\n updateTable(tableRoot, {filters}, refreshContent);\n\n/**\n * Get the filter data for the specified table.\n *\n * @param {HTMLElement} tableRoot\n * @returns {Object}\n */\nexport const getFilters = tableRoot => {\n checkTableIsDynamic(tableRoot);\n\n return getFiltersetFromTable(tableRoot);\n};\n\n/**\n * Update the sort order.\n *\n * @param {HTMLElement} tableRoot\n * @param {String} sortBy\n * @param {Number} sortOrder\n * @param {Bool} refreshContent\n * @returns {Promise}\n */\nexport const setSortOrder = (tableRoot, sortBy, sortOrder, refreshContent = true) =>\n updateTable(tableRoot, {sortBy, sortOrder}, refreshContent);\n\n/**\n * Set the page number.\n *\n * @param {HTMLElement} tableRoot\n * @param {String} pageNumber\n * @param {Bool} refreshContent\n * @returns {Promise}\n */\nexport const setPageNumber = (tableRoot, pageNumber, refreshContent = true) =>\n updateTable(tableRoot, {pageNumber}, refreshContent);\n\n/**\n * Get the current page number.\n *\n * @param {HTMLElement} tableRoot\n * @returns {Number}\n */\nexport const getPageNumber = tableRoot => getTableData(tableRoot).tablePageNumber;\n\n/**\n * Set the page size.\n *\n * @param {HTMLElement} tableRoot\n * @param {Number} pageSize\n * @param {Bool} refreshContent\n * @returns {Promise}\n */\nexport const setPageSize = (tableRoot, pageSize, refreshContent = true) =>\n updateTable(tableRoot, {pageSize, pageNumber: 1}, refreshContent);\n\n/**\n * Get the current page size.\n *\n * @param {HTMLElement} tableRoot\n * @returns {Number}\n */\nexport const getPageSize = tableRoot => getTableData(tableRoot).tablePageSize;\n\n/**\n * Update the first initial to show.\n *\n * @param {HTMLElement} tableRoot\n * @param {String} firstInitial\n * @param {Bool} refreshContent\n * @returns {Promise}\n */\nexport const setFirstInitial = (tableRoot, firstInitial, refreshContent = true) =>\n updateTable(tableRoot, {firstInitial}, refreshContent);\n\n/**\n * Get the current first initial filter.\n *\n * @param {HTMLElement} tableRoot\n * @returns {String}\n */\nexport const getFirstInitial = tableRoot => getTableData(tableRoot).tableFirstInitial;\n\n/**\n * Update the last initial to show.\n *\n * @param {HTMLElement} tableRoot\n * @param {String} lastInitial\n * @param {Bool} refreshContent\n * @returns {Promise}\n */\nexport const setLastInitial = (tableRoot, lastInitial, refreshContent = true) =>\n updateTable(tableRoot, {lastInitial}, refreshContent);\n\n/**\n * Get the current last initial filter.\n *\n * @param {HTMLElement} tableRoot\n * @returns {String}\n */\nexport const getLastInitial = tableRoot => getTableData(tableRoot).tableLastInitial;\n\n/**\n * Hide a column in the participants table.\n *\n * @param {HTMLElement} tableRoot\n * @param {String} columnToHide\n * @param {Bool} refreshContent\n */\nexport const hideColumn = (tableRoot, columnToHide, refreshContent = true) => {\n const hiddenColumns = JSON.parse(tableRoot.dataset.tableHiddenColumns);\n hiddenColumns.push(columnToHide);\n\n updateTable(tableRoot, {hiddenColumns}, refreshContent);\n};\n\n/**\n * Make a hidden column visible in the participants table.\n *\n * @param {HTMLElement} tableRoot\n * @param {String} columnToShow\n * @param {Bool} refreshContent\n */\nexport const showColumn = (tableRoot, columnToShow, refreshContent = true) => {\n let hiddenColumns = JSON.parse(tableRoot.dataset.tableHiddenColumns);\n hiddenColumns = hiddenColumns.filter(columnName => columnName !== columnToShow);\n\n updateTable(tableRoot, {hiddenColumns}, refreshContent);\n};\n\n/**\n * Reset table preferences.\n *\n * @param {HTMLElement} tableRoot\n * @returns {Promise}\n */\nconst resetTablePreferences = tableRoot => refreshTableContent(tableRoot, true);\n\n/**\n * Set up listeners to handle table updates.\n */\nexport const init = () => {\n if (watching) {\n // Already watching.\n return;\n }\n watching = true;\n\n document.addEventListener('click', e => {\n const tableRoot = e.target.closest(Selectors.main.region);\n\n if (!tableRoot) {\n return;\n }\n\n const sortableLink = e.target.closest(Selectors.table.links.sortableColumn);\n if (sortableLink) {\n e.preventDefault();\n\n setSortOrder(tableRoot, sortableLink.dataset.sortby, sortableLink.dataset.sortorder)\n .catch(Notification.exception);\n }\n\n const firstInitialLink = e.target.closest(Selectors.initialsBar.links.firstInitial);\n if (firstInitialLink !== null) {\n e.preventDefault();\n\n setFirstInitial(tableRoot, firstInitialLink.dataset.initial).catch(Notification.exception);\n }\n\n const lastInitialLink = e.target.closest(Selectors.initialsBar.links.lastInitial);\n if (lastInitialLink !== null) {\n e.preventDefault();\n\n setLastInitial(tableRoot, lastInitialLink.dataset.initial).catch(Notification.exception);\n }\n\n const pageItem = e.target.closest(Selectors.paginationBar.links.pageItem);\n if (pageItem) {\n e.preventDefault();\n\n setPageNumber(tableRoot, pageItem.dataset.pageNumber).catch(Notification.exception);\n }\n\n const hide = e.target.closest(Selectors.table.links.hide);\n if (hide) {\n e.preventDefault();\n\n hideColumn(tableRoot, hide.dataset.column).catch(Notification.exception);\n }\n\n const show = e.target.closest(Selectors.table.links.show);\n if (show) {\n e.preventDefault();\n\n showColumn(tableRoot, show.dataset.column).catch(Notification.exception);\n }\n\n const resetTablePreferencesLink = e.target.closest('.resettable a');\n if (resetTablePreferencesLink) {\n e.preventDefault();\n\n resetTablePreferences(tableRoot).catch(Notification.exception);\n }\n });\n};\n\n/**\n * Fetch the table via its table region id.\n *\n * @param {String} tableRegionId\n * @returns {HTMLElement}\n */\nexport const getTableFromId = tableRegionId => {\n const tableRoot = document.querySelector(Selectors.main.fromRegionId(tableRegionId));\n\n\n if (!tableRoot) {\n // The table is not a dynamic table.\n throw new Error(\"The table specified is not a dynamic table and cannot be updated\");\n }\n\n return tableRoot;\n};\n\nexport {\n Events\n};\n"],"file":"dynamic.min.js"} \ No newline at end of file diff --git a/lib/table/amd/src/dynamic.js b/lib/table/amd/src/dynamic.js index b812a1feac9..899809bebab 100644 --- a/lib/table/amd/src/dynamic.js +++ b/lib/table/amd/src/dynamic.js @@ -123,9 +123,14 @@ export const updateTable = (tableRoot, { checkTableIsDynamic(tableRoot); const pendingPromise = new Pending('core_table/dynamic:updateTable'); + let tableConfigChanged = false; // Update sort fields. if (sortBy && sortOrder) { + // Always update the table if requested and there were sort fields. + // These fields are only ever normalised in the backend. + tableConfigChanged = true; + const sortData = JSON.parse(tableRoot.dataset.tableSortData); sortData.unshift({ sortby: sortBy, @@ -136,33 +141,61 @@ export const updateTable = (tableRoot, { // Update initials. if (firstInitial !== null) { + if (tableRoot.dataset.tableFirstInitial !== firstInitial) { + tableConfigChanged = true; + } + tableRoot.dataset.tableFirstInitial = firstInitial; } if (lastInitial !== null) { + if (tableRoot.dataset.tableLastInitial !== lastInitial) { + tableConfigChanged = true; + } + tableRoot.dataset.tableLastInitial = lastInitial; } if (pageNumber !== null) { + if (tableRoot.dataset.tablePageNumber != pageNumber) { + tableConfigChanged = true; + } + tableRoot.dataset.tablePageNumber = pageNumber; } if (pageSize !== null) { + if (tableRoot.dataset.tablePageSize != pageSize) { + tableConfigChanged = true; + } + tableRoot.dataset.tablePageSize = pageSize; } // Update filters. if (filters) { - tableRoot.dataset.tableFilters = JSON.stringify(filters); + const filterJson = JSON.stringify(filters); + + if (tableRoot.dataset.tableFilters !== filterJson) { + tableConfigChanged = true; + } + + tableRoot.dataset.tableFilters = filterJson; } // Update hidden columns. if (hiddenColumns) { - tableRoot.dataset.tableHiddenColumns = JSON.stringify(hiddenColumns); + const columnJson = JSON.stringify(hiddenColumns); + + if (tableRoot.dataset.tableHiddenColumns !== columnJson) { + tableConfigChanged = true; + } + + tableRoot.dataset.tableHiddenColumns = columnJson; } // Refresh. - if (refreshContent) { + if (refreshContent && tableConfigChanged) { return refreshTableContent(tableRoot) .then(tableRoot => { pendingPromise.resolve(); From dd9dea6148cfb96f83a7658eb0be95c20eaf302f Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Tue, 9 Jun 2020 11:01:11 +0800 Subject: [PATCH 2/6] MDL-68977 user: Normalise participant table filterdata --- user/amd/build/local/participantsfilter/filter.min.js | 2 +- .../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 | 2 +- user/amd/src/participantsfilter.js | 9 +++++++-- 6 files changed, 12 insertions(+), 7 deletions(-) diff --git a/user/amd/build/local/participantsfilter/filter.min.js b/user/amd/build/local/participantsfilter/filter.min.js index 0f13ff20617..5c2801e174e 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 * @package core_user\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 // 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 this.filterRoot.querySelector(Selectors.filter.fields.join).value;\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","innerHTML","getSourceDataForFilter","outerHTML","dataSource","querySelector","forEach","filterValue","selectedOption","selected","showSuggestions","document","createElement","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 * @package core_user\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 // 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 60a726d6039..0e6d74a2f04 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","./local/participantsfilter/selectors","core/templates"],function(a,b,c,d,e,f,g,h){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=k(b);c=j(c);d=k(d);f=k(f);g=k(g);h=k(h);var x="undefined"!=typeof window?window:"undefined"!=typeof self?self:"undefined"!=typeof global?global:{};function i(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;i=function(){return a};return a}function j(a){if(a&&a.__esModule){return a}if(null===a||"object"!==_typeof(a)&&"function"!=typeof a){return{default:a}}var b=i();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 k(a){return a&&a.__esModule?a:{default:a}}function l(a){return o(a)||n(a)||r(a)||m()}function m(){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 n(a){if("undefined"!=typeof Symbol&&Symbol.iterator in Object(a))return Array.from(a)}function o(a){if(Array.isArray(a))return s(a)}function p(a,b){return u(a)||t(a,b)||r(a,b)||q()}function q(){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 r(a,b){if(!a)return;if("string"==typeof a)return s(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 s(a,b)}function s(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 * @package core_user\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 Selectors from './local/participantsfilter/selectors';\nimport Templates from 'core/templates';\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 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 .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 */\n const removeOrReplaceFilterRow = filterRow => {\n const filterCount = getFilterRegion().querySelectorAll(Selectors.filter.region).length;\n\n if (filterCount === 1) {\n replaceFilterRow(filterRow);\n } else {\n removeFilterRow(filterRow);\n }\n };\n\n /**\n * Remove the specified filter row and associated class.\n *\n * @param {HTMLElement} filterRow\n */\n const removeFilterRow = async filterRow => {\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 // Refresh the table.\n updateTableFromFilter();\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 {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, 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 updateTableFromFilter();\n\n return filterRow;\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 filters = getFilterRegion().querySelectorAll(Selectors.filter.region);\n filters.forEach(filterRow => removeOrReplaceFilterRow(filterRow));\n\n // Refresh the table.\n return updateTableFromFilter();\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);\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 = 1;\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 */\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;\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 Promise.resolve();\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 Promise.resolve();\n }\n\n return addFilterRow().then(([filterRow]) => addFilter(filterRow, filterType, filterValues));\n });\n\n Promise.all(filterPromises).then(() => {\n return removeEmptyFilters();\n })\n .then(updateFiltersOptions)\n .then(updateTableFromFilter)\n .catch();\n };\n\n /**\n * Update the Dynamic table based upon the current filter.\n *\n * @return {Promise}\n */\n const updateTableFromFilter = () => {\n return DynamicTable.setFilters(\n DynamicTable.getTableFromId(filterSet.dataset.tableRegion),\n {\n filters: Object.values(activeFilters).map(filter => filter.filterValue),\n jointype: filterSet.querySelector(Selectors.filterset.fields.join).value,\n }\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));\n }\n });\n\n // Add listeners for the filter type selection.\n filterSet.querySelector(Selectors.filterset.regions.filterlist).addEventListener('change', 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 // Apply the initial filter configuration.\n setFilterFromConfig(initialFilters);\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","rownum","querySelectorAll","filter","region","length","Templates","renderForPromise","then","html","js","newContentNodes","appendNodeContents","filterRow","typeList","data","forEach","contentNode","contentTypeList","fields","type","innerHTML","updateFiltersOptions","catch","Notification","exception","getFilterDataSource","filterType","filterDataNode","datasource","byName","addFilter","initialFilterValues","dataset","Filter","GenericFilter","filterTypeClass","typeField","value","disabled","getFilterObject","name","removeOrReplaceFilterRow","filterCount","replaceFilterRow","removeFilterRow","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","setFilterFromConfig","config","filterConfig","Object","entries","jointype","filterPromises","map","filterData","Promise","resolve","filterValues","values","filterValue","DynamicTable","setFilters","getTableFromId","tableRegion","parseInt","maxFilters","typeListSelect","requests","Array","_","rowIndex","push","fetchedStrings","legendStrings","addEventListener","e","target","closest","preventDefault","applyFilters","resetFilters","filterverb","tableRoot","initialFilters","getFilters"],"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 * Participants filter managemnet.\n *\n * @module core_user/participants_filter\n * @package core_user\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 Selectors from './local/participantsfilter/selectors';\nimport Templates from 'core/templates';\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 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 .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 */\n const removeOrReplaceFilterRow = filterRow => {\n const filterCount = getFilterRegion().querySelectorAll(Selectors.filter.region).length;\n\n if (filterCount === 1) {\n replaceFilterRow(filterRow);\n } else {\n removeFilterRow(filterRow);\n }\n };\n\n /**\n * Remove the specified filter row and associated class.\n *\n * @param {HTMLElement} filterRow\n */\n const removeFilterRow = async filterRow => {\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 // Refresh the table.\n updateTableFromFilter();\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 {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, 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 updateTableFromFilter();\n\n return filterRow;\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 filters = getFilterRegion().querySelectorAll(Selectors.filter.region);\n filters.forEach(filterRow => removeOrReplaceFilterRow(filterRow));\n\n // Refresh the table.\n return updateTableFromFilter();\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);\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 = 1;\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 */\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;\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 Promise.resolve();\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 Promise.resolve();\n }\n\n return addFilterRow().then(([filterRow]) => addFilter(filterRow, filterType, filterValues));\n });\n\n Promise.all(filterPromises).then(() => {\n return removeEmptyFilters();\n })\n .then(updateFiltersOptions)\n .then(updateTableFromFilter)\n .catch();\n };\n\n /**\n * Update the Dynamic table based upon the current filter.\n *\n * @return {Promise}\n */\n const updateTableFromFilter = () => {\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 .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));\n }\n });\n\n // Add listeners for the filter type selection.\n filterSet.querySelector(Selectors.filterset.regions.filterlist).addEventListener('change', 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 // Apply the initial filter configuration.\n setFilterFromConfig(initialFilters);\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 6670c2efe20..445dd3a3125 100644 --- a/user/amd/src/local/participantsfilter/filter.js +++ b/user/amd/src/local/participantsfilter/filter.js @@ -184,7 +184,7 @@ export default class { * @returns {Number} */ get jointype() { - return this.filterRoot.querySelector(Selectors.filter.fields.join).value; + return parseInt(this.filterRoot.querySelector(Selectors.filter.fields.join).value, 10); } /** diff --git a/user/amd/src/participantsfilter.js b/user/amd/src/participantsfilter.js index 4e31a1590f9..38087537b78 100644 --- a/user/amd/src/participantsfilter.js +++ b/user/amd/src/participantsfilter.js @@ -365,11 +365,16 @@ export const init = participantsRegionId => { * @return {Promise} */ const updateTableFromFilter = () => { + const filters = {}; + Object.values(activeFilters).forEach(filter => { + filters[filter.filterValue.name] = filter.filterValue; + }); + return DynamicTable.setFilters( DynamicTable.getTableFromId(filterSet.dataset.tableRegion), { - filters: Object.values(activeFilters).map(filter => filter.filterValue), - jointype: filterSet.querySelector(Selectors.filterset.fields.join).value, + jointype: parseInt(filterSet.querySelector(Selectors.filterset.fields.join).value, 10), + filters, } ) .catch(Notification.exception); From 8503e0ee53bdab6751a1fab27cfc5b26a4923398 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Tue, 9 Jun 2020 10:57:06 +0800 Subject: [PATCH 3/6] MDL-68977 user: Do not refresh participants table on load --- user/amd/build/participantsfilter.min.js | 2 +- user/amd/build/participantsfilter.min.js.map | 2 +- user/amd/src/participantsfilter.js | 19 ++++++++++++++----- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/user/amd/build/participantsfilter.min.js b/user/amd/build/participantsfilter.min.js index 0e6d74a2f04..2a5efd0d34a 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","./local/participantsfilter/selectors","core/templates"],function(a,b,c,d,e,f,g,h){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=k(b);c=j(c);d=k(d);f=k(f);g=k(g);h=k(h);var x="undefined"!=typeof window?window:"undefined"!=typeof self?self:"undefined"!=typeof global?global:{};function i(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;i=function(){return a};return a}function j(a){if(a&&a.__esModule){return a}if(null===a||"object"!==_typeof(a)&&"function"!=typeof a){return{default:a}}var b=i();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 k(a){return a&&a.__esModule?a:{default:a}}function l(a){return o(a)||n(a)||r(a)||m()}function m(){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 n(a){if("undefined"!=typeof Symbol&&Symbol.iterator in Object(a))return Array.from(a)}function o(a){if(Array.isArray(a))return s(a)}function p(a,b){return u(a)||t(a,b)||r(a,b)||q()}function q(){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 r(a,b){if(!a)return;if("string"==typeof a)return s(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 s(a,b)}function s(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 * @package core_user\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 Selectors from './local/participantsfilter/selectors';\nimport Templates from 'core/templates';\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 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 .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 */\n const removeOrReplaceFilterRow = filterRow => {\n const filterCount = getFilterRegion().querySelectorAll(Selectors.filter.region).length;\n\n if (filterCount === 1) {\n replaceFilterRow(filterRow);\n } else {\n removeFilterRow(filterRow);\n }\n };\n\n /**\n * Remove the specified filter row and associated class.\n *\n * @param {HTMLElement} filterRow\n */\n const removeFilterRow = async filterRow => {\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 // Refresh the table.\n updateTableFromFilter();\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 {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, 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 updateTableFromFilter();\n\n return filterRow;\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 filters = getFilterRegion().querySelectorAll(Selectors.filter.region);\n filters.forEach(filterRow => removeOrReplaceFilterRow(filterRow));\n\n // Refresh the table.\n return updateTableFromFilter();\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);\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 = 1;\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 */\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;\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 Promise.resolve();\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 Promise.resolve();\n }\n\n return addFilterRow().then(([filterRow]) => addFilter(filterRow, filterType, filterValues));\n });\n\n Promise.all(filterPromises).then(() => {\n return removeEmptyFilters();\n })\n .then(updateFiltersOptions)\n .then(updateTableFromFilter)\n .catch();\n };\n\n /**\n * Update the Dynamic table based upon the current filter.\n *\n * @return {Promise}\n */\n const updateTableFromFilter = () => {\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 .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));\n }\n });\n\n // Add listeners for the filter type selection.\n filterSet.querySelector(Selectors.filterset.regions.filterlist).addEventListener('change', 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 // Apply the initial filter configuration.\n setFilterFromConfig(initialFilters);\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","rownum","querySelectorAll","filter","region","length","Templates","renderForPromise","then","html","js","newContentNodes","appendNodeContents","filterRow","typeList","data","forEach","contentNode","contentTypeList","fields","type","innerHTML","updateFiltersOptions","catch","Notification","exception","getFilterDataSource","filterType","filterDataNode","datasource","byName","addFilter","initialFilterValues","dataset","Filter","GenericFilter","filterTypeClass","typeField","value","disabled","getFilterObject","name","removeOrReplaceFilterRow","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","setFilterFromConfig","config","filterConfig","Object","entries","jointype","filterPromises","map","filterData","filterValues","values","promise","Promise","filterValue","DynamicTable","setFilters","getTableFromId","tableRegion","parseInt","maxFilters","typeListSelect","requests","Array","_","rowIndex","push","fetchedStrings","legendStrings","addEventListener","e","target","closest","preventDefault","applyFilters","resetFilters","filterverb","tableRoot","initialFilters","getFilters"],"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 * Participants filter managemnet.\n *\n * @module core_user/participants_filter\n * @package core_user\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 Selectors from './local/participantsfilter/selectors';\nimport Templates from 'core/templates';\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 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 .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 */\n const removeOrReplaceFilterRow = filterRow => {\n const filterCount = getFilterRegion().querySelectorAll(Selectors.filter.region).length;\n\n if (filterCount === 1) {\n replaceFilterRow(filterRow);\n } else {\n removeFilterRow(filterRow);\n }\n };\n\n /**\n * Remove the specified filter row and associated class.\n *\n * @param {HTMLElement} filterRow\n */\n const removeFilterRow = async filterRow => {\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) {\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 {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, 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 updateTableFromFilter();\n\n return filterRow;\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 filters = getFilterRegion().querySelectorAll(Selectors.filter.region);\n filters.forEach(filterRow => removeOrReplaceFilterRow(filterRow));\n\n // Refresh the table.\n return updateTableFromFilter();\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);\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 = 1;\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 */\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;\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;\n }\n\n Promise.all(filterPromises).then(() => {\n return removeEmptyFilters();\n })\n .then(updateFiltersOptions)\n .then(updateTableFromFilter)\n .catch();\n };\n\n /**\n * Update the Dynamic table based upon the current filter.\n *\n * @return {Promise}\n */\n const updateTableFromFilter = () => {\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 .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));\n }\n });\n\n // Add listeners for the filter type selection.\n filterSet.querySelector(Selectors.filterset.regions.filterlist).addEventListener('change', 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 // Apply the initial filter configuration.\n setFilterFromConfig(initialFilters);\n }\n};\n"],"file":"participantsfilter.min.js"} \ No newline at end of file diff --git a/user/amd/src/participantsfilter.js b/user/amd/src/participantsfilter.js index 38087537b78..482466ed5ce 100644 --- a/user/amd/src/participantsfilter.js +++ b/user/amd/src/participantsfilter.js @@ -165,6 +165,9 @@ export const init = participantsRegionId => { * @param {HTMLElement} filterRow */ const removeFilterRow = async filterRow => { + const filterType = filterRow.querySelector(Selectors.filter.fields.type); + const hasFilterValue = !!filterType.value; + // Remove the filter object. removeFilterObject(filterRow.dataset.filterType); @@ -174,8 +177,10 @@ export const init = participantsRegionId => { // Update the list of available filter types. updateFiltersOptions(); - // Refresh the table. - updateTableFromFilter(); + if (hasFilterValue) { + // Refresh the table if there was any content in this row. + updateTableFromFilter(); + } // Update filter fieldset legends. const filterLegends = await getAvailableFilterLegends(); @@ -337,7 +342,7 @@ export const init = participantsRegionId => { const filterPromises = filterConfig.map(([filterType, filterData]) => { if (filterType === 'courseid') { // The courseid is a special case. - return Promise.resolve(); + return false; } const filterValues = filterData.values; @@ -345,11 +350,15 @@ export const init = participantsRegionId => { if (!filterValues.length) { // There are no values for this filter. // Skip it. - return Promise.resolve(); + return false; } return addFilterRow().then(([filterRow]) => addFilter(filterRow, filterType, filterValues)); - }); + }).filter(promise => promise); + + if (!filterPromises.length) { + return; + } Promise.all(filterPromises).then(() => { return removeEmptyFilters(); From cb2f67252555053651d48e26ca9dbbbc696482e5 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Tue, 9 Jun 2020 11:13:00 +0800 Subject: [PATCH 4/6] MDL-68977 user: Add pendingPromise to interactions --- user/amd/build/participantsfilter.min.js | 2 +- user/amd/build/participantsfilter.min.js.map | 2 +- user/amd/src/participantsfilter.js | 39 ++++++++++++++++---- 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/user/amd/build/participantsfilter.min.js b/user/amd/build/participantsfilter.min.js index 2a5efd0d34a..7816ca5f79e 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","./local/participantsfilter/selectors","core/templates"],function(a,b,c,d,e,f,g,h){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=k(b);c=j(c);d=k(d);f=k(f);g=k(g);h=k(h);var x="undefined"!=typeof window?window:"undefined"!=typeof self?self:"undefined"!=typeof global?global:{};function i(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;i=function(){return a};return a}function j(a){if(a&&a.__esModule){return a}if(null===a||"object"!==_typeof(a)&&"function"!=typeof a){return{default:a}}var b=i();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 k(a){return a&&a.__esModule?a:{default:a}}function l(a){return o(a)||n(a)||r(a)||m()}function m(){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 n(a){if("undefined"!=typeof Symbol&&Symbol.iterator in Object(a))return Array.from(a)}function o(a){if(Array.isArray(a))return s(a)}function p(a,b){return u(a)||t(a,b)||r(a,b)||q()}function q(){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 r(a,b){if(!a)return;if("string"==typeof a)return s(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 s(a,b)}function s(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 * @package core_user\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 Selectors from './local/participantsfilter/selectors';\nimport Templates from 'core/templates';\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 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 .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 */\n const removeOrReplaceFilterRow = filterRow => {\n const filterCount = getFilterRegion().querySelectorAll(Selectors.filter.region).length;\n\n if (filterCount === 1) {\n replaceFilterRow(filterRow);\n } else {\n removeFilterRow(filterRow);\n }\n };\n\n /**\n * Remove the specified filter row and associated class.\n *\n * @param {HTMLElement} filterRow\n */\n const removeFilterRow = async filterRow => {\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) {\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 {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, 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 updateTableFromFilter();\n\n return filterRow;\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 filters = getFilterRegion().querySelectorAll(Selectors.filter.region);\n filters.forEach(filterRow => removeOrReplaceFilterRow(filterRow));\n\n // Refresh the table.\n return updateTableFromFilter();\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);\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 = 1;\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 */\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;\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;\n }\n\n Promise.all(filterPromises).then(() => {\n return removeEmptyFilters();\n })\n .then(updateFiltersOptions)\n .then(updateTableFromFilter)\n .catch();\n };\n\n /**\n * Update the Dynamic table based upon the current filter.\n *\n * @return {Promise}\n */\n const updateTableFromFilter = () => {\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 .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));\n }\n });\n\n // Add listeners for the filter type selection.\n filterSet.querySelector(Selectors.filterset.regions.filterlist).addEventListener('change', 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 // Apply the initial filter configuration.\n setFilterFromConfig(initialFilters);\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","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","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","filterverb","tableRoot","initialFilters","getFilters","initialFilterPromise"],"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 * Participants filter managemnet.\n *\n * @module core_user/participants_filter\n * @package core_user\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';\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 */\n const removeOrReplaceFilterRow = filterRow => {\n const filterCount = getFilterRegion().querySelectorAll(Selectors.filter.region).length;\n\n if (filterCount === 1) {\n replaceFilterRow(filterRow);\n } else {\n removeFilterRow(filterRow);\n }\n };\n\n /**\n * Remove the specified filter row and associated class.\n *\n * @param {HTMLElement} filterRow\n */\n const removeFilterRow = async filterRow => {\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) {\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 {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, 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 updateTableFromFilter();\n\n return filterRow;\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));\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);\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 = 1;\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));\n }\n });\n\n // Add listeners for the filter type selection.\n filterSet.querySelector(Selectors.filterset.regions.filterlist).addEventListener('change', 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/participantsfilter.js b/user/amd/src/participantsfilter.js index 482466ed5ce..ca2afc356be 100644 --- a/user/amd/src/participantsfilter.js +++ b/user/amd/src/participantsfilter.js @@ -27,6 +27,7 @@ import * as DynamicTable from 'core_table/dynamic'; import GenericFilter from './local/participantsfilter/filter'; import {get_strings as getStrings} from 'core/str'; import Notification from 'core/notification'; +import Pending from 'core/pending'; import Selectors from './local/participantsfilter/selectors'; import Templates from 'core/templates'; @@ -57,6 +58,8 @@ export const init = participantsRegionId => { * @return {Promise} */ const addFilterRow = () => { + const pendingPromise = new Pending('core_user/participantsfilter:addFilterRow'); + const rownum = 1 + getFilterRegion().querySelectorAll(Selectors.filter.region).length; return Templates.renderForPromise('core_user/local/participantsfilter/filterrow', {"rownumber": rownum}) .then(({html, js}) => { @@ -86,6 +89,11 @@ export const init = participantsRegionId => { return filterRow; }) + .then(result => { + pendingPromise.resolve(); + + return result; + }) .catch(Notification.exception); }; @@ -262,11 +270,18 @@ export const init = participantsRegionId => { * @returns {Promise} */ const removeAllFilters = () => { + const pendingPromise = new Pending('core_user/participantsfilter:setFilterFromConfig'); + const filters = getFilterRegion().querySelectorAll(Selectors.filter.region); filters.forEach(filterRow => removeOrReplaceFilterRow(filterRow)); // Refresh the table. - return updateTableFromFilter(); + return updateTableFromFilter() + .then(result => { + pendingPromise.resolve(); + + return result; + }); }; /** @@ -327,13 +342,14 @@ export const init = participantsRegionId => { * @param {Object} config * @param {Number} config.jointype * @param {Object} config.filters + * @returns {Promise} */ const setFilterFromConfig = config => { const filterConfig = Object.entries(config.filters); if (!filterConfig.length) { // There are no filters to set from. - return; + return Promise.resolve(); } // Set the main join type. @@ -357,15 +373,14 @@ export const init = participantsRegionId => { }).filter(promise => promise); if (!filterPromises.length) { - return; + return Promise.resolve(); } - Promise.all(filterPromises).then(() => { + return Promise.all(filterPromises).then(() => { return removeEmptyFilters(); }) .then(updateFiltersOptions) - .then(updateTableFromFilter) - .catch(); + .then(updateTableFromFilter); }; /** @@ -374,6 +389,8 @@ export const init = participantsRegionId => { * @return {Promise} */ const updateTableFromFilter = () => { + const pendingPromise = new Pending('core_user/participantsfilter:updateTableFromFilter'); + const filters = {}; Object.values(activeFilters).forEach(filter => { filters[filter.filterValue.name] = filter.filterValue; @@ -386,6 +403,11 @@ export const init = participantsRegionId => { filters, } ) + .then(result => { + pendingPromise.resolve(); + + return result; + }) .catch(Notification.exception); }; @@ -463,7 +485,10 @@ export const init = participantsRegionId => { const tableRoot = DynamicTable.getTableFromId(filterSet.dataset.tableRegion); const initialFilters = DynamicTable.getFilters(tableRoot); if (initialFilters) { + const initialFilterPromise = new Pending('core_user/participantsfilter:setFilterFromConfig'); // Apply the initial filter configuration. - setFilterFromConfig(initialFilters); + setFilterFromConfig(initialFilters) + .then(() => initialFilterPromise.resolve()) + .catch(); } }; From 7b3026fabefc0a5cca6a4801810b4382deafc706 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Tue, 9 Jun 2020 15:15:24 +0800 Subject: [PATCH 5/6] MDL-68977 core_user: Refresh table after removing all filters Previously the update function was called for each row as it was removed. This patch changes the behaviouro to only remove one row at at ime. --- user/amd/build/participantsfilter.min.js | 2 +- user/amd/build/participantsfilter.min.js.map | 2 +- user/amd/src/participantsfilter.js | 29 ++++++++++++-------- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/user/amd/build/participantsfilter.min.js b/user/amd/build/participantsfilter.min.js index 7816ca5f79e..9a8b39021aa 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"],function(a,b,c,d,e,f,g,h,i){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=l(b);c=k(c);d=l(d);f=l(f);g=l(g);h=l(h);i=l(i);var y="undefined"!=typeof window?window:"undefined"!=typeof self?self:"undefined"!=typeof global?global:{};function j(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;j=function(){return a};return a}function k(a){if(a&&a.__esModule){return a}if(null===a||"object"!==_typeof(a)&&"function"!=typeof a){return{default:a}}var b=j();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 l(a){return a&&a.__esModule?a:{default:a}}function m(a){return p(a)||o(a)||s(a)||n()}function n(){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 o(a){if("undefined"!=typeof Symbol&&Symbol.iterator in Object(a))return Array.from(a)}function p(a){if(Array.isArray(a))return t(a)}function q(a,b){return v(a)||u(a,b)||s(a,b)||r()}function r(){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 s(a,b){if(!a)return;if("string"==typeof a)return t(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 t(a,b)}function t(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 * @package core_user\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';\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 */\n const removeOrReplaceFilterRow = filterRow => {\n const filterCount = getFilterRegion().querySelectorAll(Selectors.filter.region).length;\n\n if (filterCount === 1) {\n replaceFilterRow(filterRow);\n } else {\n removeFilterRow(filterRow);\n }\n };\n\n /**\n * Remove the specified filter row and associated class.\n *\n * @param {HTMLElement} filterRow\n */\n const removeFilterRow = async filterRow => {\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) {\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 {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, 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 updateTableFromFilter();\n\n return filterRow;\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));\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);\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 = 1;\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));\n }\n });\n\n // Add listeners for the filter type selection.\n filterSet.querySelector(Selectors.filterset.regions.filterlist).addEventListener('change', 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","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","filterverb","tableRoot","initialFilters","getFilters","initialFilterPromise"],"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 * Participants filter managemnet.\n *\n * @module core_user/participants_filter\n * @package core_user\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';\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 = 1;\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 filterSet.querySelector(Selectors.filterset.regions.filterlist).addEventListener('change', 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/participantsfilter.js b/user/amd/src/participantsfilter.js index ca2afc356be..539909a756d 100644 --- a/user/amd/src/participantsfilter.js +++ b/user/amd/src/participantsfilter.js @@ -156,14 +156,15 @@ export const init = participantsRegionId => { * that it is replaced instead of being removed. * * @param {HTMLElement} filterRow + * @param {Bool} refreshContent Whether to refresh the table content when removing */ - const removeOrReplaceFilterRow = filterRow => { + const removeOrReplaceFilterRow = (filterRow, refreshContent) => { const filterCount = getFilterRegion().querySelectorAll(Selectors.filter.region).length; if (filterCount === 1) { - replaceFilterRow(filterRow); + replaceFilterRow(filterRow, refreshContent); } else { - removeFilterRow(filterRow); + removeFilterRow(filterRow, refreshContent); } }; @@ -171,8 +172,9 @@ export const init = participantsRegionId => { * Remove the specified filter row and associated class. * * @param {HTMLElement} filterRow + * @param {Bool} refreshContent Whether to refresh the table content when removing */ - const removeFilterRow = async filterRow => { + const removeFilterRow = async(filterRow, refreshContent = true) => { const filterType = filterRow.querySelector(Selectors.filter.fields.type); const hasFilterValue = !!filterType.value; @@ -185,7 +187,7 @@ export const init = participantsRegionId => { // Update the list of available filter types. updateFiltersOptions(); - if (hasFilterValue) { + if (hasFilterValue && refreshContent) { // Refresh the table if there was any content in this row. updateTableFromFilter(); } @@ -203,10 +205,11 @@ export const init = participantsRegionId => { * Replace the specified filter row with a new one. * * @param {HTMLElement} filterRow + * @param {Bool} refreshContent Whether to refresh the table content when removing * @param {Number} rowNum The number used to label the filter fieldset legend (eg Row 1). Defaults to 1 (the first filter). * @return {Promise} */ - const replaceFilterRow = (filterRow, rowNum = 1) => { + const replaceFilterRow = (filterRow, refreshContent = true, rowNum = 1) => { // Remove the filter object. removeFilterObject(filterRow.dataset.filterType); @@ -240,9 +243,11 @@ export const init = participantsRegionId => { }) .then(filterRow => { // Refresh the table. - updateTableFromFilter(); - - return filterRow; + if (refreshContent) { + return updateTableFromFilter(); + } else { + return filterRow; + } }) .catch(Notification.exception); }; @@ -273,7 +278,7 @@ export const init = participantsRegionId => { const pendingPromise = new Pending('core_user/participantsfilter:setFilterFromConfig'); const filters = getFilterRegion().querySelectorAll(Selectors.filter.region); - filters.forEach(filterRow => removeOrReplaceFilterRow(filterRow)); + filters.forEach(filterRow => removeOrReplaceFilterRow(filterRow, false)); // Refresh the table. return updateTableFromFilter() @@ -292,7 +297,7 @@ export const init = participantsRegionId => { filters.forEach(filterRow => { const filterType = filterRow.querySelector(Selectors.filter.fields.type); if (!filterType.value) { - removeOrReplaceFilterRow(filterRow); + removeOrReplaceFilterRow(filterRow, false); } }); }; @@ -464,7 +469,7 @@ export const init = participantsRegionId => { if (e.target.closest(Selectors.filter.actions.remove)) { e.preventDefault(); - removeOrReplaceFilterRow(e.target.closest(Selectors.filter.region)); + removeOrReplaceFilterRow(e.target.closest(Selectors.filter.region), true); } }); From a9075379d86757620069baf99f2edcfcdeb9b597 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Wed, 10 Jun 2020 08:16:03 +0800 Subject: [PATCH 6/6] MDL-68977 enrol_manual: Add pending promises to enrol modal --- enrol/manual/amd/build/quickenrolment.min.js | 2 +- enrol/manual/amd/build/quickenrolment.min.js.map | 2 +- enrol/manual/amd/src/quickenrolment.js | 5 +++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/enrol/manual/amd/build/quickenrolment.min.js b/enrol/manual/amd/build/quickenrolment.min.js index a451f2b7b18..02411ec7ef8 100644 --- a/enrol/manual/amd/build/quickenrolment.min.js +++ b/enrol/manual/amd/build/quickenrolment.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 ("enrol_manual/quickenrolment",["exports","core_table/dynamic","core/str","core/toast","core/config","core/fragment","core/modal_events","core/modal_factory","core/notification","jquery","core/prefetch"],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=n(c);d=n(d);e=l(e);f=l(f);g=l(g);h=l(h);i=l(i);j=l(j);k=l(k);function l(a){return a&&a.__esModule?a:{default:a}}function m(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;m=function(){return a};return a}function n(a){if(a&&a.__esModule){return a}if(null===a||"object"!==_typeof(a)&&"function"!=typeof a){return{default:a}}var b=m();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 o(a,b){return t(a)||s(a,b)||q(a,b)||p()}function p(){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 q(a,b){if(!a)return;if("string"==typeof a)return r(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 r(a,b)}function r(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 * Quick enrolment AMD module.\n *\n * @module enrol_manual/quickenrolment\n * @copyright 2016 Damyon Wiese \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport * as DynamicTable from 'core_table/dynamic';\nimport * as Str from 'core/str';\nimport * as Toast from 'core/toast';\nimport Config from 'core/config';\nimport Fragment from 'core/fragment';\nimport ModalEvents from 'core/modal_events';\nimport ModalFactory from 'core/modal_factory';\nimport Notification from 'core/notification';\nimport jQuery from 'jquery';\nimport Prefetch from 'core/prefetch';\n\nconst Selectors = {\n cohortSelector: \"#id_cohortlist\",\n triggerButtons: \".enrolusersbutton.enrol_manual_plugin [type='submit']\",\n unwantedHiddenFields: \"input[value='_qf__force_multiselect_submission']\",\n buttonWrapper: '[data-region=\"wrapper\"]',\n};\n\n/**\n * Get the content of the body for the specified context.\n *\n * @param {Number} contextId\n * @returns {Promise}\n */\nconst getBodyForContext = contextId => {\n return Fragment.loadFragment('enrol_manual', 'enrol_users_form', contextId, {});\n};\n\n/**\n * Get the dynamic table for the button.\n *\n * @param {HTMLElement} element\n * @returns {HTMLElement}\n */\nconst getDynamicTableForElement = element => {\n const wrapper = element.closest(Selectors.buttonWrapper);\n\n return DynamicTable.getTableFromId(wrapper.dataset.tableUniqueid);\n};\n\n/**\n * Register the event listeners for this contextid.\n *\n * @param {Number} contextId\n */\nconst registerEventListeners = contextId => {\n document.addEventListener('click', e => {\n if (e.target.closest(Selectors.triggerButtons)) {\n e.preventDefault();\n\n showModal(getDynamicTableForElement(e.target), contextId);\n\n return;\n }\n });\n};\n\n/**\n * Display the modal for this contextId.\n *\n * @param {HTMLElement} dynamicTable The table to beb refreshed when changes are made\n * @param {Number} contextId\n * @returns {Promise}\n */\nconst showModal = (dynamicTable, contextId) => {\n return ModalFactory.create({\n type: ModalFactory.types.SAVE_CANCEL,\n large: true,\n title: Str.get_string('enrolusers', 'enrol_manual'),\n body: getBodyForContext(contextId),\n })\n .then(modal => {\n modal.getRoot().on(ModalEvents.save, e => {\n // Trigger a form submission, so that any mform elements can do final tricks before the form submission\n // is processed.\n // The actual submit even tis captured in the next handler.\n\n e.preventDefault();\n modal.getRoot().find('form').submit();\n });\n\n modal.getRoot().on('submit', 'form', e => {\n e.preventDefault();\n\n submitFormAjax(dynamicTable, modal);\n });\n\n modal.getRoot().on(ModalEvents.hidden, () => {\n modal.destroy();\n });\n\n return modal;\n })\n .then(modal => {\n modal.show();\n\n return modal;\n })\n .then(modal => {\n modal.setSaveButtonText(Str.get_string('enrolusers', 'enrol_manual'));\n\n modal.getBodyPromise().then(body => {\n if (body.get(0).querySelector(Selectors.cohortSelector)) {\n modal.setSaveButtonText(Str.get_string('enroluserscohorts', 'enrol_manual'));\n }\n\n return body;\n })\n .catch();\n\n return modal;\n })\n .catch(Notification.exception);\n};\n\n/**\n * Submit the form via ajax.\n *\n * @param {HTMLElement} dynamicTable\n * @param {Object} modal\n */\nconst submitFormAjax = (dynamicTable, modal) => {\n // Note: We use a jQuery object here so that we can use its serialize functionality.\n const form = modal.getRoot().find('form');\n\n // Before send the data through AJAX, we need to parse and remove some unwanted hidden fields.\n // This hidden fields are added automatically by mforms and when it reaches the AJAX we get an error.\n form.get(0).querySelectorAll(Selectors.unwantedHiddenFields).forEach(hiddenField => hiddenField.remove());\n\n modal.hide();\n modal.destroy();\n\n jQuery.ajax(\n `${Config.wwwroot}/enrol/manual/ajax.php?${form.serialize()}`,\n {\n type: 'GET',\n processData: false,\n contentType: \"application/json\",\n }\n )\n .then(response => {\n if (response.error) {\n throw new Error(response.error);\n }\n\n return response.count;\n })\n .then(count => {\n return Promise.all([\n Str.get_string('totalenrolledusers', 'enrol', count),\n DynamicTable.refreshTableContent(dynamicTable),\n ]);\n })\n .then(([notificationBody]) => notificationBody)\n .then(notificationBody => Toast.add(notificationBody))\n .catch(error => {\n Notification.addNotification({\n message: error.message,\n type: 'error',\n });\n });\n};\n\n/**\n * Set up quick enrolment for the manual enrolment plugin.\n *\n * @param {Number} contextid The context id to setup for\n */\nexport const init = ({contextid}) => {\n registerEventListeners(contextid);\n\n Prefetch.prefetchStrings('enrol_manual', [\n 'enrolusers',\n 'enroluserscohorts',\n ]);\n\n Prefetch.prefetchString('enrol', 'totalenrolledusers');\n};\n"],"file":"quickenrolment.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/quickenrolment.js"],"names":["Selectors","cohortSelector","triggerButtons","unwantedHiddenFields","buttonWrapper","getBodyForContext","contextId","Fragment","loadFragment","getDynamicTableForElement","element","wrapper","closest","DynamicTable","getTableFromId","dataset","tableUniqueid","registerEventListeners","document","addEventListener","e","target","preventDefault","showModal","dynamicTable","pendingPromise","Pending","ModalFactory","create","type","types","SAVE_CANCEL","large","title","Str","get_string","body","then","modal","getRoot","on","ModalEvents","save","find","submit","submitFormAjax","hidden","destroy","show","setSaveButtonText","getBodyPromise","get","querySelector","catch","resolve","Notification","exception","form","querySelectorAll","forEach","hiddenField","remove","hide","jQuery","ajax","Config","wwwroot","serialize","processData","contentType","response","error","Error","count","Promise","all","refreshTableContent","notificationBody","Toast","add","addNotification","message","init","contextid","Prefetch","prefetchStrings","prefetchString"],"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 * Quick enrolment AMD module.\n *\n * @module enrol_manual/quickenrolment\n * @copyright 2016 Damyon Wiese \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport * as DynamicTable from 'core_table/dynamic';\nimport * as Str from 'core/str';\nimport * as Toast from 'core/toast';\nimport Config from 'core/config';\nimport Fragment from 'core/fragment';\nimport ModalEvents from 'core/modal_events';\nimport ModalFactory from 'core/modal_factory';\nimport Notification from 'core/notification';\nimport jQuery from 'jquery';\nimport Pending from 'core/pending';\nimport Prefetch from 'core/prefetch';\n\nconst Selectors = {\n cohortSelector: \"#id_cohortlist\",\n triggerButtons: \".enrolusersbutton.enrol_manual_plugin [type='submit']\",\n unwantedHiddenFields: \"input[value='_qf__force_multiselect_submission']\",\n buttonWrapper: '[data-region=\"wrapper\"]',\n};\n\n/**\n * Get the content of the body for the specified context.\n *\n * @param {Number} contextId\n * @returns {Promise}\n */\nconst getBodyForContext = contextId => {\n return Fragment.loadFragment('enrol_manual', 'enrol_users_form', contextId, {});\n};\n\n/**\n * Get the dynamic table for the button.\n *\n * @param {HTMLElement} element\n * @returns {HTMLElement}\n */\nconst getDynamicTableForElement = element => {\n const wrapper = element.closest(Selectors.buttonWrapper);\n\n return DynamicTable.getTableFromId(wrapper.dataset.tableUniqueid);\n};\n\n/**\n * Register the event listeners for this contextid.\n *\n * @param {Number} contextId\n */\nconst registerEventListeners = contextId => {\n document.addEventListener('click', e => {\n if (e.target.closest(Selectors.triggerButtons)) {\n e.preventDefault();\n\n showModal(getDynamicTableForElement(e.target), contextId);\n\n return;\n }\n });\n};\n\n/**\n * Display the modal for this contextId.\n *\n * @param {HTMLElement} dynamicTable The table to beb refreshed when changes are made\n * @param {Number} contextId\n * @returns {Promise}\n */\nconst showModal = (dynamicTable, contextId) => {\n const pendingPromise = new Pending('enrol_manual/quickenrolment:showModal');\n\n return ModalFactory.create({\n type: ModalFactory.types.SAVE_CANCEL,\n large: true,\n title: Str.get_string('enrolusers', 'enrol_manual'),\n body: getBodyForContext(contextId),\n })\n .then(modal => {\n modal.getRoot().on(ModalEvents.save, e => {\n // Trigger a form submission, so that any mform elements can do final tricks before the form submission\n // is processed.\n // The actual submit even tis captured in the next handler.\n\n e.preventDefault();\n modal.getRoot().find('form').submit();\n });\n\n modal.getRoot().on('submit', 'form', e => {\n e.preventDefault();\n\n submitFormAjax(dynamicTable, modal);\n });\n\n modal.getRoot().on(ModalEvents.hidden, () => {\n modal.destroy();\n });\n\n return modal;\n })\n .then(modal => {\n modal.show();\n\n return modal;\n })\n .then(modal => {\n modal.setSaveButtonText(Str.get_string('enrolusers', 'enrol_manual'));\n\n modal.getBodyPromise().then(body => {\n if (body.get(0).querySelector(Selectors.cohortSelector)) {\n modal.setSaveButtonText(Str.get_string('enroluserscohorts', 'enrol_manual'));\n }\n\n return body;\n })\n .catch();\n\n pendingPromise.resolve();\n\n return modal;\n })\n .catch(Notification.exception);\n};\n\n/**\n * Submit the form via ajax.\n *\n * @param {HTMLElement} dynamicTable\n * @param {Object} modal\n */\nconst submitFormAjax = (dynamicTable, modal) => {\n // Note: We use a jQuery object here so that we can use its serialize functionality.\n const form = modal.getRoot().find('form');\n\n // Before send the data through AJAX, we need to parse and remove some unwanted hidden fields.\n // This hidden fields are added automatically by mforms and when it reaches the AJAX we get an error.\n form.get(0).querySelectorAll(Selectors.unwantedHiddenFields).forEach(hiddenField => hiddenField.remove());\n\n modal.hide();\n modal.destroy();\n\n jQuery.ajax(\n `${Config.wwwroot}/enrol/manual/ajax.php?${form.serialize()}`,\n {\n type: 'GET',\n processData: false,\n contentType: \"application/json\",\n }\n )\n .then(response => {\n if (response.error) {\n throw new Error(response.error);\n }\n\n return response.count;\n })\n .then(count => {\n return Promise.all([\n Str.get_string('totalenrolledusers', 'enrol', count),\n DynamicTable.refreshTableContent(dynamicTable),\n ]);\n })\n .then(([notificationBody]) => notificationBody)\n .then(notificationBody => Toast.add(notificationBody))\n .catch(error => {\n Notification.addNotification({\n message: error.message,\n type: 'error',\n });\n });\n};\n\n/**\n * Set up quick enrolment for the manual enrolment plugin.\n *\n * @param {Number} contextid The context id to setup for\n */\nexport const init = ({contextid}) => {\n registerEventListeners(contextid);\n\n Prefetch.prefetchStrings('enrol_manual', [\n 'enrolusers',\n 'enroluserscohorts',\n ]);\n\n Prefetch.prefetchString('enrol', 'totalenrolledusers');\n};\n"],"file":"quickenrolment.min.js"} \ No newline at end of file diff --git a/enrol/manual/amd/src/quickenrolment.js b/enrol/manual/amd/src/quickenrolment.js index ee67d3cb11d..1034c2b2072 100644 --- a/enrol/manual/amd/src/quickenrolment.js +++ b/enrol/manual/amd/src/quickenrolment.js @@ -29,6 +29,7 @@ import ModalEvents from 'core/modal_events'; import ModalFactory from 'core/modal_factory'; import Notification from 'core/notification'; import jQuery from 'jquery'; +import Pending from 'core/pending'; import Prefetch from 'core/prefetch'; const Selectors = { @@ -85,6 +86,8 @@ const registerEventListeners = contextId => { * @returns {Promise} */ const showModal = (dynamicTable, contextId) => { + const pendingPromise = new Pending('enrol_manual/quickenrolment:showModal'); + return ModalFactory.create({ type: ModalFactory.types.SAVE_CANCEL, large: true, @@ -130,6 +133,8 @@ const showModal = (dynamicTable, contextId) => { }) .catch(); + pendingPromise.resolve(); + return modal; }) .catch(Notification.exception);