Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1441 ariadna 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
 * A small dropdown to filter users.
18
 *
19
 * @module    core_course/actionbar/initials
20
 * @copyright 2022 Mathew May <mathew.solutions>
21
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22
 */
23
 
24
import Pending from 'core/pending';
25
import * as Url from 'core/url';
26
import CustomEvents from "core/custom_interaction_events";
27
import Dropdown from 'theme_boost/bootstrap/dropdown';
28
 
29
/**
30
 * Whether the event listener has already been registered for this module.
31
 *
32
 * @type {boolean}
33
 */
34
let registered = false;
35
 
36
// Contain our selectors within this file until they could be of use elsewhere.
37
const selectors = {
38
    pageListItem: 'page-item',
39
    pageClickableItem: '.page-link',
40
    activeItem: 'active',
41
    formDropdown: '.initialsdropdownform',
42
    parentDomNode: '.initials-selector',
43
    firstInitial: 'firstinitial',
44
    lastInitial: 'lastinitial',
45
    initialBars: '.initialbar', // Both first and last name use this class.
46
    targetButton: 'initialswidget',
47
    formItems: {
48
        type: 'submit',
49
        save: 'save',
50
        cancel: 'cancel'
51
    }
52
};
53
 
54
/**
55
 * Our initial hook into the module which will eventually allow us to handle the dropdown initials bar form.
56
 *
57
 * @param {String} callingLink The link to redirect upon form submission.
58
 * @param {String} firstInitialParam The URL parameter to set for the first name initial.
59
 * @param {String} lastInitialParam The URL parameter to set for the last name initial.
60
 * @param {Array} additionalParams Any additional parameters to set for the URL.
61
 */
62
export const init = (callingLink, firstInitialParam = 'sifirst',
63
        lastInitialParam = 'silast', additionalParams = []) => {
64
    if (registered) {
65
        return;
66
    }
67
    const pendingPromise = new Pending();
68
    registerListenerEvents(callingLink, firstInitialParam, lastInitialParam, additionalParams);
69
    // BS events always bubble so, we need to listen for the event higher up the chain.
70
    document.querySelector(selectors.parentDomNode).addEventListener('shown.bs.dropdown', () => {
71
        document.querySelector(selectors.pageClickableItem).focus({preventScroll: true});
72
    });
73
    pendingPromise.resolve();
74
    registered = true;
75
};
76
 
77
/**
78
 * Register event listeners.
79
 *
80
 * @param {String} callingLink The link to redirect upon form submission.
81
 * @param {String} firstInitialParam The URL parameter to set for the first name initial.
82
 * @param {String} lastInitialParam The URL parameter to set for the last name initial.
83
 * @param {Array} additionalParams Any additional parameters to set for the URL.
84
 */
85
const registerListenerEvents = (callingLink, firstInitialParam = 'sifirst',
86
        lastInitialParam = 'silast', additionalParams = []) => {
87
    const events = [
88
        'click',
89
        CustomEvents.events.activate,
90
        CustomEvents.events.keyboardActivate
91
    ];
92
    CustomEvents.define(document, events);
93
 
94
    // Register events.
95
    events.forEach((event) => {
96
        document.addEventListener(event, (e) => {
97
            // Always fetch the latest information when we click as state is a fickle thing.
98
            let {firstActive, lastActive, sifirst, silast} = onClickVariables();
99
            let itemToReset = '';
100
 
101
            // Prevent the usual form behaviour.
102
            if (e.target.closest(selectors.formDropdown)) {
103
                e.preventDefault();
104
            }
105
 
106
            // Handle the state of active initials before form submission.
107
            if (e.target.closest(`${selectors.formDropdown} .${selectors.pageListItem}`)) {
108
                // Ensure the li items don't cause weird clicking emptying out the form.
109
                if (e.target.classList.contains(selectors.pageListItem)) {
110
                    return;
111
                }
112
 
113
                const initialsBar = e.target.closest(selectors.initialBars); // Find out which initial bar we are in.
114
 
115
                // We want to find the current active item in the menu area the user selected.
116
                // We also want to fetch the raw item out of the array for instant manipulation.
117
                if (initialsBar.classList.contains(selectors.firstInitial)) {
118
                    sifirst = e.target;
119
                    itemToReset = firstActive;
120
                } else {
121
                    silast = e.target;
122
                    itemToReset = lastActive;
123
                }
124
                swapActiveItems(itemToReset, e);
125
            }
126
 
127
            // Handle form submissions.
128
            if (e.target.closest(`${selectors.formDropdown}`) && e.target.type === selectors.formItems.type) {
129
                if (e.target.dataset.action === selectors.formItems.save) {
130
                    // Ensure we strip out the value (All) as it messes with the PHP side of the initials bar.
131
                    // Then we will redirect the user back onto the page with new filters applied.
132
                    const params = {
133
                        'id': e.target.closest(selectors.formDropdown).dataset.courseid,
134
                        [firstInitialParam]: sifirst.parentElement.classList.contains('initialbarall') ? '' : sifirst.value,
135
                        [lastInitialParam]: silast.parentElement.classList.contains('initialbarall') ? '' : silast.value,
136
                    };
137
 
138
                    // If additional parameters are passed, add them here (overriding any already set above).
139
                    for (const [key, value] of Object.entries(additionalParams)) {
140
                        params[key] = value;
141
                    }
142
                    window.location = Url.relativeUrl(callingLink, params);
143
                }
144
                if (e.target.dataset.action === selectors.formItems.cancel) {
145
                    Dropdown.getOrCreateInstance(document.querySelector(`.${selectors.targetButton}`)).toggle();
146
                }
147
            }
148
        });
149
    });
150
};
151
 
152
/**
153
 * A small abstracted helper function which allows us to ensure we have up-to-date lists of nodes.
154
 *
155
 * @returns {{firstActive: HTMLElement, lastActive: HTMLElement, sifirst: ?String, silast: ?String}}
156
 */
157
const onClickVariables = () => {
158
    // Ensure we have an up-to-date initials bar.
159
    const firstItems = [...document.querySelectorAll(`.${selectors.firstInitial} li`)];
160
    const lastItems = [...document.querySelectorAll(`.${selectors.lastInitial} li`)];
161
    const firstActive = firstItems.filter((item) => item.classList.contains(selectors.activeItem))[0];
162
    const lastActive = lastItems.filter((item) => item.classList.contains(selectors.activeItem))[0];
163
    // Ensure we retain both of the selections from a previous instance.
164
    let sifirst = firstActive.querySelector(selectors.pageClickableItem);
165
    let silast = lastActive.querySelector(selectors.pageClickableItem);
166
    return {firstActive, lastActive, sifirst, silast};
167
};
168
 
169
/**
170
 * Given we are provided the old li and current click event, swap around the active properties.
171
 *
172
 * @param {HTMLElement} itemToReset
173
 * @param {Event} e
174
 */
175
const swapActiveItems = (itemToReset, e) => {
176
    itemToReset.classList.remove(selectors.activeItem);
177
    itemToReset.querySelector(selectors.pageClickableItem).ariaCurrent = false;
178
 
179
    // Set the select item as the current item.
180
    const itemToSetActive = e.target.parentElement;
181
    itemToSetActive.classList.add(selectors.activeItem);
182
    e.target.ariaCurrent = true;
183
};