63040c64b8d3042fd32cccc9c01796df6875803c
[moodle.git] / mod / forum / report / summary / amd / src / filters.js
1 // This file is part of Moodle - http://moodle.org/
2 //
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.
7 //
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.
12 //
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/>.
16 /**
17  * Module responsible for handling forum summary report filters.
18  *
19  * @module     forumreport_summary/filters
20  * @package    forumreport_summary
21  * @copyright  2019 Michael Hawkins <michaelh@moodle.com>
22  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
25 import $ from 'jquery';
26 import Popper from 'core/popper';
27 import CustomEvents from 'core/custom_interaction_events';
28 import Selectors from 'forumreport_summary/selectors';
29 import Y from 'core/yui';
30 import Ajax from 'core/ajax';
31 import KeyCodes from 'core/key_codes';
33 export const init = (root) => {
34     let jqRoot = $(root);
36     // Hide loading spinner and show report once page is ready.
37     // This ensures filters can be applied when sorting by columns.
38     $(document).ready(function() {
39         $('.loading-icon').hide();
40         $('#summaryreport').removeClass('hidden');
41     });
43     // Generic filter handlers.
45     // Called to override click event to trigger a proper generate request with filtering.
46     const generateWithFilters = (event) => {
47         let newLink = $('#filtersform').attr('action');
49         if (event) {
50             event.preventDefault();
52             let filterParams = event.target.search.substr(1);
53             newLink += '&' + filterParams;
54         }
56         $('#filtersform').attr('action', newLink);
57         $('#filtersform').submit();
58     };
60     // Override 'reset table preferences' so it generates with filters.
61     $('.resettable').on("click", "a", function(event) {
62         generateWithFilters(event);
63     });
65     // Override table heading sort links so they generate with filters.
66     $('thead').on("click", "a", function(event) {
67         generateWithFilters(event);
68     });
70     // Override pagination page links so they generate with filters.
71     $('.pagination').on("click", "a", function(event) {
72         generateWithFilters(event);
73     });
75     // Submit report via filter
76     const submitWithFilter = (containerelement) => {
77         // Disable the dates filter mform checker to prevent any changes triggering a warning to the user.
78         Y.use('moodle-core-formchangechecker', function() {
79             M.core_formchangechecker.reset_form_dirty_state();
80         });
82         // Close the container (eg popover).
83         $(containerelement).addClass('hidden');
85         // Submit the filter values and re-generate report.
86         generateWithFilters(false);
87     };
89     // Use popper to override date mform calendar position.
90     const updateCalendarPosition = (referenceid) => {
91         let referenceElement = document.querySelector(referenceid),
92             popperContent = document.querySelector(Selectors.filters.date.calendar);
94         popperContent.style.removeProperty("z-index");
95         new Popper(referenceElement, popperContent, {placement: 'bottom'});
96     };
98     // Close the relevant filter.
99     var closeOpenFilters = (openFilterButton, openFilter) => {
100         openFilter.classList.add('hidden');
101         openFilter.setAttribute('data-openfilter', 'false');
103         openFilterButton.classList.add('btn-primary');
104         openFilterButton.classList.remove('btn-outline-primary');
105         openFilterButton.setAttribute('aria-expanded', false);
106     };
108     // Groups filter specific handlers.
110     // Event handler for clicking select all groups.
111     jqRoot.on(CustomEvents.events.activate, Selectors.filters.group.selectall, function() {
112         let deselected = root.querySelectorAll(Selectors.filters.group.checkbox + ':not(:checked)');
113         deselected.forEach(function(checkbox) {
114             checkbox.checked = true;
115         });
116     });
118     // Event handler for clearing filter by clicking option.
119     jqRoot.on(CustomEvents.events.activate, Selectors.filters.group.clear, function() {
120         // Clear checkboxes.
121         let selected = root.querySelectorAll(Selectors.filters.group.checkbox + ':checked');
122         selected.forEach(function(checkbox) {
123             checkbox.checked = false;
124         });
125     });
127     // Event handler for showing groups filter popover.
128     jqRoot.on(CustomEvents.events.activate, Selectors.filters.group.trigger, function() {
129         // Create popover.
130         let referenceElement = root.querySelector(Selectors.filters.group.trigger),
131             popperContent = root.querySelector(Selectors.filters.group.popover);
133         new Popper(referenceElement, popperContent, {placement: 'bottom'});
135         // Show popover.
136         popperContent.classList.remove('hidden');
137         popperContent.setAttribute('data-openfilter', 'true');
139         // Change to outlined button.
140         referenceElement.classList.add('btn-outline-primary');
141         referenceElement.classList.remove('btn-primary');
143         // Let screen readers know that it's now expanded.
144         referenceElement.setAttribute('aria-expanded', true);
146         // Add listeners to handle closing filter.
147         const closeListener = e => {
148             if (e.target.id !== referenceElement.id && popperContent !== e.target.closest('[data-openfilter="true"]')) {
149                 closeOpenFilters(referenceElement, popperContent);
150                 document.removeEventListener('click', closeListener);
151                 document.removeEventListener('keyup', escCloseListener);
152             }
153         };
155         document.addEventListener('click', closeListener);
157         const escCloseListener = e => {
158             if (e.keyCode === KeyCodes.escape) {
159                 closeOpenFilters(referenceElement, popperContent);
160                 document.removeEventListener('keyup', escCloseListener);
161                 document.removeEventListener('click', closeListener);
162             }
163         };
165         document.addEventListener('keyup', escCloseListener);
166     });
168     // Event handler to click save groups filter.
169     jqRoot.on(CustomEvents.events.activate, Selectors.filters.group.save, function() {
170         // Copy the saved values into the form before submitting.
171         let popcheckboxes = root.querySelectorAll(Selectors.filters.group.checkbox);
173         popcheckboxes.forEach(function(popcheckbox) {
174             let filtersform = document.forms.filtersform,
175                 saveid = popcheckbox.getAttribute('data-saveid');
177             filtersform.querySelector(`#${saveid}`).checked = popcheckbox.checked;
178         });
180         submitWithFilter('#filter-groups-popover');
181     });
183     // Dates filter specific handlers.
185    // Event handler for showing dates filter popover.
186     jqRoot.on(CustomEvents.events.activate, Selectors.filters.date.trigger, function() {
188         // Create popover.
189         let referenceElement = root.querySelector(Selectors.filters.date.trigger),
190             popperContent = root.querySelector(Selectors.filters.date.popover);
192         new Popper(referenceElement, popperContent, {placement: 'bottom'});
194         // Show popover and move focus.
195         popperContent.classList.remove('hidden');
196         popperContent.setAttribute('data-openfilter', 'true');
197         popperContent.querySelector('[name="filterdatefrompopover[enabled]"]').focus();
199         // Change to outlined button.
200         referenceElement.classList.add('btn-outline-primary');
201         referenceElement.classList.remove('btn-primary');
203         // Let screen readers know that it's now expanded.
204         referenceElement.setAttribute('aria-expanded', true);
206         // Add listener to handle closing filter.
207         const closeListener = e => {
208             if (e.target.id !== referenceElement.id && popperContent !== e.target.closest('[data-openfilter="true"]')) {
209                 closeOpenFilters(referenceElement, popperContent);
210                 document.removeEventListener('click', closeListener);
211                 document.removeEventListener('keyup', escCloseListener);
212             }
213         };
215         document.addEventListener('click', closeListener);
217         const escCloseListener = e => {
218             if (e.keyCode === KeyCodes.escape) {
219                 closeOpenFilters(referenceElement, popperContent);
220                 document.removeEventListener('keyup', escCloseListener);
221                 document.removeEventListener('click', closeListener);
222             }
223         };
225         document.addEventListener('keyup', escCloseListener);
226     });
228     // Event handler to save dates filter.
229     jqRoot.on(CustomEvents.events.activate, Selectors.filters.date.save, function() {
230         // Populate the hidden form inputs to submit the data.
231         let filtersForm = document.forms.filtersform;
232         const datesPopover = root.querySelector(Selectors.filters.date.popover);
233         const fromEnabled = datesPopover.querySelector('[name="filterdatefrompopover[enabled]"]').checked ? 1 : 0;
234         const toEnabled = datesPopover.querySelector('[name="filterdatetopopover[enabled]"]').checked ? 1 : 0;
236         if (!fromEnabled && !toEnabled) {
237             // Update the elements in the filter form.
238             filtersForm.elements['datefrom[timestamp]'].value = 0;
239             filtersForm.elements['datefrom[enabled]'].value = fromEnabled;
240             filtersForm.elements['dateto[timestamp]'].value = 0;
241             filtersForm.elements['dateto[enabled]'].value = toEnabled;
243             // Submit the filter values and re-generate report.
244             submitWithFilter('#filter-dates-popover');
245         } else {
246             let args = {data: []};
248             if (fromEnabled) {
249                 args.data.push({
250                     'key': 'from',
251                     'year': datesPopover.querySelector('[name="filterdatefrompopover[year]"]').value,
252                     'month': datesPopover.querySelector('[name="filterdatefrompopover[month]"]').value,
253                     'day': datesPopover.querySelector('[name="filterdatefrompopover[day]"]').value,
254                     'hour': 0,
255                     'minute': 0
256                 });
257             }
259             if (toEnabled) {
260                 args.data.push({
261                     'key': 'to',
262                     'year': datesPopover.querySelector('[name="filterdatetopopover[year]"]').value,
263                     'month': datesPopover.querySelector('[name="filterdatetopopover[month]"]').value,
264                     'day': datesPopover.querySelector('[name="filterdatetopopover[day]"]').value,
265                     'hour': 23,
266                     'minute': 59
267                 });
268             }
270             const request = {
271                 methodname: 'core_calendar_get_timestamps',
272                 args: args
273             };
275             Ajax.call([request])[0].done(function(result) {
276                 let fromTimestamp = 0,
277                     toTimestamp = 0;
279                 result['timestamps'].forEach(function(data){
280                     if (data.key === 'from') {
281                         fromTimestamp = data.timestamp;
282                     } else if (data.key === 'to') {
283                         toTimestamp = data.timestamp;
284                     }
285                 });
287                 // Display an error if the from date is later than the do date.
288                 if (toTimestamp > 0 && fromTimestamp > toTimestamp) {
289                     const warningdiv = document.getElementById('dates-filter-warning');
290                     warningdiv.classList.remove('hidden');
291                     warningdiv.classList.add('d-block');
292                 } else {
293                     filtersForm.elements['datefrom[timestamp]'].value = fromTimestamp;
294                     filtersForm.elements['datefrom[enabled]'].value = fromEnabled;
295                     filtersForm.elements['dateto[timestamp]'].value = toTimestamp;
296                     filtersForm.elements['dateto[enabled]'].value = toEnabled;
298                     // Submit the filter values and re-generate report.
299                     submitWithFilter('#filter-dates-popover');
300                 }
301             });
302         }
303     });
305     jqRoot.on(CustomEvents.events.activate, Selectors.filters.date.calendariconfrom, function() {
306         updateCalendarPosition(Selectors.filters.date.calendariconfrom);
307     });
309     jqRoot.on(CustomEvents.events.activate, Selectors.filters.date.calendariconto, function() {
310         updateCalendarPosition(Selectors.filters.date.calendariconto);
311     });
312 };