1 // This file is part of Moodle - http://moodle.org/
3 // Moodle is free software: you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation, either version 3 of the License, or
6 // (at your option) any later version.
8 // Moodle is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU General Public License for more details.
13 // You should have received a copy of the GNU General Public License
14 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17 * Report builder conditions editor
19 * @module core_reportbuilder/local/editor/conditions
20 * @copyright 2021 Paul Holden <paulh@moodle.com>
21 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26 import $ from 'jquery';
27 import {dispatchEvent} from 'core/event_dispatcher';
28 import 'core/inplace_editable';
29 import Notification from 'core/notification';
30 import Pending from 'core/pending';
31 import {prefetchStrings} from 'core/prefetch';
32 import SortableList from 'core/sortable_list';
33 import {get_string as getString, get_strings as getStrings} from 'core/str';
34 import Templates from 'core/templates';
35 import {add as addToast} from 'core/toast';
36 import DynamicForm from 'core_form/dynamicform';
37 import * as reportEvents from 'core_reportbuilder/local/events';
38 import * as reportSelectors from 'core_reportbuilder/local/selectors';
39 import {addCondition, deleteCondition, reorderCondition, resetConditions} from 'core_reportbuilder/local/repository/conditions';
42 * Reload conditions settings region
44 * @param {Element} reportElement
45 * @param {Object} templateContext
48 const reloadSettingsConditionsRegion = (reportElement, templateContext) => {
49 const pendingPromise = new Pending('core_reportbuilder/conditions:reload');
50 const settingsConditionsRegion = reportElement.querySelector(reportSelectors.regions.settingsConditions);
52 return Templates.renderForPromise('core_reportbuilder/local/settings/conditions', {conditions: templateContext})
53 .then(({html, js}) => {
54 Templates.replaceNode(settingsConditionsRegion, html, js + templateContext.javascript);
55 initConditionsForm(reportElement);
56 return pendingPromise.resolve();
61 * Initialise conditions form, must be called on each init because the form container is re-created when switching editor modes
63 const initConditionsForm = () => {
64 // Handle dynamic conditions form.
65 const reportElement = document.querySelector(reportSelectors.regions.report);
66 const conditionFormContainer = reportElement.querySelector(reportSelectors.regions.settingsConditions);
67 if (!conditionFormContainer) {
70 const conditionForm = new DynamicForm(conditionFormContainer, '\\core_reportbuilder\\form\\condition');
72 // Submit report conditions.
73 conditionForm.addEventListener(conditionForm.events.FORM_SUBMITTED, event => {
74 event.preventDefault();
76 getString('conditionsapplied', 'core_reportbuilder')
78 .catch(Notification.exception);
80 // After the form has been submitted, we should trigger report table reload.
81 dispatchEvent(reportEvents.tableReload, {}, reportElement);
84 // Reset report conditions.
85 conditionForm.addEventListener(conditionForm.events.NOSUBMIT_BUTTON_PRESSED, event => {
86 event.preventDefault();
89 {key: 'resetconditions', component: 'core_reportbuilder'},
90 {key: 'resetconditionsconfirm', component: 'core_reportbuilder'},
91 {key: 'resetall', component: 'core_reportbuilder'},
92 ]).then(([confirmTitle, confirmText, confirmButton]) => {
93 Notification.confirm(confirmTitle, confirmText, confirmButton, null, () => {
94 const pendingPromise = new Pending('core_reportbuilder/conditions:reset');
96 resetConditions(reportElement.dataset.reportId)
97 .then(data => reloadSettingsConditionsRegion(reportElement, data))
98 .then(() => getString('conditionsreset', 'core_reportbuilder'))
101 dispatchEvent(reportEvents.tableReload, {}, reportElement);
102 return pendingPromise.resolve();
104 .catch(Notification.exception);
107 }).catch(Notification.exception);
112 * Initialise module, prefetch all required strings
114 * @param {Boolean} initialized Ensure we only add our listeners once
116 export const init = initialized => {
117 prefetchStrings('core_reportbuilder', [
124 'deleteconditionconfirm',
127 'resetconditionsconfirm',
130 prefetchStrings('core', [
134 initConditionsForm();
139 document.addEventListener('click', event => {
141 // Add condition to report.
142 const reportAddCondition = event.target.closest(reportSelectors.actions.reportAddCondition);
143 if (reportAddCondition) {
144 event.preventDefault();
146 const reportElement = reportAddCondition.closest(reportSelectors.regions.report);
148 // Check if dropdown is closed with no condition selected.
149 if (reportAddCondition.value === '0') {
153 const pendingPromise = new Pending('core_reportbuilder/conditions:add');
155 addCondition(reportElement.dataset.reportId, reportAddCondition.value)
156 .then(data => reloadSettingsConditionsRegion(reportElement, data))
157 .then(() => getString('conditionadded', 'core_reportbuilder',
158 reportAddCondition.options[reportAddCondition.selectedIndex].text))
161 dispatchEvent(reportEvents.tableReload, {}, reportElement);
162 return pendingPromise.resolve();
164 .catch(Notification.exception);
167 // Remove condition from report.
168 const reportRemoveCondition = event.target.closest(reportSelectors.actions.reportRemoveCondition);
169 if (reportRemoveCondition) {
170 event.preventDefault();
172 const reportElement = reportRemoveCondition.closest(reportSelectors.regions.report);
173 const conditionContainer = reportRemoveCondition.closest(reportSelectors.regions.activeCondition);
174 const conditionName = conditionContainer.dataset.conditionName;
177 {key: 'deletecondition', component: 'core_reportbuilder', param: conditionName},
178 {key: 'deleteconditionconfirm', component: 'core_reportbuilder', param: conditionName},
179 {key: 'delete', component: 'core'},
180 ]).then(([confirmTitle, confirmText, confirmButton]) => {
181 Notification.confirm(confirmTitle, confirmText, confirmButton, null, () => {
182 const pendingPromise = new Pending('core_reportbuilder/conditions:remove');
184 deleteCondition(reportElement.dataset.reportId, conditionContainer.dataset.conditionId)
185 .then(data => reloadSettingsConditionsRegion(reportElement, data))
186 .then(() => getString('conditiondeleted', 'core_reportbuilder', conditionName))
189 dispatchEvent(reportEvents.tableReload, {}, reportElement);
190 return pendingPromise.resolve();
192 .catch(Notification.exception);
195 }).catch(Notification.exception);
199 // Initialize sortable list to handle active conditions moving (note JQuery dependency, see MDL-72293 for resolution).
200 var activeConditionsSortableList = new SortableList(`${reportSelectors.regions.activeConditions}`,
201 {isHorizontal: false});
202 activeConditionsSortableList.getElementName = element => Promise.resolve(element.data('conditionName'));
204 $(document).on(SortableList.EVENTS.DROP, reportSelectors.regions.activeCondition, (event, info) => {
205 if (info.positionChanged) {
206 const pendingPromise = new Pending('core_reportbuilder/conditions:reorder');
207 const reportElement = event.target.closest(reportSelectors.regions.report);
208 const conditionId = info.element.data('conditionId');
209 const conditionPosition = info.element.data('conditionPosition');
211 // Select target position, if moving to the end then count number of element siblings.
212 let targetConditionPosition = info.targetNextElement.data('conditionPosition') || info.element.siblings().length + 2;
213 if (targetConditionPosition > conditionPosition) {
214 targetConditionPosition--;
217 reorderCondition(reportElement.dataset.reportId, conditionId, targetConditionPosition)
218 .then(data => reloadSettingsConditionsRegion(reportElement, data))
219 .then(() => getString('conditionmoved', 'core_reportbuilder', info.element.data('conditionName')))
222 dispatchEvent(reportEvents.tableReload, {}, reportElement);
223 return pendingPromise.resolve();
225 .catch(Notification.exception);