Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
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
 * Field controller for choicedropdown field.
18
 *
19
 * @module core_form/choicedropdown
20
 * @copyright 2023 Ferran Recio <ferran@moodle.com>
21
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22
 */
23
 
24
import {getDropdownStatus} from 'core/local/dropdown/status';
25
import {markFormAsDirty} from 'core_form/changechecker';
26
 
27
const Classes = {
28
    notClickable: 'not-clickable',
29
    hidden: 'd-none',
30
};
31
 
32
/**
33
 * Internal form element class.
34
 *
35
 * @private
36
 * @class     FieldController
37
 * @copyright  2023 Ferran Recio <ferran@moodle.com>
38
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
39
 */
40
class FieldController {
41
    /**
42
     * Class constructor.
43
     *
44
     * @param {String} elementId Form element id
45
     */
46
    constructor(elementId) {
47
        this.elementId = elementId;
48
        this.mainSelect = document.getElementById(this.elementId);
49
        this.dropdown = getDropdownStatus(`[data-form-controls="${this.elementId}"]`);
50
        this.dropdown.getElement().classList.remove(Classes.hidden);
51
    }
52
 
53
    /**
54
     * Add form element event listener.
55
     */
56
    addEventListeners() {
57
        this.dropdown.getElement().addEventListener(
58
            'change',
59
            this.updateSelect.bind(this)
60
        );
61
        // Click on a dropdown link can trigger a wrong dirty form reload warning.
62
        this.dropdown.getElement().addEventListener(
63
            'click',
64
            (event) => event.preventDefault()
65
        );
66
        this.mainSelect.addEventListener(
67
            'change',
68
            this.updateDropdown.bind(this)
69
        );
70
        // Enabling or disabling the select does not trigger any JS event.
71
        const observerCallback = (mutations) => {
72
            mutations.forEach((mutation) => {
73
                if (mutation.type !== 'attributes' || mutation.attributeName !== 'disabled') {
74
                    return;
75
                }
76
                this.updateDropdown();
77
            });
78
        };
79
        new MutationObserver(observerCallback).observe(
80
            this.mainSelect,
81
            {attributeFilter: ['disabled']}
82
        );
83
    }
84
 
85
    /**
86
     * Check if the field is disabled.
87
     * @returns {Boolean}
88
     */
89
    isDisabled() {
90
        return this.mainSelect?.hasAttribute('disabled');
91
    }
92
 
93
    /**
94
     * Update selected option preview in form.
95
     */
96
    async updateDropdown() {
97
        this.dropdown.setButtonDisabled(this.isDisabled());
98
        if (this.dropdown.getSelectedValue() == this.mainSelect.value) {
99
            return;
100
        }
101
        this.dropdown.setSelectedValue(this.mainSelect.value);
102
    }
103
 
104
    /**
105
     * Update selected option preview in form.
106
     */
107
    async updateSelect() {
108
        if (this.dropdown.getSelectedValue() == this.mainSelect.value) {
109
            return;
110
        }
111
        this.mainSelect.value = this.dropdown.getSelectedValue();
112
        markFormAsDirty(this.mainSelect.closest('form'));
113
        // Change the select element via JS does not trigger the standard change event.
114
        this.mainSelect.dispatchEvent(new Event('change'));
115
    }
116
 
117
    /**
118
     * Disable the choice dialog and convert it into a regular select field.
119
     */
120
    disableInteractiveDialog() {
121
        this.mainSelect?.classList.remove(Classes.hidden);
122
        const dropdownElement = this.dropdown.getElement();
123
        dropdownElement.classList.add(Classes.hidden);
124
    }
125
 
126
    /**
127
     * Check if the field has a force dialog attribute.
128
    //  *
129
     * The force dialog is a setting to force the javascript control even in
130
     * behat test.
131
     *
132
     * @returns {Boolean} if the dialog modal should be forced or not
133
     */
134
    hasForceDialog() {
135
        return !!this.mainSelect?.dataset.forceDialog;
136
    }
137
}
138
 
139
/**
140
 * Initialises a choice dialog field.
141
 *
142
 * @method init
143
 * @param {String} elementId Form element id
144
 * @listens event:uploadStarted
145
 * @listens event:uploadCompleted
146
 */
147
export const init = (elementId) => {
148
    const field = new FieldController(elementId);
149
    // This field is just a select wrapper. To optimize tests, we don't want to keep behat
150
    // waiting for extra loadings in this case. The set field steps are about testing other
151
    // stuff, not to test fancy javascript form fields. However, we keep the possibility of
152
    // testing the javascript part using behat when necessary.
153
    if (document.body.classList.contains('behat-site') && !field.hasForceDialog()) {
154
        field.disableInteractiveDialog();
155
        return;
156
    }
157
    field.addEventListeners();
158
};