MDL-34221 disabledIf support for filemanager and filepicker elements
[moodle.git] / lib / form / form.js
1 /**
2  * This file contains JS functionality required by mforms and is included automatically
3  * when required.
4  */
6 // Namespace for the form bits and bobs
7 M.form = M.form || {};
9 /**
10  * Initialises the show advanced functionality and events.
11  * This should only ever happen ONCE per page.
12  *
13  * @param {YUI} Y
14  * @param {object} config
15  */
16 M.form.initShowAdvanced = function(Y, config) {
17     if (M.form.showAdvanced) {
18         return M.form.showAdvanced;
19     }
20     var showAdvanced = function(config) {
21         showAdvanced.superclass.constructor.apply(this, arguments);
22     };
23     showAdvanced.prototype = {
24         _advButtons : [],
25         _advAreas : [],
26         _stateInput : null,
27         initializer : function() {
28             this._advAreas = Y.all('form .advanced');
29             this._advButtons = Y.all('.showadvancedbtn');
30             if (this._advButtons.size() > 0) {
31                 this._stateInput = new Y.NodeList(document.getElementsByName('mform_showadvanced_last'));
32                 this._advButtons.on('click', this.switchState, this);
33                 this._advButtons.set('type', 'button');
34             }
35         },
36         /**
37          * Toggles between showing advanced items and hiding them.
38          * Should be fired by an event.
39          */
40         switchState : function(e) {
41             e.preventDefault();
42             if (this._stateInput.get('value')=='1') {
43                 this._stateInput.set('value', '0');
44                 this._advButtons.setAttribute('value', M.str.form.showadvanced);
45                 this._advAreas.addClass('hide');
46             } else {
47                 this._stateInput.set('value', '1');
48                 this._advButtons.setAttribute('value', M.str.form.hideadvanced);
49                 this._advAreas.removeClass('hide');
50             }
51         }
52     };
53     // Extend it with the YUI widget fw.
54     Y.extend(showAdvanced, Y.Base, showAdvanced.prototype, {
55         NAME : 'mform-showAdvanced'
56     });
57     M.form.showAdvanced = new showAdvanced(config);
58     return M.form.showAdvanced;
59 };
61 /**
62  * Initialises a manager for a forms dependencies.
63  * This should happen once per form.
64  */
65 M.form.initFormDependencies = function(Y, formid, dependencies) {
67     // If the dependencies isn't an array or object we don't want to
68     // know about it
69     if (!Y.Lang.isArray(dependencies) && !Y.Lang.isObject(dependencies)) {
70         return false;
71     }
73     /**
74      * Fixes an issue with YUI's processing method of form.elements property
75      * in Internet Explorer.
76      *     http://yuilibrary.com/projects/yui3/ticket/2528030
77      */
78     Y.Node.ATTRS.elements = {
79         getter: function() {
80             return Y.all(new Y.Array(this._node.elements, 0, true));
81         }
82     };
84     // Define the dependency manager if it hasn't already been defined.
85     M.form.dependencyManager = M.form.dependencyManager || (function(){
86         var dependencyManager = function(config) {
87             dependencyManager.superclass.constructor.apply(this, arguments);
88         };
89         dependencyManager.prototype = {
90             _form : null,
91             _depElements : [],
92             _nameCollections : [],
93             initializer : function(config) {
94                 var i = 0, nodeName;
95                 this._form = Y.one('#'+formid);
96                 for (i in dependencies) {
97                     this._depElements[i] = this.elementsByName(i);
98                     if (this._depElements[i].size() == 0) {
99                         continue;
100                     }
101                     this._depElements[i].each(function(node){
102                         nodeName = node.get('nodeName').toUpperCase();
103                         if (nodeName == 'INPUT') {
104                             if (node.getAttribute('type').match(/^(button|submit|radio|checkbox)$/)) {
105                                 node.on('click', this.checkDependencies, this);
106                             } else {
107                                 node.on('blur', this.checkDependencies, this);
108                             }
109                             node.on('change', this.checkDependencies, this);
110                         } else if (nodeName == 'SELECT') {
111                             node.on('change', this.checkDependencies, this);
112                         } else {
113                             node.on('click', this.checkDependencies, this);
114                             node.on('blur', this.checkDependencies, this);
115                             node.on('change', this.checkDependencies, this);
116                         }
117                     }, this);
118                 }
119                 this._form.get('elements').each(function(input){
120                     if (input.getAttribute('type')=='reset') {
121                         input.on('click', function(){
122                             this._form.reset();
123                             this.checkDependencies();
124                         }, this);
125                     }
126                 }, this);
128                 return this.checkDependencies(null);
129             },
130             /**
131              * Gets all elements in the form by thier name and returns
132              * a YUI NodeList
133              * @return Y.NodeList
134              */
135             elementsByName : function(name) {
136                 if (!this._nameCollections[name]) {
137                     var elements = [];
138                     this._form.get('elements').each(function(){
139                         if (this.getAttribute('name') == name) {
140                             elements.push(this);
141                         }
142                     });
143                     this._nameCollections[name] = new Y.NodeList(elements);
144                 }
145                 return this._nameCollections[name];
146             },
147             /**
148              * Checks the dependencies the form has an makes any changes to the
149              * form that are required.
150              *
151              * Changes are made by functions title _dependency_{dependencytype}
152              * and more can easily be introduced by defining further functions.
153              */
154             checkDependencies : function(e) {
155                 var tolock = [],
156                     tohide = [],
157                     dependon, condition, value,
158                     lock, hide, checkfunction, result;
159                 for (dependon in dependencies) {
160                     if (this._depElements[dependon].size() == 0) {
161                         continue;
162                     }
163                     for (condition in dependencies[dependon]) {
164                         for (value in dependencies[dependon][condition]) {
165                             lock = false;
166                             hide = false;
167                             checkfunction = '_dependency_'+condition;
168                             if (Y.Lang.isFunction(this[checkfunction])) {
169                                 result = this[checkfunction].apply(this, [this._depElements[dependon], value, e]);
170                             } else {
171                                 result = this._dependency_default(this._depElements[dependon], value, e);
172                             }
173                             lock = result.lock || false;
174                             hide = result.hide || false;
175                             for (var ei in dependencies[dependon][condition][value]) {
176                                 var eltolock = dependencies[dependon][condition][value][ei];
177                                 if (hide) {
178                                     tohide[eltolock] = true;
179                                 }
180                                 if (tolock[eltolock] != null) {
181                                     tolock[eltolock] = lock || tolock[eltolock];
182                                 } else {
183                                     tolock[eltolock] = lock;
184                                 }
185                             }
186                         }
187                     }
188                 }
189                 for (var el in tolock) {
190                     this._disableElement(el, tolock[el]);
191                     if (tohide.propertyIsEnumerable(el)) {
192                         this._hideElement(el, tohide[el]);
193                     }
194                 }
195                 return true;
196             },
197             /**
198              * Disabled all form elements with the given name
199              */
200             _disableElement : function(name, disabled) {
201                 var els = this.elementsByName(name);
202                 var form = this;
203                 els.each(function(){
204                     if (disabled) {
205                         this.setAttribute('disabled', 'disabled');
206                     } else {
207                         this.removeAttribute('disabled');
208                     }
210                     // Extra code to disable filepicker or filemanager form elements
211                     var fitem = this.ancestor('.fitem');
212                     if (fitem && (fitem.hasClass('fitem_ffilemanager') || fitem.hasClass('fitem_ffilepicker'))) {
213                         if (disabled){
214                             fitem.addClass('disabled');
215                         } else {
216                             fitem.removeClass('disabled');
217                         }
218                     }
219                 })
220             },
221             /**
222              * Hides all elements with the given name.
223              */
224             _hideElement : function(name, hidden) {
225                 var els = this.elementsByName(name);
226                 els.each(function(){
227                     var e = els.ancestor('.fitem');
228                     if (e) {
229                         e.setStyles({
230                             display : (hidden)?'none':''
231                         })
232                     }
233                 });
234             },
235             _dependency_notchecked : function(elements, value) {
236                 var lock = false;
237                 elements.each(function(){
238                     if (this.getAttribute('type').toLowerCase()=='hidden' && !this.siblings('input[type=checkbox][name="' + this.get('name') + '"]').isEmpty()) {
239                         // This is the hidden input that is part of an advcheckbox.
240                         return;
241                     }
242                     if (this.getAttribute('type').toLowerCase()=='radio' && this.get('value') != value) {
243                         return;
244                     }
245                     lock = lock || !Y.Node.getDOMNode(this).checked;
246                 });
247                 return {
248                     lock : lock,
249                     hide : false
250                 }
251             },
252             _dependency_checked : function(elements, value) {
253                 var lock = false;
254                 elements.each(function(){
255                     if (this.getAttribute('type').toLowerCase()=='hidden' && !this.siblings('input[type=checkbox][name="' + this.get('name') + '"]').isEmpty()) {
256                         // This is the hidden input that is part of an advcheckbox.
257                         return;
258                     }
259                     if (this.getAttribute('type').toLowerCase()=='radio' && this.get('value') != value) {
260                         return;
261                     }
262                     lock = lock || Y.Node.getDOMNode(this).checked;
263                 });
264                 return {
265                     lock : lock,
266                     hide : false
267                 }
268             },
269             _dependency_noitemselected : function(elements, value) {
270                 var lock = false;
271                 elements.each(function(){
272                     lock = lock || this.get('selectedIndex') == -1;
273                 });
274                 return {
275                     lock : lock,
276                     hide : false
277                 }
278             },
279             _dependency_eq : function(elements, value) {
280                 var lock = false;
281                 var hidden_val = false;
282                 elements.each(function(){
283                     if (this.getAttribute('type').toLowerCase()=='radio' && !Y.Node.getDOMNode(this).checked) {
284                         return;
285                     } else if (this.getAttribute('type').toLowerCase() == 'hidden' && !this.siblings('input[type=checkbox][name="' + this.get('name') + '"]').isEmpty()) {
286                         // This is the hidden input that is part of an advcheckbox.
287                         hidden_val = (this.get('value') == value);
288                         return;
289                     } else if (this.getAttribute('type').toLowerCase() == 'checkbox' && !Y.Node.getDOMNode(this).checked) {
290                         lock = lock || hidden_val;
291                         return;
292                     }
293                     //check for filepicker status
294                     if (this.getAttribute('class').toLowerCase() == 'filepickerhidden') {
295                         var elementname = this.getAttribute('name');
296                         if (elementname && M.form_filepicker.instances[elementname].fileadded) {
297                             lock = false;
298                         } else {
299                             lock = true;
300                         }
301                     } else {
302                         lock = lock || this.get('value') == value;
303                     }
304                 });
305                 return {
306                     lock : lock,
307                     hide : false
308                 }
309             },
310             _dependency_hide : function(elements, value) {
311                 return {
312                     lock : false,
313                     hide : true
314                 }
315             },
316             _dependency_default : function(elements, value, ev) {
317                 var lock = false;
318                 var hidden_val = false;
319                 elements.each(function(){
320                     if (this.getAttribute('type').toLowerCase()=='radio' && !Y.Node.getDOMNode(this).checked) {
321                         return;
322                     } else if (this.getAttribute('type').toLowerCase() == 'hidden' && !this.siblings('input[type=checkbox][name="' + this.get('name') + '"]').isEmpty()) {
323                         // This is the hidden input that is part of an advcheckbox.
324                         hidden_val = (this.get('value') != value);
325                         return;
326                     } else if (this.getAttribute('type').toLowerCase() == 'checkbox' && !Y.Node.getDOMNode(this).checked) {
327                         lock = lock || hidden_val;
328                         return;
329                     }
330                     //check for filepicker status
331                     if (this.getAttribute('class').toLowerCase() == 'filepickerhidden') {
332                         var elementname = this.getAttribute('name');
333                         if (elementname && M.form_filepicker.instances[elementname].fileadded) {
334                             lock = true;
335                         } else {
336                             lock = false;
337                         }
338                     } else {
339                         lock = lock || this.get('value') != value;
340                     }
341                 });
342                 return {
343                     lock : lock,
344                     hide : false
345                 }
346             }
347         };
348         Y.extend(dependencyManager, Y.Base, dependencyManager.prototype, {
349             NAME : 'mform-dependency-manager'
350         });
352         return dependencyManager;
353     })();
355     return new M.form.dependencyManager();
356 };