| 1 | efrain | 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/>.
 | 
        
           |  |  | 15 |   | 
        
           |  |  | 16 | /**
 | 
        
           |  |  | 17 |  * Report builder columns sorting editor
 | 
        
           |  |  | 18 |  *
 | 
        
           |  |  | 19 |  * @module      core_reportbuilder/local/editor/sorting
 | 
        
           |  |  | 20 |  * @copyright   2021 David Matamoros <davidmc@moodle.com>
 | 
        
           |  |  | 21 |  * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 | 
        
           |  |  | 22 |  */
 | 
        
           |  |  | 23 |   | 
        
           |  |  | 24 | "use strict";
 | 
        
           |  |  | 25 |   | 
        
           |  |  | 26 | import 'core/inplace_editable';
 | 
        
           |  |  | 27 | import Notification from 'core/notification';
 | 
        
           |  |  | 28 | import Pending from 'core/pending';
 | 
        
           |  |  | 29 | import {subscribe} from 'core/pubsub';
 | 
        
           |  |  | 30 | import SortableList from 'core/sortable_list';
 | 
        
           |  |  | 31 | import {getString} from 'core/str';
 | 
        
           |  |  | 32 | import {add as addToast} from 'core/toast';
 | 
        
           |  |  | 33 | import * as reportSelectors from 'core_reportbuilder/local/selectors';
 | 
        
           |  |  | 34 | import {reorderColumnSorting, toggleColumnSorting} from 'core_reportbuilder/local/repository/sorting';
 | 
        
           |  |  | 35 | import Templates from 'core/templates';
 | 
        
           |  |  | 36 | import {dispatchEvent} from 'core/event_dispatcher';
 | 
        
           |  |  | 37 | import * as reportEvents from 'core_reportbuilder/local/events';
 | 
        
           |  |  | 38 |   | 
        
           |  |  | 39 | // These constants match PHP consts SORT_ASC, SORT_DESC.
 | 
        
           |  |  | 40 | const SORTORDER = {
 | 
        
           |  |  | 41 |     ASCENDING: 4,
 | 
        
           |  |  | 42 |     DESCENDING: 3,
 | 
        
           |  |  | 43 | };
 | 
        
           |  |  | 44 |   | 
        
           |  |  | 45 | /**
 | 
        
           |  |  | 46 |  * Reload sorting settings region
 | 
        
           |  |  | 47 |  *
 | 
        
           |  |  | 48 |  * @param {Object} context
 | 
        
           |  |  | 49 |  * @return {Promise}
 | 
        
           |  |  | 50 |  */
 | 
        
           |  |  | 51 | const reloadSettingsSortingRegion = context => {
 | 
        
           |  |  | 52 |     const pendingPromise = new Pending('core_reportbuilder/sorting:reload');
 | 
        
           |  |  | 53 |     const settingsSortingRegion = document.querySelector(reportSelectors.regions.settingsSorting);
 | 
        
           |  |  | 54 |   | 
        
           |  |  | 55 |     return Templates.renderForPromise('core_reportbuilder/local/settings/sorting', {sorting: context})
 | 
        
           |  |  | 56 |         .then(({html, js}) => {
 | 
        
           |  |  | 57 |             Templates.replaceNode(settingsSortingRegion, html, js);
 | 
        
           |  |  | 58 |             return pendingPromise.resolve();
 | 
        
           |  |  | 59 |         });
 | 
        
           |  |  | 60 | };
 | 
        
           |  |  | 61 |   | 
        
           |  |  | 62 | /**
 | 
        
           |  |  | 63 |  * Updates column sorting
 | 
        
           |  |  | 64 |  *
 | 
        
           |  |  | 65 |  * @param {Element} reportElement
 | 
        
           |  |  | 66 |  * @param {Element} element
 | 
        
           |  |  | 67 |  * @param {Number} sortenabled
 | 
        
           |  |  | 68 |  * @param {Number} sortdirection
 | 
        
           |  |  | 69 |  * @return {Promise}
 | 
        
           |  |  | 70 |  */
 | 
        
           |  |  | 71 | const updateSorting = (reportElement, element, sortenabled, sortdirection) => {
 | 
        
           | 1441 | ariadna | 72 |     const columnSortContainer = element.closest(reportSelectors.regions.activeColumnSort);
 | 
        
           |  |  | 73 |     const {columnSortId, columnSortName} = columnSortContainer.dataset;
 | 
        
           | 1 | efrain | 74 |   | 
        
           | 1441 | ariadna | 75 |     return toggleColumnSorting(reportElement.dataset.reportId, columnSortId, sortenabled, sortdirection)
 | 
        
           | 1 | efrain | 76 |         .then(reloadSettingsSortingRegion)
 | 
        
           | 1441 | ariadna | 77 |         .then(() => getString('columnsortupdated', 'core_reportbuilder', columnSortName))
 | 
        
           | 1 | efrain | 78 |         .then(addToast)
 | 
        
           |  |  | 79 |         .then(() => {
 | 
        
           |  |  | 80 |             dispatchEvent(reportEvents.tableReload, {}, reportElement);
 | 
        
           |  |  | 81 |             return null;
 | 
        
           |  |  | 82 |         });
 | 
        
           |  |  | 83 | };
 | 
        
           |  |  | 84 |   | 
        
           |  |  | 85 | /**
 | 
        
           |  |  | 86 |  * Initialise module
 | 
        
           |  |  | 87 |  *
 | 
        
           |  |  | 88 |  * @param {Boolean} initialized Ensure we only add our listeners once
 | 
        
           |  |  | 89 |  */
 | 
        
           |  |  | 90 | export const init = (initialized) => {
 | 
        
           |  |  | 91 |     if (initialized) {
 | 
        
           |  |  | 92 |         return;
 | 
        
           |  |  | 93 |     }
 | 
        
           |  |  | 94 |   | 
        
           |  |  | 95 |     // Update sorting region each time report columns are updated (added or removed).
 | 
        
           |  |  | 96 |     subscribe(reportEvents.publish.reportColumnsUpdated, data => reloadSettingsSortingRegion(data)
 | 
        
           |  |  | 97 |         .catch(Notification.exception)
 | 
        
           |  |  | 98 |     );
 | 
        
           |  |  | 99 |   | 
        
           |  |  | 100 |     document.addEventListener('click', event => {
 | 
        
           |  |  | 101 |   | 
        
           |  |  | 102 |         // Enable/disable sorting on columns.
 | 
        
           |  |  | 103 |         const toggleSorting = event.target.closest(reportSelectors.actions.reportToggleColumnSort);
 | 
        
           |  |  | 104 |         if (toggleSorting) {
 | 
        
           |  |  | 105 |             event.preventDefault();
 | 
        
           |  |  | 106 |   | 
        
           |  |  | 107 |             const pendingPromise = new Pending('core_reportbuilder/sorting:toggle');
 | 
        
           |  |  | 108 |             const reportElement = toggleSorting.closest(reportSelectors.regions.report);
 | 
        
           | 1441 | ariadna | 109 |             const columnSortContainer = toggleSorting.closest(reportSelectors.regions.activeColumnSort);
 | 
        
           |  |  | 110 |             const sortdirection = parseInt(columnSortContainer.dataset.columnSortDirection);
 | 
        
           | 1 | efrain | 111 |   | 
        
           |  |  | 112 |             updateSorting(reportElement, toggleSorting, toggleSorting.checked, sortdirection)
 | 
        
           |  |  | 113 |                 .then(() => {
 | 
        
           |  |  | 114 |                     // Re-focus the toggle sorting element after reloading the region.
 | 
        
           |  |  | 115 |                     const toggleSortingElement = document.getElementById(toggleSorting.id);
 | 
        
           |  |  | 116 |                     toggleSortingElement?.focus();
 | 
        
           |  |  | 117 |                     return pendingPromise.resolve();
 | 
        
           |  |  | 118 |                 })
 | 
        
           |  |  | 119 |                 .catch(Notification.exception);
 | 
        
           |  |  | 120 |         }
 | 
        
           |  |  | 121 |   | 
        
           |  |  | 122 |         // Change column sort direction.
 | 
        
           |  |  | 123 |         const toggleSortDirection = event.target.closest(reportSelectors.actions.reportToggleColumnSortDirection);
 | 
        
           |  |  | 124 |         if (toggleSortDirection) {
 | 
        
           |  |  | 125 |             event.preventDefault();
 | 
        
           |  |  | 126 |   | 
        
           |  |  | 127 |             const pendingPromise = new Pending('core_reportbuilder/sorting:direction');
 | 
        
           |  |  | 128 |             const reportElement = toggleSortDirection.closest(reportSelectors.regions.report);
 | 
        
           | 1441 | ariadna | 129 |             const columnSortContainer = toggleSortDirection.closest(reportSelectors.regions.activeColumnSort);
 | 
        
           |  |  | 130 |             const toggleSorting = columnSortContainer.querySelector(reportSelectors.actions.reportToggleColumnSort);
 | 
        
           | 1 | efrain | 131 |   | 
        
           | 1441 | ariadna | 132 |             let sortdirection = parseInt(columnSortContainer.dataset.columnSortDirection);
 | 
        
           | 1 | efrain | 133 |             if (sortdirection === SORTORDER.ASCENDING) {
 | 
        
           |  |  | 134 |                 sortdirection = SORTORDER.DESCENDING;
 | 
        
           |  |  | 135 |             } else if (sortdirection === SORTORDER.DESCENDING) {
 | 
        
           |  |  | 136 |                 sortdirection = SORTORDER.ASCENDING;
 | 
        
           |  |  | 137 |             }
 | 
        
           |  |  | 138 |   | 
        
           |  |  | 139 |             updateSorting(reportElement, toggleSortDirection, toggleSorting.checked, sortdirection)
 | 
        
           |  |  | 140 |                 .then(() => {
 | 
        
           |  |  | 141 |                     // Re-focus the toggle sort direction element after reloading the region.
 | 
        
           |  |  | 142 |                     const toggleSortDirectionElement = document.getElementById(toggleSortDirection.id);
 | 
        
           |  |  | 143 |                     toggleSortDirectionElement?.focus();
 | 
        
           |  |  | 144 |                     return pendingPromise.resolve();
 | 
        
           |  |  | 145 |                 })
 | 
        
           |  |  | 146 |                 .catch(Notification.exception);
 | 
        
           |  |  | 147 |         }
 | 
        
           |  |  | 148 |     });
 | 
        
           |  |  | 149 |   | 
        
           | 1441 | ariadna | 150 |     // Initialize sortable list to handle column sorting moving.
 | 
        
           |  |  | 151 |     const columnsSortingSelector = `${reportSelectors.regions.settingsSorting} ul`;
 | 
        
           |  |  | 152 |     const columnsSortingSortableList = new SortableList(columnsSortingSelector, {isHorizontal: false});
 | 
        
           | 1 | efrain | 153 |     columnsSortingSortableList.getElementName = element => Promise.resolve(element.data('columnSortName'));
 | 
        
           |  |  | 154 |   | 
        
           | 1441 | ariadna | 155 |     document.addEventListener(SortableList.EVENTS.elementDrop, event => {
 | 
        
           |  |  | 156 |         const toggleSortOrder = event.target.closest(`${columnsSortingSelector} ${reportSelectors.regions.activeColumnSort}`);
 | 
        
           |  |  | 157 |         if (toggleSortOrder && event.detail.positionChanged) {
 | 
        
           | 1 | efrain | 158 |             const pendingPromise = new Pending('core_reportbuilder/sorting:reorder');
 | 
        
           |  |  | 159 |   | 
        
           | 1441 | ariadna | 160 |             const reportElement = toggleSortOrder.closest(reportSelectors.regions.report);
 | 
        
           |  |  | 161 |             const {columnSortId, columnSortPosition, columnSortName} = toggleSortOrder.dataset;
 | 
        
           |  |  | 162 |   | 
        
           | 1 | efrain | 163 |             // Select target position, if moving to the end then count number of element siblings.
 | 
        
           | 1441 | ariadna | 164 |             let targetColumnSortPosition = event.detail.targetNextElement.data('columnSortPosition')
 | 
        
           |  |  | 165 |                 || event.detail.element.siblings().length + 2;
 | 
        
           |  |  | 166 |             if (targetColumnSortPosition > columnSortPosition) {
 | 
        
           | 1 | efrain | 167 |                 targetColumnSortPosition--;
 | 
        
           |  |  | 168 |             }
 | 
        
           |  |  | 169 |   | 
        
           |  |  | 170 |             // Re-order column sorting, giving drop event transition time to finish.
 | 
        
           | 1441 | ariadna | 171 |             const reorderPromise = reorderColumnSorting(reportElement.dataset.reportId, columnSortId, targetColumnSortPosition);
 | 
        
           | 1 | efrain | 172 |             Promise.all([reorderPromise, new Promise(resolve => setTimeout(resolve, 1000))])
 | 
        
           |  |  | 173 |                 .then(([data]) => reloadSettingsSortingRegion(data))
 | 
        
           | 1441 | ariadna | 174 |                 .then(() => getString('columnsortupdated', 'core_reportbuilder', columnSortName))
 | 
        
           | 1 | efrain | 175 |                 .then(addToast)
 | 
        
           |  |  | 176 |                 .then(() => {
 | 
        
           |  |  | 177 |                     dispatchEvent(reportEvents.tableReload, {}, reportElement);
 | 
        
           |  |  | 178 |                     return pendingPromise.resolve();
 | 
        
           |  |  | 179 |                 })
 | 
        
           |  |  | 180 |                 .catch(Notification.exception);
 | 
        
           |  |  | 181 |         }
 | 
        
           |  |  | 182 |     });
 | 
        
           |  |  | 183 | };
 |