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