Proyectos de Subversion Moodle

Rev

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