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
 * AJAX helper for the tag management page.
18
 *
19
 * @module     core/tag
20
 * @copyright  2015 Marina Glancy
21
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22
 * @since      3.0
23
 */
24
 
25
import $ from 'jquery';
26
import {call as fetchMany} from 'core/ajax';
27
import * as Notification from 'core/notification';
28
import * as Templates from 'core/templates';
29
import {getString} from 'core/str';
30
import * as ModalEvents from 'core/modal_events';
31
import Pending from 'core/pending';
32
import SaveCancelModal from 'core/modal_save_cancel';
33
import Config from 'core/config';
34
import * as reportSelectors from 'core_reportbuilder/local/selectors';
35
 
36
const getTagIndex = (tagindex) => fetchMany([{
37
    methodname: 'core_tag_get_tagindex',
38
    args: {tagindex}
39
}])[0];
40
 
41
const getCheckedTags = (root) => root.querySelectorAll('[data-togglegroup="report-select-all"][data-toggle="slave"]:checked');
42
 
43
const handleCombineRequest = async(tagManagementCombine) => {
44
    const pendingPromise = new Pending('core/tag:tag-management-combine');
45
    const form = tagManagementCombine.closest('form');
46
 
47
    const reportElement = document.querySelector(reportSelectors.regions.report);
48
    const checkedTags = getCheckedTags(reportElement);
49
 
50
    if (checkedTags.length <= 1) {
51
        // We need at least 2 tags to combine them.
52
        Notification.alert(
53
            getString('combineselected', 'tag'),
54
            getString('selectmultipletags', 'tag'),
55
            getString('ok'),
56
        );
57
 
58
        return;
59
    }
60
 
61
    const tags = Array.from(checkedTags.values()).map((tag) => {
62
        const namedElement = document.querySelector(`.inplaceeditable[data-itemtype=tagname][data-itemid="${tag.value}"]`);
63
        return {
64
            id: tag.value,
65
            name: namedElement.dataset.value,
66
        };
67
    });
68
 
69
    const modal = await SaveCancelModal.create({
70
        title: getString('combineselected', 'tag'),
71
        buttons: {
72
            save: getString('continue', 'core'),
73
        },
74
        body: Templates.render('core_tag/combine_tags', {tags}),
75
        show: true,
76
        removeOnClose: true,
77
    });
78
 
79
    // Handle save event.
80
    modal.getRoot().on(ModalEvents.save, (e) => {
81
        e.preventDefault();
82
 
83
        // Append this temp element in the form in the tags list, not the form in the modal. Confusing, right?!?
84
        const tempElement = document.createElement('input');
85
        tempElement.hidden = true;
86
        tempElement.name = tagManagementCombine.name;
87
        form.append(tempElement);
88
 
89
        // Append selected tags element.
90
        const tagsElement = document.createElement('input');
91
        tagsElement.hidden = true;
92
        tagsElement.name = 'tagschecked';
93
        tagsElement.value = [...checkedTags].map(check => check.value).join(',');
94
        form.append(tagsElement);
95
 
96
        // Get the selected tag from the modal.
97
        var maintag = $('input[name=maintag]:checked', '#combinetags_form').val();
98
        // Append this in the tags list form.
99
        $("<input type='hidden'/>").attr('name', 'maintag').attr('value', maintag).appendTo(form);
100
        // Submit the tags list form.
101
        form.submit();
102
    });
103
 
104
    await modal.getBodyPromise();
105
    // Tick the first option.
106
    const firstOption = document.querySelector('#combinetags_form input[type=radio]');
107
    firstOption.focus();
108
    firstOption.checked = true;
109
 
110
    pendingPromise.resolve();
111
 
112
    return;
113
};
114
 
115
const addStandardTags = async() => {
116
    var pendingPromise = new Pending('core/tag:addstandardtag');
117
 
118
    const modal = await SaveCancelModal.create({
119
        title: getString('addotags', 'tag'),
120
        body: Templates.render('core_tag/add_tags', {
121
            actionurl: window.location.href,
122
            sesskey: M.cfg.sesskey,
123
        }),
124
        buttons: {
125
            save: getString('continue', 'core'),
126
        },
127
        removeOnClose: true,
128
        show: true,
129
    });
130
 
131
    // Handle save event.
132
    modal.getRoot().on(ModalEvents.save, (e) => {
133
        var tagsInput = $(e.currentTarget).find('#id_tagslist');
134
        var name = tagsInput.val().trim();
135
 
136
        // Set the text field's value to the trimmed value.
137
        tagsInput.val(name);
138
 
139
        // Add submit event listener to the form.
140
        var tagsForm = $('#addtags_form');
141
        tagsForm.on('submit', function(e) {
142
            // Validate the form.
143
            var form = $('#addtags_form');
144
            if (form[0].checkValidity() === false) {
145
                e.preventDefault();
146
                e.stopPropagation();
147
            }
148
            form.addClass('was-validated');
149
 
150
            // BS2 compatibility.
151
            $('[data-region="tagslistinput"]').addClass('error');
152
            var errorMessage = $('#id_tagslist_error_message');
153
            errorMessage.removeAttr('hidden');
154
            errorMessage.addClass('help-block');
155
        });
156
 
157
        // Try to submit the form.
158
        tagsForm.submit();
159
 
160
        return false;
161
    });
162
 
163
    await modal.getBodyPromise();
164
    pendingPromise.resolve();
165
};
166
 
167
const deleteSelectedTags = async(bulkActionDeleteButton) => {
168
    const form = bulkActionDeleteButton.closest('form');
169
 
170
    const reportElement = document.querySelector(reportSelectors.regions.report);
171
    const checkedTags = getCheckedTags(reportElement);
172
 
173
    if (!checkedTags.length) {
174
        return;
175
    }
176
 
177
    try {
178
        await Notification.saveCancelPromise(
179
            getString('delete'),
180
            getString('confirmdeletetags', 'tag'),
181
            getString('yes'),
182
            getString('no'),
183
        );
184
 
185
        // Append this temp element in the form in the tags list, not the form in the modal. Confusing, right?!?
186
        const tempElement = document.createElement('input');
187
        tempElement.hidden = true;
188
        tempElement.name = bulkActionDeleteButton.name;
189
        form.append(tempElement);
190
 
191
        // Append selected tags element.
192
        const tagsElement = document.createElement('input');
193
        tagsElement.hidden = true;
194
        tagsElement.name = 'tagschecked';
195
        tagsElement.value = [...checkedTags].map(check => check.value).join(',');
196
        form.append(tagsElement);
197
 
198
        form.submit();
199
    } catch {
200
        return;
201
    }
202
};
203
 
204
const deleteSelectedTag = async(button) => {
205
    try {
206
        await Notification.saveCancelPromise(
207
            getString('delete'),
208
            getString('confirmdeletetag', 'tag'),
209
            getString('yes'),
210
            getString('no'),
211
        );
212
 
213
        window.location.href = button.href;
214
    } catch {
215
        return;
216
    }
217
};
218
 
219
const deleteSelectedCollection = async(button) => {
220
    try {
221
        await Notification.saveCancelPromise(
222
            getString('delete'),
223
            getString('suredeletecoll', 'tag', button.dataset.collname),
224
            getString('yes'),
225
            getString('no'),
226
        );
227
 
228
        const redirectTarget = new URL(button.dataset.url);
229
        redirectTarget.searchParams.set('sesskey', Config.sesskey);
230
        window.location.href = redirectTarget;
231
    } catch {
232
        return;
233
    }
234
};
235
 
236
const addTagCollection = async(link) => {
237
    const pendingPromise = new Pending('core/tag:initManageCollectionsPage-addtagcoll');
238
    const href = link.dataset.url;
239
 
240
    const modal = await SaveCancelModal.create({
241
        title: getString('addtagcoll', 'tag'),
242
        buttons: {
243
            save: getString('create', 'core'),
244
        },
245
        body: Templates.render('core_tag/add_tag_collection', {
246
            actionurl: href,
247
            sesskey: M.cfg.sesskey,
248
        }),
249
        removeOnClose: true,
250
        show: true,
251
    });
252
 
253
    // Handle save event.
254
    modal.getRoot().on(ModalEvents.save, (e) => {
255
        const collectionInput = $(e.currentTarget).find('#addtagcoll_name');
256
        const name = collectionInput.val().trim();
257
        // Set the text field's value to the trimmed value.
258
        collectionInput.val(name);
259
 
260
        // Add submit event listener to the form.
261
        const form = $('#addtagcoll_form');
262
        form.on('submit', function(e) {
263
            // Validate the form.
264
            if (form[0].checkValidity() === false) {
265
                e.preventDefault();
266
                e.stopPropagation();
267
            }
268
            form.addClass('was-validated');
269
 
270
            // BS2 compatibility.
271
            $('[data-region="addtagcoll_nameinput"]').addClass('error');
272
            const errorMessage = $('#id_addtagcoll_name_error_message');
273
            errorMessage.removeAttr('hidden');
274
            errorMessage.addClass('help-block');
275
        });
276
 
277
        // Try to submit the form.
278
        form.submit();
279
 
280
        return false;
281
    });
282
 
283
    pendingPromise.resolve();
284
};
285
 
286
/**
287
 * Initialises tag index page.
288
 *
289
 * @method initTagindexPage
290
 */
291
export const initTagindexPage = async() => {
292
    document.addEventListener('click', async(e) => {
293
        const targetArea = e.target.closest('a[data-quickload="1"]');
294
        if (!targetArea) {
295
            return;
296
        }
297
        const tagArea = targetArea.closest('.tagarea[data-ta]');
298
        if (!tagArea) {
299
            return;
300
        }
301
 
302
        e.preventDefault();
303
        const pendingPromise = new Pending('core/tag:initTagindexPage');
304
 
305
        const query = targetArea.search.replace(/^\?/, '');
306
        const params = Object.fromEntries((new URLSearchParams(query)).entries());
307
 
308
        try {
309
            const data = await getTagIndex(params);
310
            const {html, js} = await Templates.renderForPromise('core_tag/index', data);
311
            Templates.replaceNode(tagArea, html, js);
312
        } catch (error) {
313
            Notification.exception(error);
314
        }
315
        pendingPromise.resolve();
316
    });
317
};
318
 
319
/**
320
 * Initialises tag management page.
321
 *
322
 * @method initManagePage
323
 */
324
export const initManagePage = () => {
325
    // Toggle row class when updating flag.
326
    $('body').on('updated', '[data-inplaceeditable][data-itemtype=tagflag]', function(e) {
327
        var row = $(e.target).closest('tr');
328
        row.toggleClass('table-warning', e.ajaxreturn.value === '1');
329
    });
330
 
331
    // Confirmation for bulk tag combine button.
332
    document.addEventListener('click', async(e) => {
333
        const tagManagementCombine = e.target.closest('#tag-management-combine');
334
        if (tagManagementCombine) {
335
            e.preventDefault();
336
            handleCombineRequest(tagManagementCombine);
337
        }
338
 
339
        if (e.target.closest('[data-action="addstandardtag"]')) {
340
            e.preventDefault();
341
            addStandardTags();
342
        }
343
 
344
        const bulkActionDeleteButton = e.target.closest('#tag-management-delete');
345
        if (bulkActionDeleteButton) {
346
            e.preventDefault();
347
            deleteSelectedTags(bulkActionDeleteButton);
348
        }
349
 
350
        const rowDeleteButton = e.target.closest('.tagdelete');
351
        if (rowDeleteButton) {
352
            e.preventDefault();
353
            deleteSelectedTag(rowDeleteButton);
354
        }
355
    });
356
 
357
    // When user changes tag name to some name that already exists suggest to combine the tags.
358
    $('body').on('updatefailed', '[data-inplaceeditable][data-itemtype=tagname]', async(e) => {
359
        var exception = e.exception; // The exception object returned by the callback.
360
        var newvalue = e.newvalue; // The value that user tried to udpated the element to.
361
        var tagid = $(e.target).attr('data-itemid');
362
        if (exception.errorcode !== 'namesalreadybeeingused') {
363
            return;
364
        }
365
        e.preventDefault(); // This will prevent default error dialogue.
366
 
367
        try {
368
            await Notification.saveCancelPromise(
369
                getString('confirm'),
370
                getString('nameuseddocombine', 'tag'),
371
                getString('yes'),
372
                getString('cancel'),
373
            );
374
 
375
            // The Promise will resolve on 'Yes' button, and reject on 'Cancel' button.
376
            const redirectTarget = new URL(window.location);
377
            redirectTarget.searchParams.set('newname', newvalue);
378
            redirectTarget.searchParams.set('tagid', tagid);
379
            redirectTarget.searchParams.set('action', 'renamecombine');
380
            redirectTarget.searchParams.set('sesskey', Config.sesskey);
381
 
382
            window.location.href = redirectTarget;
383
        } catch {
384
            return;
385
        }
386
    });
387
};
388
 
389
/**
390
 * Initialises tag collection management page.
391
 *
392
 * @method initManageCollectionsPage
393
 */
394
export const initManageCollectionsPage = () => {
395
    $('body').on('updated', '[data-inplaceeditable]', function(e) {
396
        var pendingPromise = new Pending('core/tag:initManageCollectionsPage-updated');
397
 
398
        var ajaxreturn = e.ajaxreturn,
399
            areaid, collid, isenabled;
400
        if (ajaxreturn.component === 'core_tag' && ajaxreturn.itemtype === 'tagareaenable') {
401
            areaid = $(this).attr('data-itemid');
402
            $(".tag-collections-table ul[data-collectionid] li[data-areaid=" + areaid + "]").hide();
403
            isenabled = ajaxreturn.value;
404
            if (isenabled === '1') {
405
                $(this).closest('tr').removeClass('dimmed_text');
406
                collid = $(this).closest('tr').find('[data-itemtype="tagareacollection"]').attr("data-value");
407
                $(".tag-collections-table ul[data-collectionid=" + collid + "] li[data-areaid=" + areaid + "]").show();
408
            } else {
409
                $(this).closest('tr').addClass('dimmed_text');
410
            }
411
        }
412
        if (ajaxreturn.component === 'core_tag' && ajaxreturn.itemtype === 'tagareacollection') {
413
            areaid = $(this).attr('data-itemid');
414
            $(".tag-collections-table ul[data-collectionid] li[data-areaid=" + areaid + "]").hide();
415
            collid = $(this).attr('data-value');
416
            isenabled = $(this).closest('tr').find('[data-itemtype="tagareaenable"]').attr("data-value");
417
            if (isenabled === "1") {
418
                $(".tag-collections-table ul[data-collectionid=" + collid + "] li[data-areaid=" + areaid + "]").show();
419
            }
420
        }
421
 
422
        pendingPromise.resolve();
423
    });
424
 
425
    document.addEventListener('click', async(e) => {
426
        const addTagCollectionNode = e.target.closest('.addtagcoll > a');
427
        if (addTagCollectionNode) {
428
            e.preventDefault();
429
            addTagCollection(addTagCollectionNode);
430
            return;
431
        }
432
 
433
        const deleteCollectionButton = e.target.closest('.tag-collections-table .action_delete');
434
        if (deleteCollectionButton) {
435
            e.preventDefault();
436
            deleteSelectedCollection(deleteCollectionButton);
437
        }
438
    });
439
};