Proyectos de Subversion Moodle

Rev

Ir a la última revisión | | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
/**
2
 * This file contains JS functionality required by mforms and is included automatically
3
 * when required.
4
 */
5
 
6
// Namespace for the form bits and bobs
7
M.form = M.form || {};
8
 
9
if (typeof M.form.dependencyManager === 'undefined') {
10
    var dependencyManager = function() {
11
        dependencyManager.superclass.constructor.apply(this, arguments);
12
    };
13
    Y.extend(dependencyManager, Y.Base, {
14
        _locks: null,
15
        _hides: null,
16
        _dirty: null,
17
        _nameCollections: null,
18
        _fileinputs: null,
19
 
20
        initializer: function() {
21
            // Setup initial values for complex properties.
22
            this._locks = {};
23
            this._hides = {};
24
            this._dirty = {};
25
 
26
            // Setup event handlers.
27
            Y.Object.each(this.get('dependencies'), function(value, i) {
28
                var elements = this.elementsByName(i);
29
                elements.each(function(node) {
30
                    var nodeName = node.get('nodeName').toUpperCase();
31
                    if (nodeName == 'INPUT') {
32
                        if (node.getAttribute('type').match(/^(button|submit|radio|checkbox)$/)) {
33
                            node.on('click', this.updateEventDependencies, this);
34
                        } else {
35
                            node.on('blur', this.updateEventDependencies, this);
36
                        }
37
                        node.on('change', this.updateEventDependencies, this);
38
                    } else if (nodeName == 'SELECT') {
39
                        node.on('change', this.updateEventDependencies, this);
40
                    } else {
41
                        node.on('click', this.updateEventDependencies, this);
42
                        node.on('blur', this.updateEventDependencies, this);
43
                        node.on('change', this.updateEventDependencies, this);
44
                    }
45
                }, this);
46
            }, this);
47
 
48
            // Handle the reset button.
49
            this.get('form').get('elements').each(function(input) {
50
                if (input.getAttribute('type') == 'reset') {
51
                    input.on('click', function() {
52
                        this.get('form').reset();
53
                        this.updateAllDependencies();
54
                    }, this);
55
                }
56
            }, this);
57
 
58
            this.updateAllDependencies();
59
        },
60
 
61
        /**
62
         * Initializes the mapping from element name to YUI NodeList
63
         */
64
        initElementsByName: function() {
65
            var names = {}; // Form elements with a given name.
66
            var allnames = {}; // Form elements AND outer elements for groups with a given name.
67
 
68
            // Collect element names.
69
            Y.Object.each(this.get('dependencies'), function(conditions, i) {
70
                names[i] = new Y.NodeList();
71
                allnames[i] = new Y.NodeList();
72
                for (var condition in conditions) {
73
                    for (var value in conditions[condition]) {
74
                        for (var hide in conditions[condition][value]) {
75
                            for (var ei in conditions[condition][value][hide]) {
76
                                names[conditions[condition][value][hide][ei]] = new Y.NodeList();
77
                                allnames[conditions[condition][value][hide][ei]] = new Y.NodeList();
78
                            }
79
                        }
80
                    }
81
                }
82
            });
83
 
84
            // Locate elements for each name.
85
            this.get('form').get('elements').each(function(node) {
86
                var name = node.getAttribute('name');
87
                if (({}).hasOwnProperty.call(names, name)) {
88
                    names[name].push(node);
89
                    allnames[name].push(node);
90
                }
91
            });
92
            // Locate any groups with the given name.
93
            this.get('form').all('.fitem').each(function(node) {
94
                var name = node.getData('groupname');
95
                if (name && ({}).hasOwnProperty.call(allnames, name)) {
96
                    allnames[name].push(node);
97
                }
98
            });
99
            this._nameCollections = {names: names, allnames: allnames};
100
        },
101
 
102
        /**
103
         * Gets all elements in the form by their name and returns
104
         * a YUI NodeList
105
         *
106
         * @param {String} name The form element name.
107
         * @param {Boolean} includeGroups (optional - default false) Should the outer element for groups be included?
108
         * @return {Y.NodeList}
109
         */
110
        elementsByName: function(name, includeGroups) {
111
            if (includeGroups === undefined) {
112
                includeGroups = false;
113
            }
114
            var collection = (includeGroups ? 'allnames' : 'names');
115
 
116
            if (!this._nameCollections) {
117
                this.initElementsByName();
118
            }
119
            if (!({}).hasOwnProperty.call(this._nameCollections[collection], name)) {
120
                return new Y.NodeList();
121
            }
122
            return this._nameCollections[collection][name];
123
        },
124
 
125
        /**
126
         * Checks the dependencies the form has an makes any changes to the
127
         * form that are required.
128
         *
129
         * Changes are made by functions title _dependency{Dependencytype}
130
         * and more can easily be introduced by defining further functions.
131
         *
132
         * @param {EventFacade | null} e The event, if any.
133
         * @param {String} dependon The form element name to check dependencies against.
134
         * @return {Boolean}
135
         */
136
        checkDependencies: function(e, dependon) {
137
            var dependencies = this.get('dependencies'),
138
                tohide = {},
139
                tolock = {},
140
                condition, value, isHide, lock, hide,
141
                checkfunction, result, elements;
142
            if (!({}).hasOwnProperty.call(dependencies, dependon)) {
143
                return true;
144
            }
145
            elements = this.elementsByName(dependon);
146
            for (condition in dependencies[dependon]) {
147
                for (value in dependencies[dependon][condition]) {
148
                    for (isHide in dependencies[dependon][condition][value]) {
149
                        checkfunction = '_dependency' + condition[0].toUpperCase() + condition.slice(1);
150
                        if (Y.Lang.isFunction(this[checkfunction])) {
151
                            result = this[checkfunction].apply(this, [elements, value, (isHide === "1"), e]);
152
                        } else {
153
                            result = this._dependencyDefault(elements, value, (isHide === "1"), e);
154
                        }
155
                        lock = result.lock || false;
156
                        hide = result.hide || false;
157
                        for (var ei in dependencies[dependon][condition][value][isHide]) {
158
                            var eltolock = dependencies[dependon][condition][value][isHide][ei];
159
                            if (({}).hasOwnProperty.call(tohide, eltolock)) {
160
                                tohide[eltolock] = tohide[eltolock] || hide;
161
                            } else {
162
                                tohide[eltolock] = hide;
163
                            }
164
 
165
                            if (({}).hasOwnProperty.call(tolock, eltolock)) {
166
                                tolock[eltolock] = tolock[eltolock] || lock;
167
                            } else {
168
                                tolock[eltolock] = lock;
169
                            }
170
                        }
171
                    }
172
                }
173
            }
174
 
175
            for (var el in tolock) {
176
                var needsupdate = false;
177
                if (!({}).hasOwnProperty.call(this._locks, el)) {
178
                    this._locks[el] = {};
179
                }
180
                if (({}).hasOwnProperty.call(tolock, el) && tolock[el]) {
181
                    if (!({}).hasOwnProperty.call(this._locks[el], dependon) || this._locks[el][dependon]) {
182
                        this._locks[el][dependon] = true;
183
                        needsupdate = true;
184
                    }
185
                } else if (({}).hasOwnProperty.call(this._locks[el], dependon) && this._locks[el][dependon]) {
186
                    delete this._locks[el][dependon];
187
                    needsupdate = true;
188
                }
189
 
190
                if (!({}).hasOwnProperty.call(this._hides, el)) {
191
                    this._hides[el] = {};
192
                }
193
                if (({}).hasOwnProperty.call(tohide, el) && tohide[el]) {
194
                    if (!({}).hasOwnProperty.call(this._hides[el], dependon) || this._hides[el][dependon]) {
195
                        this._hides[el][dependon] = true;
196
                        needsupdate = true;
197
                    }
198
                } else if (({}).hasOwnProperty.call(this._hides[el], dependon) && this._hides[el][dependon]) {
199
                    delete this._hides[el][dependon];
200
                    needsupdate = true;
201
                }
202
 
203
                if (needsupdate) {
204
                    this._dirty[el] = true;
205
                }
206
            }
207
 
208
            return true;
209
        },
210
        /**
211
         * Update all dependencies in form
212
         */
213
        updateAllDependencies: function() {
214
            Y.Object.each(this.get('dependencies'), function(value, name) {
215
                this.checkDependencies(null, name);
216
            }, this);
217
 
218
            this.updateForm();
219
        },
220
        /**
221
         * Update dependencies associated with event
222
         *
223
         * @param {Event} e The event.
224
         */
225
        updateEventDependencies: function(e) {
226
            var el = e.target.getAttribute('name');
227
            this.checkDependencies(e, el);
228
            this.updateForm();
229
        },
230
        /**
231
         * Flush pending changes to the form
232
         */
233
        updateForm: function() {
234
            var el;
235
            for (el in this._dirty) {
236
                if (({}).hasOwnProperty.call(this._locks, el)) {
237
                    this._disableElement(el, !Y.Object.isEmpty(this._locks[el]));
238
                }
239
                if (({}).hasOwnProperty.call(this._hides, el)) {
240
                    this._hideElement(el, !Y.Object.isEmpty(this._hides[el]));
241
                }
242
            }
243
 
244
            this._dirty = {};
245
        },
246
        /**
247
         * Disables or enables all form elements with the given name
248
         *
249
         * @param {String} name The form element name.
250
         * @param {Boolean} disabled True to disable, false to enable.
251
         */
252
        _disableElement: function(name, disabled) {
253
            var els = this.elementsByName(name),
254
                filepicker = this.isFilePicker(name),
255
                editors = this.get('form').all('.fitem [data-fieldtype="editor"] textarea[name="' + name + '[text]"]');
256
 
257
            els.each(function(node) {
258
                if (disabled) {
259
                    node.setAttribute('disabled', 'disabled');
260
                } else {
261
                    node.removeAttribute('disabled');
262
                }
263
 
264
                // Extra code to disable filepicker or filemanager form elements
265
                if (filepicker) {
266
                    var fitem = node.ancestor('.fitem');
267
                    if (fitem) {
268
                        if (disabled) {
269
                            fitem.addClass('disabled');
270
                        } else {
271
                            fitem.removeClass('disabled');
272
                        }
273
                    }
274
                }
275
            });
276
            editors.each(function(editor) {
277
                if (disabled) {
278
                    editor.setAttribute('readonly', 'readonly');
279
                } else {
280
                    editor.removeAttribute('readonly', 'readonly');
281
                }
282
                editor.getDOMNode().dispatchEvent(new Event('form:editorUpdated'));
283
            });
284
        },
285
        /**
286
         * Hides or shows all form elements with the given name.
287
         *
288
         * @param {String} name The form element name.
289
         * @param {Boolean} hidden True to hide, false to show.
290
         */
291
        _hideElement: function(name, hidden) {
292
            var els = this.elementsByName(name, true);
293
            els.each(function(node) {
294
                var e = node.ancestor('.fitem', true);
295
                var label = null,
296
                    id = null;
297
                if (e) {
298
                    // Cope with differences between clean and boost themes.
299
                    if (e.hasClass('fitem_fgroup')) {
300
                        // Items within groups are not wrapped in div.fitem in theme_clean, so
301
                        // we need to hide the input, not the div.fitem.
302
                        e = node;
303
                    }
304
 
305
                    if (hidden) {
306
                        e.setAttribute('hidden', 'hidden');
307
                    } else {
308
                        e.removeAttribute('hidden');
309
                    }
310
                    e.setStyles({
311
                        display: (hidden) ? 'none' : ''
312
                    });
313
 
314
                    // Hide/unhide the label as well.
315
                    id = node.get('id');
316
                    if (id) {
317
                        label = Y.all('label[for="' + id + '"]');
318
                        if (label) {
319
                            if (hidden) {
320
                                label.setAttribute('hidden', 'hidden');
321
                            } else {
322
                                label.removeAttribute('hidden');
323
                            }
324
                            label.setStyles({
325
                                display: (hidden) ? 'none' : ''
326
                            });
327
                        }
328
                    }
329
                }
330
            });
331
        },
332
        /**
333
         * Is the form element inside a filepicker or filemanager?
334
         *
335
         * @param {String} el The form element name.
336
         * @return {Boolean}
337
         */
338
        isFilePicker: function(el) {
339
            if (!this._fileinputs) {
340
                var fileinputs = {};
341
                var selector = '.fitem [data-fieldtype="filepicker"] input,.fitem [data-fieldtype="filemanager"] input';
342
                // Include a selector where the filemanager input is nested in a group.
343
                selector += ',.fitem [data-fieldtype="group"] input[id*="filemanager"]';
344
                var els = this.get('form').all(selector);
345
                els.each(function(node) {
346
                    fileinputs[node.getAttribute('name')] = true;
347
                });
348
                this._fileinputs = fileinputs;
349
            }
350
 
351
            if (({}).hasOwnProperty.call(this._fileinputs, el)) {
352
                return this._fileinputs[el] || false;
353
            }
354
 
355
            return false;
356
        },
357
        _dependencyNotchecked: function(elements, value, isHide) {
358
            var lock = false;
359
            elements.each(function() {
360
                if (this.getAttribute('type').toLowerCase() == 'hidden' &&
361
                        !this.siblings('input[type=checkbox][name="' + this.get('name') + '"]').isEmpty()) {
362
                    // This is the hidden input that is part of an advcheckbox.
363
                    return;
364
                }
365
                if (this.getAttribute('type').toLowerCase() == 'radio' && this.get('value') != value) {
366
                    return;
367
                }
368
                lock = lock || !Y.Node.getDOMNode(this).checked;
369
            });
370
            return {
371
                lock: lock,
372
                hide: isHide ? lock : false
373
            };
374
        },
375
        _dependencyChecked: function(elements, value, isHide) {
376
            var lock = false;
377
            elements.each(function() {
378
                if (this.getAttribute('type').toLowerCase() == 'hidden' &&
379
                        !this.siblings('input[type=checkbox][name="' + this.get('name') + '"]').isEmpty()) {
380
                    // This is the hidden input that is part of an advcheckbox.
381
                    return;
382
                }
383
                if (this.getAttribute('type').toLowerCase() == 'radio' && this.get('value') != value) {
384
                    return;
385
                }
386
                lock = lock || Y.Node.getDOMNode(this).checked;
387
            });
388
            return {
389
                lock: lock,
390
                hide: isHide ? lock : false
391
            };
392
        },
393
        _dependencyNoitemselected: function(elements, value, isHide) {
394
            var lock = false;
395
            elements.each(function() {
396
                lock = lock || this.get('selectedIndex') == -1;
397
            });
398
            return {
399
                lock: lock,
400
                hide: isHide ? lock : false
401
            };
402
        },
403
        _dependencyEq: function(elements, value, isHide) {
404
            var lock = false;
405
            var hiddenVal = false;
406
            var options, v, selected, values;
407
            elements.each(function() {
408
                if (this.getAttribute('type').toLowerCase() == 'radio' && !Y.Node.getDOMNode(this).checked) {
409
                    return;
410
                } else if (this.getAttribute('type').toLowerCase() == 'hidden' &&
411
                        !this.siblings('input[type=checkbox][name="' + this.get('name') + '"]').isEmpty()) {
412
                    // This is the hidden input that is part of an advcheckbox.
413
                    hiddenVal = (this.get('value') == value);
414
                    return;
415
                } else if (this.getAttribute('type').toLowerCase() == 'checkbox' && !Y.Node.getDOMNode(this).checked) {
416
                    lock = lock || hiddenVal;
417
                    return;
418
                }
419
                if (this.getAttribute('class').toLowerCase() == 'filepickerhidden') {
420
                    // Check for filepicker status.
421
                    var elementname = this.getAttribute('name');
422
                    if (elementname && M.form_filepicker.instances[elementname].fileadded) {
423
                        lock = false;
424
                    } else {
425
                        lock = true;
426
                    }
427
                } else if (this.get('nodeName').toUpperCase() === 'SELECT' && this.get('multiple') === true) {
428
                    // Multiple selects can have one or more value assigned. A pipe (|) is used as a value separator
429
                    // when multiple values have to be selected at the same time.
430
                    values = value.split('|');
431
                    selected = [];
432
                    options = this.get('options');
433
                    options.each(function() {
434
                        if (this.get('selected')) {
435
                            selected[selected.length] = this.get('value');
436
                        }
437
                    });
438
                    if (selected.length > 0 && selected.length === values.length) {
439
                        for (var i in selected) {
440
                            v = selected[i];
441
                            if (values.indexOf(v) > -1) {
442
                                lock = true;
443
                            } else {
444
                                lock = false;
445
                                return;
446
                            }
447
                        }
448
                    } else {
449
                        lock = false;
450
                    }
451
                } else {
452
                    lock = lock || this.get('value') == value;
453
                }
454
            });
455
            return {
456
                lock: lock,
457
                hide: isHide ? lock : false
458
            };
459
        },
460
        /**
461
         * Lock the given field if the field value is in the given set of values.
462
         *
463
         * @param {Array} elements
464
         * @param {String} values Single value or pipe (|) separated values when multiple
465
         * @returns {{lock: boolean, hide: boolean}}
466
         * @private
467
         */
468
        _dependencyIn: function(elements, values, isHide) {
469
            // A pipe (|) is used as a value separator
470
            // when multiple values have to be passed on at the same time.
471
            values = values.split('|');
472
            var lock = false;
473
            var hiddenVal = false;
474
            var options, v, selected, value;
475
            elements.each(function() {
476
                if (this.getAttribute('type').toLowerCase() == 'radio' && !Y.Node.getDOMNode(this).checked) {
477
                    return;
478
                } else if (this.getAttribute('type').toLowerCase() == 'hidden' &&
479
                        !this.siblings('input[type=checkbox][name="' + this.get('name') + '"]').isEmpty()) {
480
                    // This is the hidden input that is part of an advcheckbox.
481
                    hiddenVal = (values.indexOf(this.get('value')) > -1);
482
                    return;
483
                } else if (this.getAttribute('type').toLowerCase() == 'checkbox' && !Y.Node.getDOMNode(this).checked) {
484
                    lock = lock || hiddenVal;
485
                    return;
486
                }
487
                if (this.getAttribute('class').toLowerCase() == 'filepickerhidden') {
488
                    // Check for filepicker status.
489
                    var elementname = this.getAttribute('name');
490
                    if (elementname && M.form_filepicker.instances[elementname].fileadded) {
491
                        lock = false;
492
                    } else {
493
                        lock = true;
494
                    }
495
                } else if (this.get('nodeName').toUpperCase() === 'SELECT' && this.get('multiple') === true) {
496
                    // Multiple selects can have one or more value assigned.
497
                    selected = [];
498
                    options = this.get('options');
499
                    options.each(function() {
500
                        if (this.get('selected')) {
501
                            selected[selected.length] = this.get('value');
502
                        }
503
                    });
504
                    if (selected.length > 0 && selected.length === values.length) {
505
                        for (var i in selected) {
506
                            v = selected[i];
507
                            if (values.indexOf(v) > -1) {
508
                                lock = true;
509
                            } else {
510
                                lock = false;
511
                                return;
512
                            }
513
                        }
514
                    } else {
515
                        lock = false;
516
                    }
517
                } else {
518
                    value = this.get('value');
519
                    lock = lock || (values.indexOf(value) > -1);
520
                }
521
            });
522
            return {
523
                lock: lock,
524
                hide: isHide ? lock : false
525
            };
526
        },
527
        _dependencyHide: function(elements, value) {
528
            return {
529
                lock: false,
530
                hide: true
531
            };
532
        },
533
        _dependencyDefault: function(elements, value, isHide) {
534
            var lock = false,
535
                hiddenVal = false,
536
                values
537
                ;
538
            elements.each(function() {
539
                var selected;
540
                if (this.getAttribute('type').toLowerCase() == 'radio' && !Y.Node.getDOMNode(this).checked) {
541
                    return;
542
                } else if (this.getAttribute('type').toLowerCase() == 'hidden' &&
543
                        !this.siblings('input[type=checkbox][name="' + this.get('name') + '"]').isEmpty()) {
544
                    // This is the hidden input that is part of an advcheckbox.
545
                    hiddenVal = (this.get('value') != value);
546
                    return;
547
                } else if (this.getAttribute('type').toLowerCase() == 'checkbox' && !Y.Node.getDOMNode(this).checked) {
548
                    lock = lock || hiddenVal;
549
                    return;
550
                }
551
                // Check for filepicker status.
552
                if (this.getAttribute('class').toLowerCase() == 'filepickerhidden') {
553
                    var elementname = this.getAttribute('name');
554
                    if (elementname && M.form_filepicker.instances[elementname].fileadded) {
555
                        lock = true;
556
                    } else {
557
                        lock = false;
558
                    }
559
                } else if (this.get('nodeName').toUpperCase() === 'SELECT' && this.get('multiple') === true) {
560
                    // Multiple selects can have one or more value assigned. A pipe (|) is used as a value separator
561
                    // when multiple values have to be selected at the same time.
562
                    values = value.split('|');
563
                    selected = [];
564
                    this.get('options').each(function() {
565
                        if (this.get('selected')) {
566
                            selected[selected.length] = this.get('value');
567
                        }
568
                    });
569
                    if (selected.length > 0 && selected.length === values.length) {
570
                        for (var i in selected) {
571
                            if (values.indexOf(selected[i]) > -1) {
572
                                lock = false;
573
                            } else {
574
                                lock = true;
575
                                return;
576
                            }
577
                        }
578
                    } else {
579
                        lock = true;
580
                    }
581
                } else {
582
                    lock = lock || this.get('value') != value;
583
                }
584
            });
585
            return {
586
                lock: lock,
587
                hide: isHide ? lock : false
588
            };
589
        }
590
    }, {
591
        NAME: 'mform-dependency-manager',
592
        ATTRS: {
593
            form: {
594
                setter: function(value) {
595
                    return Y.one('#' + value);
596
                },
597
                value: null
598
            },
599
 
600
            dependencies: {
601
                value: {}
602
            }
603
        }
604
    });
605
 
606
    M.form.dependencyManager = dependencyManager;
607
}
608
 
609
/**
610
 * Stores a list of the dependencyManager for each form on the page.
611
 */
612
M.form.dependencyManagers = {};
613
 
614
/**
615
 * Initialises a manager for a forms dependencies.
616
 * This should happen once per form.
617
 *
618
 * @param {YUI} Y YUI3 instance
619
 * @param {String} formid ID of the form
620
 * @param {Array} dependencies array
621
 * @return {M.form.dependencyManager}
622
 */
623
M.form.initFormDependencies = function(Y, formid, dependencies) {
624
 
625
    // If the dependencies isn't an array or object we don't want to
626
    // know about it
627
    if (!Y.Lang.isArray(dependencies) && !Y.Lang.isObject(dependencies)) {
628
        return false;
629
    }
630
 
631
    /**
632
     * Fixes an issue with YUI's processing method of form.elements property
633
     * in Internet Explorer.
634
     *     http://yuilibrary.com/projects/yui3/ticket/2528030
635
     */
636
    Y.Node.ATTRS.elements = {
637
        getter: function() {
638
            return Y.all(new Y.Array(this._node.elements, 0, true));
639
        }
640
    };
641
 
642
    M.form.dependencyManagers[formid] = new M.form.dependencyManager({form: formid, dependencies: dependencies});
643
    return M.form.dependencyManagers[formid];
644
};
645
 
646
/**
647
 * Update the state of a form. You need to call this after, for example, changing
648
 * the state of some of the form input elements in your own code, in order that
649
 * things like the disableIf state of elements can be updated.
650
 *
651
 * @param {String} formid ID of the form
652
 */
653
M.form.updateFormState = function(formid) {
654
    if (formid in M.form.dependencyManagers) {
655
        M.form.dependencyManagers[formid].updateAllDependencies();
656
    }
657
};