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
 * Custom Field interaction management for Moodle.
18
 *
19
 * @module     core_customfield/form
20
 * @copyright  2018 Toni Barbera
21
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22
 */
23
 
24
import 'core/inplace_editable';
25
import {call as fetchMany} from 'core/ajax';
26
import {
27
    getString,
28
    getStrings,
29
} from 'core/str';
30
import ModalForm from 'core_form/modalform';
31
import Notification from 'core/notification';
32
import Pending from 'core/pending';
33
import SortableList from 'core/sortable_list';
34
import Templates from 'core/templates';
35
import jQuery from 'jquery';
36
 
37
/**
38
 * Display confirmation dialogue
39
 *
40
 * @param {Number} id
41
 * @param {String} type
42
 * @param {String} component
43
 * @param {String} area
44
 * @param {Number} itemid
45
 */
46
const confirmDelete = (id, type, component, area, itemid) => {
47
    const pendingPromise = new Pending('core_customfield/form:confirmDelete');
48
 
49
    getStrings([
50
        {'key': 'confirm'},
51
        {'key': 'confirmdelete' + type, component: 'core_customfield'},
52
        {'key': 'yes'},
53
        {'key': 'no'},
54
    ])
55
    .then(strings => {
56
        return Notification.confirm(strings[0], strings[1], strings[2], strings[3], function() {
57
            const pendingDeletePromise = new Pending('core_customfield/form:confirmDelete');
58
            fetchMany([
59
                {
60
                    methodname: (type === 'field') ? 'core_customfield_delete_field' : 'core_customfield_delete_category',
61
                    args: {id},
62
                },
63
                {methodname: 'core_customfield_reload_template', args: {component, area, itemid}}
64
            ])[1]
65
            .then(response => Templates.render('core_customfield/list', response))
66
            .then((html, js) => Templates.replaceNode(jQuery('[data-region="list-page"]'), html, js))
67
            .then(pendingDeletePromise.resolve)
68
            .catch(Notification.exception);
69
        });
70
    })
71
    .then(pendingPromise.resolve)
72
    .catch(Notification.exception);
73
};
74
 
75
 
76
/**
77
 * Creates a new custom fields category with default name and updates the list
78
 *
79
 * @param {String} component
80
 * @param {String} area
81
 * @param {Number} itemid
82
 */
83
const createNewCategory = (component, area, itemid) => {
84
    const pendingPromise = new Pending('core_customfield/form:createNewCategory');
85
    const promises = fetchMany([
86
        {methodname: 'core_customfield_create_category', args: {component, area, itemid}},
87
        {methodname: 'core_customfield_reload_template', args: {component, area, itemid}}
88
    ]);
89
 
90
    promises[1].then(response => Templates.render('core_customfield/list', response))
91
    .then((html, js) => Templates.replaceNode(jQuery('[data-region="list-page"]'), html, js))
92
    .then(() => pendingPromise.resolve())
93
    .catch(Notification.exception);
94
};
95
 
96
/**
97
 * Create new custom field
98
 *
99
 * @param {HTMLElement} element
100
 * @param {String} component
101
 * @param {String} area
102
 * @param {Number} itemid
103
 */
104
const createNewField = (element, component, area, itemid) => {
105
    const pendingPromise = new Pending('core_customfield/form:createNewField');
106
 
107
    const returnFocus = element.closest(".action-menu").querySelector(".dropdown-toggle");
108
    const form = new ModalForm({
109
        formClass: "core_customfield\\field_config_form",
110
        args: {
111
            categoryid: element.getAttribute('data-categoryid'),
112
            type: element.getAttribute('data-type'),
113
        },
114
        modalConfig: {
115
            title: getString('addingnewcustomfield', 'core_customfield', element.getAttribute('data-typename')),
116
        },
117
        returnFocus,
118
    });
119
 
120
    form.addEventListener(form.events.FORM_SUBMITTED, () => {
121
        const pendingCreatedPromise = new Pending('core_customfield/form:createdNewField');
122
        const promises = fetchMany([
123
            {methodname: 'core_customfield_reload_template', args: {component: component, area: area, itemid: itemid}}
124
        ]);
125
 
126
        promises[0].then(response => Templates.render('core_customfield/list', response))
127
        .then((html, js) => Templates.replaceNode(jQuery('[data-region="list-page"]'), html, js))
128
        .then(() => pendingCreatedPromise.resolve())
129
        .catch(() => window.location.reload());
130
    });
131
 
132
    form.show();
133
 
134
    pendingPromise.resolve();
135
};
136
 
137
/**
138
 * Edit custom field
139
 *
140
 * @param {HTMLElement} element
141
 * @param {String} component
142
 * @param {String} area
143
 * @param {Number} itemid
144
 */
145
const editField = (element, component, area, itemid) => {
146
    const pendingPromise = new Pending('core_customfield/form:editField');
147
 
148
    const form = new ModalForm({
149
        formClass: "core_customfield\\field_config_form",
150
        args: {
151
            id: element.getAttribute('data-id'),
152
        },
153
        modalConfig: {
154
            title: getString('editingfield', 'core_customfield', element.getAttribute('data-name')),
155
        },
156
        returnFocus: element,
157
    });
158
 
159
    form.addEventListener(form.events.FORM_SUBMITTED, () => {
160
        const pendingCreatedPromise = new Pending('core_customfield/form:createdNewField');
161
        const promises = fetchMany([
162
            {methodname: 'core_customfield_reload_template', args: {component: component, area: area, itemid: itemid}}
163
        ]);
164
 
165
        promises[0].then(response => Templates.render('core_customfield/list', response))
166
        .then((html, js) => Templates.replaceNode(jQuery('[data-region="list-page"]'), html, js))
167
        .then(() => pendingCreatedPromise.resolve())
168
        .catch(() => window.location.reload());
169
    });
170
 
171
    form.show();
172
 
173
    pendingPromise.resolve();
174
};
175
 
176
/**
177
 * Fetch the category name from an inplace editable, given a child node of that field.
178
 *
179
 * @param {NodeElement} nodeElement
180
 * @returns {String}
181
 */
182
const getCategoryNameFor = nodeElement => nodeElement
183
    .closest('[data-category-id]')
184
    .find('[data-inplaceeditable][data-itemtype=category][data-component=core_customfield]')
185
    .attr('data-value');
186
 
187
const setupSortableLists = rootNode => {
188
    // Sort category.
189
    const sortCat = new SortableList(
190
        '#customfield_catlist .categorieslist',
191
        {
192
            moveHandlerSelector: '.movecategory [data-drag-type=move]',
193
        }
194
    );
195
    sortCat.getElementName = nodeElement => Promise.resolve(getCategoryNameFor(nodeElement));
196
 
197
    // Note: The sortable list currently uses jQuery events.
198
    jQuery('[data-category-id]').on(SortableList.EVENTS.DROP, (evt, info) => {
199
        if (info.positionChanged) {
200
            const pendingPromise = new Pending('core_customfield/form:categoryid:on:sortablelist-drop');
201
            fetchMany([{
202
                methodname: 'core_customfield_move_category',
203
                args: {
204
                    id: info.element.data('category-id'),
205
                    beforeid: info.targetNextElement.data('category-id')
206
                }
207
 
208
            }])[0]
209
            .then(pendingPromise.resolve)
210
            .catch(Notification.exception);
211
        }
212
        evt.stopPropagation(); // Important for nested lists to prevent multiple targets.
213
    });
214
 
215
    // Sort fields.
216
    var sort = new SortableList(
217
        '#customfield_catlist .fieldslist tbody',
218
        {
219
            moveHandlerSelector: '.movefield [data-drag-type=move]',
220
        }
221
    );
222
 
223
    sort.getDestinationName = (parentElement, afterElement) => {
224
        if (!afterElement.length) {
225
            return getString('totopofcategory', 'customfield', getCategoryNameFor(parentElement));
226
        } else if (afterElement.attr('data-field-name')) {
227
            return getString('afterfield', 'customfield', afterElement.attr('data-field-name'));
228
        } else {
229
            return Promise.resolve('');
230
        }
231
    };
232
 
233
    jQuery('[data-field-name]').on(SortableList.EVENTS.DROP, (evt, info) => {
234
        if (info.positionChanged) {
235
            const pendingPromise = new Pending('core_customfield/form:fieldname:on:sortablelist-drop');
236
            fetchMany([{
237
                methodname: 'core_customfield_move_field',
238
                args: {
239
                    id: info.element.data('field-id'),
240
                    beforeid: info.targetNextElement.data('field-id'),
241
                    categoryid: Number(info.targetList.closest('[data-category-id]').attr('data-category-id'))
242
                },
243
            }])[0]
244
            .then(pendingPromise.resolve)
245
            .catch(Notification.exception);
246
        }
247
        evt.stopPropagation(); // Important for nested lists to prevent multiple targets.
248
    });
249
 
250
    jQuery('[data-field-name]').on(SortableList.EVENTS.DRAG, evt => {
251
        var pendingPromise = new Pending('core_customfield/form:fieldname:on:sortablelist-drag');
252
 
253
        evt.stopPropagation(); // Important for nested lists to prevent multiple targets.
254
 
255
        // Refreshing fields tables.
256
        Templates.render('core_customfield/nofields', {})
257
        .then(html => {
258
            rootNode.querySelectorAll('.categorieslist > *')
259
            .forEach(category => {
260
                const fields = category.querySelectorAll('.field:not(.sortable-list-is-dragged)');
261
                const noFields = category.querySelector('.nofields');
262
 
263
                if (!fields.length && !noFields) {
264
                    category.querySelector('tbody').innerHTML = html;
265
                } else if (fields.length && noFields) {
266
                    noFields.remove();
267
                }
268
            });
269
            return;
270
        })
271
        .then(pendingPromise.resolve)
272
        .catch(Notification.exception);
273
    });
274
 
275
    jQuery('[data-category-id], [data-field-name]').on(SortableList.EVENTS.DRAGSTART, (evt, info) => {
276
        setTimeout(() => {
277
            jQuery('.sortable-list-is-dragged').width(info.element.width());
278
        }, 501);
279
    });
280
};
281
 
282
/**
283
 * Initialise the custom fields manager.
284
 */
285
export const init = () => {
286
    const rootNode = document.querySelector('#customfield_catlist');
287
 
288
    const component = rootNode.dataset.component;
289
    const area = rootNode.dataset.area;
290
    const itemid = rootNode.dataset.itemid;
291
 
292
    rootNode.addEventListener('click', e => {
293
        const roleHolder = e.target.closest('[data-role]');
294
        if (!roleHolder) {
295
            return;
296
        }
297
 
298
        if (roleHolder.dataset.role === 'deletefield') {
299
            e.preventDefault();
300
 
301
            confirmDelete(roleHolder.dataset.id, 'field', component, area, itemid);
302
            return;
303
        }
304
 
305
        if (roleHolder.dataset.role === 'deletecategory') {
306
            e.preventDefault();
307
 
308
            confirmDelete(roleHolder.dataset.id, 'category', component, area, itemid);
309
            return;
310
        }
311
 
312
        if (roleHolder.dataset.role === 'addnewcategory') {
313
            e.preventDefault();
314
            createNewCategory(component, area, itemid);
315
 
316
            return;
317
        }
318
 
319
        if (roleHolder.dataset.role === 'addfield') {
320
            e.preventDefault();
321
            createNewField(roleHolder, component, area, itemid);
322
 
323
            return;
324
        }
325
 
326
        if (roleHolder.dataset.role === 'editfield') {
327
            e.preventDefault();
328
            editField(roleHolder, component, area, itemid);
329
 
330
            return;
331
        }
332
    });
333
 
334
    setupSortableLists(rootNode, component, area, itemid);
335
};