From e3250763359b0a7748d7077912fdb1ffba9a6f38 Mon Sep 17 00:00:00 2001 From: Paul Holden Date: Tue, 25 Mar 2025 11:12:30 +0000 Subject: [PATCH] MDL-85001 reportbuilder: ensure report uniqueness in filters loader. Where the same report exists multiple times on the page, ensure we are still able to uniquely identify each via ID. Update the filters module to ensure the filters form for each report is initialised. --- reportbuilder/amd/build/filters.min.js | 2 +- reportbuilder/amd/build/filters.min.js.map | 2 +- reportbuilder/amd/src/filters.js | 8 ++++---- reportbuilder/templates/local/dynamictabs/editor.mustache | 3 ++- reportbuilder/templates/local/filters/area.mustache | 3 +-- reportbuilder/templates/report.mustache | 3 ++- 6 files changed, 11 insertions(+), 10 deletions(-) diff --git a/reportbuilder/amd/build/filters.min.js b/reportbuilder/amd/build/filters.min.js index 79761b920e6..f559e9179c6 100644 --- a/reportbuilder/amd/build/filters.min.js +++ b/reportbuilder/amd/build/filters.min.js @@ -5,6 +5,6 @@ define("core_reportbuilder/filters",["exports","core/event_dispatcher","core/fra * @module core_reportbuilder/filters * @copyright 2021 Paul Holden * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_notification=_interopRequireDefault(_notification),_pending=_interopRequireDefault(_pending),_templates=_interopRequireDefault(_templates),_dynamicform=_interopRequireDefault(_dynamicform),reportEvents=_interopRequireWildcard(reportEvents),reportSelectors=_interopRequireWildcard(reportSelectors);const setFilterButtonCount=async(reportElement,filterCount)=>{const filterButtonLabel=reportElement.querySelector(reportSelectors.regions.filterButtonLabel);filterButtonLabel.textContent=filterCount>0?await(0,_str.getString)("filtersappliedx","core_reportbuilder",filterCount):await(0,_str.getString)("filters","moodle")};_exports.init=(reportId,contextId)=>{const reportElement=document.querySelector(reportSelectors.forReport(reportId)),filterFormContainer=reportElement.querySelector(reportSelectors.regions.filtersForm);if(filterFormContainer.dataset.initialized)return;filterFormContainer.dataset.initialized=!0;const filterForm=new _dynamicform.default(filterFormContainer,"\\core_reportbuilder\\form\\filter");filterForm.addEventListener(filterForm.events.FORM_SUBMITTED,(event=>{event.preventDefault(),(0,_event_dispatcher.dispatchEvent)(reportEvents.tableReload,{},reportElement),setFilterButtonCount(reportElement,event.detail),(0,_str.getString)("filtersapplied","core_reportbuilder").then(_toast.add).catch(_notification.default.exception)})),filterForm.addEventListener(filterForm.events.NOSUBMIT_BUTTON_PRESSED,(event=>{event.preventDefault();const pendingPromise=new _pending.default("core_reportbuilder/filters:reset"),reportParameters=reportElement.dataset.parameter;(0,_filters.resetFilters)(reportId,reportParameters).then((()=>(0,_str.getString)("filtersreset","core_reportbuilder"))).then(_toast.add).then((()=>(0,_fragment.loadFragment)("core_reportbuilder","filters_form",contextId,{reportid:reportId,parameters:reportParameters}))).then(((html,js)=>(_templates.default.replaceNodeContents(filterFormContainer,html,js),(0,_event_dispatcher.dispatchEvent)(reportEvents.tableReload,{},reportElement),setFilterButtonCount(reportElement,0),pendingPromise.resolve()))).catch(_notification.default.exception)})),document.querySelector("#region-main").style.overflowX="visible"}})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_notification=_interopRequireDefault(_notification),_pending=_interopRequireDefault(_pending),_templates=_interopRequireDefault(_templates),_dynamicform=_interopRequireDefault(_dynamicform),reportEvents=_interopRequireWildcard(reportEvents),reportSelectors=_interopRequireWildcard(reportSelectors);const setFilterButtonCount=async(reportElement,filterCount)=>{const filterButtonLabel=reportElement.querySelector(reportSelectors.regions.filterButtonLabel);filterButtonLabel.textContent=filterCount>0?await(0,_str.getString)("filtersappliedx","core_reportbuilder",filterCount):await(0,_str.getString)("filters","moodle")};_exports.init=(reportElementId,contextId)=>{const reportElement=document.getElementById(reportElementId),filterFormContainer=reportElement.querySelector(reportSelectors.regions.filtersForm);if(filterFormContainer.dataset.initialized)return;filterFormContainer.dataset.initialized=!0;const filterForm=new _dynamicform.default(filterFormContainer,"\\core_reportbuilder\\form\\filter");filterForm.addEventListener(filterForm.events.FORM_SUBMITTED,(event=>{event.preventDefault(),(0,_event_dispatcher.dispatchEvent)(reportEvents.tableReload,{},reportElement),setFilterButtonCount(reportElement,event.detail),(0,_str.getString)("filtersapplied","core_reportbuilder").then(_toast.add).catch(_notification.default.exception)})),filterForm.addEventListener(filterForm.events.NOSUBMIT_BUTTON_PRESSED,(event=>{event.preventDefault();const pendingPromise=new _pending.default("core_reportbuilder/filters:reset"),{reportId:reportId,reportParameters:reportParameters}=reportElement.dataset;(0,_filters.resetFilters)(reportId,reportParameters).then((()=>(0,_str.getString)("filtersreset","core_reportbuilder"))).then(_toast.add).then((()=>(0,_fragment.loadFragment)("core_reportbuilder","filters_form",contextId,{reportid:reportId,parameters:reportParameters}))).then(((html,js)=>(_templates.default.replaceNodeContents(filterFormContainer,html,js),(0,_event_dispatcher.dispatchEvent)(reportEvents.tableReload,{},reportElement),setFilterButtonCount(reportElement,0),pendingPromise.resolve()))).catch(_notification.default.exception)})),document.querySelector("#region-main").style.overflowX="visible"}})); //# sourceMappingURL=filters.min.js.map \ No newline at end of file diff --git a/reportbuilder/amd/build/filters.min.js.map b/reportbuilder/amd/build/filters.min.js.map index 06cc2f700f0..80def8c76a1 100644 --- a/reportbuilder/amd/build/filters.min.js.map +++ b/reportbuilder/amd/build/filters.min.js.map @@ -1 +1 @@ -{"version":3,"file":"filters.min.js","sources":["../src/filters.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Report builder filter management\n *\n * @module core_reportbuilder/filters\n * @copyright 2021 Paul Holden \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {dispatchEvent} from 'core/event_dispatcher';\nimport {loadFragment} from 'core/fragment';\nimport Notification from 'core/notification';\nimport Pending from 'core/pending';\nimport {getString} from 'core/str';\nimport Templates from 'core/templates';\nimport {add as addToast} from 'core/toast';\nimport DynamicForm from 'core_form/dynamicform';\nimport * as reportEvents from 'core_reportbuilder/local/events';\nimport * as reportSelectors from 'core_reportbuilder/local/selectors';\nimport {resetFilters} from 'core_reportbuilder/local/repository/filters';\n\n/**\n * Update filter button text to indicate applied filter count\n *\n * @param {Element} reportElement\n * @param {Number} filterCount\n */\nconst setFilterButtonCount = async(reportElement, filterCount) => {\n const filterButtonLabel = reportElement.querySelector(reportSelectors.regions.filterButtonLabel);\n\n if (filterCount > 0) {\n filterButtonLabel.textContent = await getString('filtersappliedx', 'core_reportbuilder', filterCount);\n } else {\n filterButtonLabel.textContent = await getString('filters', 'moodle');\n }\n};\n\n/**\n * Initialise module for given report\n *\n * @method\n * @param {Number} reportId\n * @param {Number} contextId\n */\nexport const init = (reportId, contextId) => {\n const reportElement = document.querySelector(reportSelectors.forReport(reportId));\n const filterFormContainer = reportElement.querySelector(reportSelectors.regions.filtersForm);\n\n // Ensure we only add our listeners once (can be called multiple times by mustache template).\n if (filterFormContainer.dataset.initialized) {\n return;\n }\n filterFormContainer.dataset.initialized = true;\n\n const filterForm = new DynamicForm(filterFormContainer, '\\\\core_reportbuilder\\\\form\\\\filter');\n\n // Submit report filters.\n filterForm.addEventListener(filterForm.events.FORM_SUBMITTED, event => {\n event.preventDefault();\n\n // After the form has been submitted, we should trigger report table reload.\n dispatchEvent(reportEvents.tableReload, {}, reportElement);\n setFilterButtonCount(reportElement, event.detail);\n\n getString('filtersapplied', 'core_reportbuilder')\n .then(addToast)\n .catch(Notification.exception);\n });\n\n // Reset report filters.\n filterForm.addEventListener(filterForm.events.NOSUBMIT_BUTTON_PRESSED, event => {\n event.preventDefault();\n\n const pendingPromise = new Pending('core_reportbuilder/filters:reset');\n const reportParameters = reportElement.dataset.parameter;\n\n resetFilters(reportId, reportParameters)\n .then(() => getString('filtersreset', 'core_reportbuilder'))\n .then(addToast)\n .then(() => loadFragment('core_reportbuilder', 'filters_form', contextId, {\n reportid: reportId,\n parameters: reportParameters,\n }))\n .then((html, js) => {\n Templates.replaceNodeContents(filterFormContainer, html, js);\n\n dispatchEvent(reportEvents.tableReload, {}, reportElement);\n setFilterButtonCount(reportElement, 0);\n\n return pendingPromise.resolve();\n })\n .catch(Notification.exception);\n });\n\n // Modify \"region-main\" overflow for big filter forms.\n document.querySelector('#region-main').style.overflowX = \"visible\";\n};\n"],"names":["setFilterButtonCount","async","reportElement","filterCount","filterButtonLabel","querySelector","reportSelectors","regions","textContent","reportId","contextId","document","forReport","filterFormContainer","filtersForm","dataset","initialized","filterForm","DynamicForm","addEventListener","events","FORM_SUBMITTED","event","preventDefault","reportEvents","tableReload","detail","then","addToast","catch","Notification","exception","NOSUBMIT_BUTTON_PRESSED","pendingPromise","Pending","reportParameters","parameter","reportid","parameters","html","js","replaceNodeContents","resolve","style","overflowX"],"mappings":";;;;;;;kYAyCMA,qBAAuBC,MAAMC,cAAeC,qBACxCC,kBAAoBF,cAAcG,cAAcC,gBAAgBC,QAAQH,mBAG1EA,kBAAkBI,YADlBL,YAAc,QACwB,kBAAU,kBAAmB,qBAAsBA,mBAEnD,kBAAU,UAAW,yBAW/C,CAACM,SAAUC,mBACrBR,cAAgBS,SAASN,cAAcC,gBAAgBM,UAAUH,WACjEI,oBAAsBX,cAAcG,cAAcC,gBAAgBC,QAAQO,gBAG5ED,oBAAoBE,QAAQC,mBAGhCH,oBAAoBE,QAAQC,aAAc,QAEpCC,WAAa,IAAIC,qBAAYL,oBAAqB,sCAGxDI,WAAWE,iBAAiBF,WAAWG,OAAOC,gBAAgBC,QAC1DA,MAAMC,qDAGQC,aAAaC,YAAa,GAAIvB,eAC5CF,qBAAqBE,cAAeoB,MAAMI,2BAEhC,iBAAkB,sBACvBC,KAAKC,YACLC,MAAMC,sBAAaC,cAI5Bd,WAAWE,iBAAiBF,WAAWG,OAAOY,yBAAyBV,QACnEA,MAAMC,uBAEAU,eAAiB,IAAIC,iBAAQ,oCAC7BC,iBAAmBjC,cAAca,QAAQqB,oCAElC3B,SAAU0B,kBAClBR,MAAK,KAAM,kBAAU,eAAgB,wBACrCA,KAAKC,YACLD,MAAK,KAAM,0BAAa,qBAAsB,eAAgBjB,UAAW,CACtE2B,SAAU5B,SACV6B,WAAYH,qBAEfR,MAAK,CAACY,KAAMC,yBACCC,oBAAoB5B,oBAAqB0B,KAAMC,wCAE3ChB,aAAaC,YAAa,GAAIvB,eAC5CF,qBAAqBE,cAAe,GAE7B+B,eAAeS,aAEzBb,MAAMC,sBAAaC,cAI5BpB,SAASN,cAAc,gBAAgBsC,MAAMC,UAAY"} \ No newline at end of file +{"version":3,"file":"filters.min.js","sources":["../src/filters.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Report builder filter management\n *\n * @module core_reportbuilder/filters\n * @copyright 2021 Paul Holden \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {dispatchEvent} from 'core/event_dispatcher';\nimport {loadFragment} from 'core/fragment';\nimport Notification from 'core/notification';\nimport Pending from 'core/pending';\nimport {getString} from 'core/str';\nimport Templates from 'core/templates';\nimport {add as addToast} from 'core/toast';\nimport DynamicForm from 'core_form/dynamicform';\nimport * as reportEvents from 'core_reportbuilder/local/events';\nimport * as reportSelectors from 'core_reportbuilder/local/selectors';\nimport {resetFilters} from 'core_reportbuilder/local/repository/filters';\n\n/**\n * Update filter button text to indicate applied filter count\n *\n * @param {Element} reportElement\n * @param {Number} filterCount\n */\nconst setFilterButtonCount = async(reportElement, filterCount) => {\n const filterButtonLabel = reportElement.querySelector(reportSelectors.regions.filterButtonLabel);\n\n if (filterCount > 0) {\n filterButtonLabel.textContent = await getString('filtersappliedx', 'core_reportbuilder', filterCount);\n } else {\n filterButtonLabel.textContent = await getString('filters', 'moodle');\n }\n};\n\n/**\n * Initialise module for given report\n *\n * @method\n * @param {String} reportElementId\n * @param {Number} contextId\n */\nexport const init = (reportElementId, contextId) => {\n const reportElement = document.getElementById(reportElementId);\n const filterFormContainer = reportElement.querySelector(reportSelectors.regions.filtersForm);\n\n // Ensure we only add our listeners once (can be called multiple times by mustache template).\n if (filterFormContainer.dataset.initialized) {\n return;\n }\n filterFormContainer.dataset.initialized = true;\n\n const filterForm = new DynamicForm(filterFormContainer, '\\\\core_reportbuilder\\\\form\\\\filter');\n\n // Submit report filters.\n filterForm.addEventListener(filterForm.events.FORM_SUBMITTED, event => {\n event.preventDefault();\n\n // After the form has been submitted, we should trigger report table reload.\n dispatchEvent(reportEvents.tableReload, {}, reportElement);\n setFilterButtonCount(reportElement, event.detail);\n\n getString('filtersapplied', 'core_reportbuilder')\n .then(addToast)\n .catch(Notification.exception);\n });\n\n // Reset report filters.\n filterForm.addEventListener(filterForm.events.NOSUBMIT_BUTTON_PRESSED, event => {\n event.preventDefault();\n\n const pendingPromise = new Pending('core_reportbuilder/filters:reset');\n\n const {reportId, reportParameters} = reportElement.dataset;\n resetFilters(reportId, reportParameters)\n .then(() => getString('filtersreset', 'core_reportbuilder'))\n .then(addToast)\n .then(() => loadFragment('core_reportbuilder', 'filters_form', contextId, {\n reportid: reportId,\n parameters: reportParameters,\n }))\n .then((html, js) => {\n Templates.replaceNodeContents(filterFormContainer, html, js);\n\n dispatchEvent(reportEvents.tableReload, {}, reportElement);\n setFilterButtonCount(reportElement, 0);\n\n return pendingPromise.resolve();\n })\n .catch(Notification.exception);\n });\n\n // Modify \"region-main\" overflow for big filter forms.\n document.querySelector('#region-main').style.overflowX = \"visible\";\n};\n"],"names":["setFilterButtonCount","async","reportElement","filterCount","filterButtonLabel","querySelector","reportSelectors","regions","textContent","reportElementId","contextId","document","getElementById","filterFormContainer","filtersForm","dataset","initialized","filterForm","DynamicForm","addEventListener","events","FORM_SUBMITTED","event","preventDefault","reportEvents","tableReload","detail","then","addToast","catch","Notification","exception","NOSUBMIT_BUTTON_PRESSED","pendingPromise","Pending","reportId","reportParameters","reportid","parameters","html","js","replaceNodeContents","resolve","style","overflowX"],"mappings":";;;;;;;kYAyCMA,qBAAuBC,MAAMC,cAAeC,qBACxCC,kBAAoBF,cAAcG,cAAcC,gBAAgBC,QAAQH,mBAG1EA,kBAAkBI,YADlBL,YAAc,QACwB,kBAAU,kBAAmB,qBAAsBA,mBAEnD,kBAAU,UAAW,yBAW/C,CAACM,gBAAiBC,mBAC5BR,cAAgBS,SAASC,eAAeH,iBACxCI,oBAAsBX,cAAcG,cAAcC,gBAAgBC,QAAQO,gBAG5ED,oBAAoBE,QAAQC,mBAGhCH,oBAAoBE,QAAQC,aAAc,QAEpCC,WAAa,IAAIC,qBAAYL,oBAAqB,sCAGxDI,WAAWE,iBAAiBF,WAAWG,OAAOC,gBAAgBC,QAC1DA,MAAMC,qDAGQC,aAAaC,YAAa,GAAIvB,eAC5CF,qBAAqBE,cAAeoB,MAAMI,2BAEhC,iBAAkB,sBACvBC,KAAKC,YACLC,MAAMC,sBAAaC,cAI5Bd,WAAWE,iBAAiBF,WAAWG,OAAOY,yBAAyBV,QACnEA,MAAMC,uBAEAU,eAAiB,IAAIC,iBAAQ,qCAE7BC,SAACA,SAADC,iBAAWA,kBAAoBlC,cAAca,kCACtCoB,SAAUC,kBAClBT,MAAK,KAAM,kBAAU,eAAgB,wBACrCA,KAAKC,YACLD,MAAK,KAAM,0BAAa,qBAAsB,eAAgBjB,UAAW,CACtE2B,SAAUF,SACVG,WAAYF,qBAEfT,MAAK,CAACY,KAAMC,yBACCC,oBAAoB5B,oBAAqB0B,KAAMC,wCAE3ChB,aAAaC,YAAa,GAAIvB,eAC5CF,qBAAqBE,cAAe,GAE7B+B,eAAeS,aAEzBb,MAAMC,sBAAaC,cAI5BpB,SAASN,cAAc,gBAAgBsC,MAAMC,UAAY"} \ No newline at end of file diff --git a/reportbuilder/amd/src/filters.js b/reportbuilder/amd/src/filters.js index 1ef767bcfa1..ba3431b3b50 100644 --- a/reportbuilder/amd/src/filters.js +++ b/reportbuilder/amd/src/filters.js @@ -53,11 +53,11 @@ const setFilterButtonCount = async(reportElement, filterCount) => { * Initialise module for given report * * @method - * @param {Number} reportId + * @param {String} reportElementId * @param {Number} contextId */ -export const init = (reportId, contextId) => { - const reportElement = document.querySelector(reportSelectors.forReport(reportId)); +export const init = (reportElementId, contextId) => { + const reportElement = document.getElementById(reportElementId); const filterFormContainer = reportElement.querySelector(reportSelectors.regions.filtersForm); // Ensure we only add our listeners once (can be called multiple times by mustache template). @@ -86,8 +86,8 @@ export const init = (reportId, contextId) => { event.preventDefault(); const pendingPromise = new Pending('core_reportbuilder/filters:reset'); - const reportParameters = reportElement.dataset.parameter; + const {reportId, reportParameters} = reportElement.dataset; resetFilters(reportId, reportParameters) .then(() => getString('filtersreset', 'core_reportbuilder')) .then(addToast) diff --git a/reportbuilder/templates/local/dynamictabs/editor.mustache b/reportbuilder/templates/local/dynamictabs/editor.mustache index a04fb0fb3d2..322c0b3ee83 100644 --- a/reportbuilder/templates/local/dynamictabs/editor.mustache +++ b/reportbuilder/templates/local/dynamictabs/editor.mustache @@ -95,10 +95,11 @@ }}

{{#str}} editor, core_reportbuilder {{/str}}

diff --git a/reportbuilder/templates/local/filters/area.mustache b/reportbuilder/templates/local/filters/area.mustache index 355b9b69636..12c19c8924e 100644 --- a/reportbuilder/templates/local/filters/area.mustache +++ b/reportbuilder/templates/local/filters/area.mustache @@ -21,7 +21,6 @@ Example context (json): { - "id": 3, "contextid": 1, "filtersapplied": 3, "filtersform": "form" @@ -51,6 +50,6 @@ {{#js}} require(['core_reportbuilder/filters'], function(filters) { - filters.init({{id}}, {{contextid}}); + filters.init('reportbuilder-report-{{uniqid}}', {{contextid}}); }); {{/js}} diff --git a/reportbuilder/templates/report.mustache b/reportbuilder/templates/report.mustache index 729df86b4a8..c15911bf120 100644 --- a/reportbuilder/templates/report.mustache +++ b/reportbuilder/templates/report.mustache @@ -44,10 +44,11 @@ } }}
{{! Action buttons }}