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
 * Handles edit penalty form.
18
 *
19
 * @module     gradepenalty_duedate/edit_penalty_form
20
 * @copyright  2024 Catalyst IT
21
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22
 */
23
 
24
import * as notification from 'core/notification';
25
import Fragment from 'core/fragment';
26
import Templates from 'core/templates';
27
 
28
/**
29
 * Rule js class.
30
 */
31
class PenaltyRule {
32
    constructor(
33
        overdueby = 0,
34
        penalty = 0,
35
    ) {
36
        this.overdueby = overdueby;
37
        this.penalty = penalty;
38
    }
39
}
40
 
41
/**
42
 * Selectors
43
 */
44
const SELECTORS = {
45
    FORM_CONTAINER: '#penalty_rule_form_container',
46
    ACTION_MENU: '.action-menu',
47
    ADD_BUTTON: '#addrulebutton',
48
    INSERT_BUTTON: '.insertbelow',
49
    DELETE_BUTTON: '.deleterulebuttons',
50
    DELETE_ALL_BUTTON_CONTAINER: '#deleteallrulesbuttoncontainer',
51
};
52
 
53
/**
54
 * Register click event for delete and insert buttons.
55
 */
56
const registerEventListeners = () => {
57
    // Find all action menus in penalty rule form.
58
    const container = document.querySelector(SELECTORS.FORM_CONTAINER);
59
    container.addEventListener('click', (e) => {
60
        if (e.target.closest(SELECTORS.DELETE_BUTTON)) {
61
            e.preventDefault();
62
            deleteRule(e.target);
63
 
64
            return;
65
        }
66
 
67
        if (e.target.closest(SELECTORS.INSERT_BUTTON)) {
68
            e.preventDefault();
69
            insertRule(e.target);
70
 
71
            return;
72
        }
73
    });
74
 
75
    document.querySelector(SELECTORS.ADD_BUTTON).addEventListener('click', (e) => {
76
        e.preventDefault();
77
        insertRuleAtIndex(container.querySelectorAll(SELECTORS.ACTION_MENU).length);
78
 
79
        return;
80
    });
81
};
82
 
83
/**
84
 * Delete a rule group represented by thenode.
85
 *
86
 * @param {NodeElement} target
87
 */
88
const deleteRule = (target) => {
89
    // Get all form data.
90
    const {contextid, penaltyRules, finalPenaltyRule} = buildFormParams();
91
    const ruleNumber = getRuleNumber(target);
92
 
93
    // Remove the penalty rule.
94
    const updatedPenaltyRules = penaltyRules.filter((rule, index) => index !== ruleNumber);
95
 
96
    loadPenaltyRuleForm(
97
        contextid,
98
        updatedPenaltyRules,
99
        finalPenaltyRule,
100
    );
101
};
102
 
103
/**
104
 * Insert a rule group below the clicked button.
105
 *
106
 * @param {NodeElement} target
107
 */
108
const insertRule = (target) => insertRuleAtIndex(getRuleNumber(target) + 1);
109
 
110
/**
111
 * Add a new rule group at the specified index.
112
 *
113
 * @param {Number} ruleNumber
114
 */
115
const insertRuleAtIndex = (ruleNumber) => {
116
    // Get all form data.
117
    const {contextid, penaltyRules, finalPenaltyRule} = buildFormParams();
118
 
119
    // Insert a new penalty rule.
120
    penaltyRules.splice(ruleNumber, 0, new PenaltyRule());
121
 
122
    loadPenaltyRuleForm(
123
        contextid,
124
        penaltyRules,
125
        finalPenaltyRule,
126
    );
127
};
128
 
129
/**
130
 * Get the rule number from the target.
131
 *
132
 * @param {Object} target
133
 * @return {Number} rule number
134
 */
135
const getRuleNumber = (target) => {
136
    const allRules = target
137
        .closest(SELECTORS.FORM_CONTAINER)
138
        .querySelectorAll(SELECTORS.ACTION_MENU);
139
 
140
    const foundIndex = Array.prototype.findIndex.call(
141
        allRules,
142
        (element) => element.contains(target),
143
    );
144
 
145
    if (foundIndex === -1) {
146
        throw new Error('Rule number not found on target', target);
147
    }
148
 
149
    return foundIndex;
150
};
151
 
152
/**
153
 * Build form parameters for loading fragment.
154
 *
155
 * @return {Object} form params
156
 */
157
const buildFormParams = () => {
158
    // Get the penalty rule form in its container.
159
    const container = document.querySelector(SELECTORS.FORM_CONTAINER);
160
    const form = container.querySelector('form');
161
 
162
    // Get all form data
163
    const formData = new FormData(form);
164
 
165
    // Get context id.
166
    const contextid = formData.get('contextid');
167
 
168
    // Get group count.
169
    const groupCount = formData.get('rulegroupcount');
170
 
171
    // Create list of penalty rules.
172
    const penaltyRules = [];
173
 
174
    // Current penalty rules.
175
 
176
    for (let i = 0; i < groupCount; i++) {
177
        penaltyRules.push(new PenaltyRule(
178
            formData.get(`overdueby[${i}][number]`) * formData.get(`overdueby[${i}][timeunit]`),
179
            formData.get(`penalty[${i}]`)
180
        ));
181
    }
182
 
183
    return {
184
        contextid,
185
        penaltyRules,
186
        finalPenaltyRule: formData.get('finalpenaltyrule'),
187
    };
188
};
189
 
190
/**
191
 * Load the penalty rule form.
192
 *
193
 * @param {Number} contextId
194
 * @param {Array} penaltyRules
195
 * @param {Number} finalPenaltyRule
196
 */
197
const loadPenaltyRuleForm = (
198
    contextId,
199
    penaltyRules,
200
    finalPenaltyRule,
201
) => {
202
    // Disable the form while loading to improve UX.
203
    const container = document.querySelector(SELECTORS.FORM_CONTAINER);
204
    const form = container.querySelector('form');
205
    form.querySelectorAll('input, select').forEach(input => {
206
        input.disabled = true;
207
    });
208
 
209
    // Disable the add rule button.
210
    const addButton = document.querySelector(SELECTORS.ADD_BUTTON);
211
    if (addButton) {
212
        addButton.disabled = true;
213
    }
214
 
215
    // Disable the delete all rules button.
216
    const deleteAllButton = document.querySelector(SELECTORS.DELETE_ALL_BUTTON_CONTAINER).querySelector('button');
217
    if (deleteAllButton) {
218
        deleteAllButton.disabled = true;
219
    }
220
 
221
    // Replace the form with the new form.
222
    Fragment.loadFragment(
223
        'gradepenalty_duedate',
224
        'penalty_rule_form',
225
        contextId,
226
        {
227
            penaltyrules: JSON.stringify(penaltyRules),
228
            finalpenaltyrule: finalPenaltyRule,
229
        },
230
    )
231
        .then((html, js) => {
232
            Templates.replaceNodeContents(document.querySelector(SELECTORS.FORM_CONTAINER), html, js);
233
 
234
            if (addButton) {
235
                addButton.disabled = false;
236
            }
237
 
238
            if (deleteAllButton) {
239
                deleteAllButton.disabled = false;
240
            }
241
            return;
242
        })
243
        .catch(notification.exception);
244
 
245
 
246
};
247
 
248
/**
249
 * Initialize the js.
250
 */
251
export const init = () => {
252
    registerEventListeners();
253
};