2dd75f498f01e1516299765e77413d38d98dfd2d
[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 a filepicker
211                     if (this.getAttribute('class') == 'filepickerhidden'){
212                         var pickerbuttons = form.elementsByName(name + 'choose');
213                         pickerbuttons.each(function(){
214                             var clientid = this.get('id').split('-')[2];
215                             var filepicker = Y.one('#file_info_'+clientid);
216                             if (disabled){
217                                 this.setAttribute('disabled','disabled');
218                                 filepicker.addClass('disabled');
219                             } else {
220                                 this.removeAttribute('disabled');
221                                 filepicker.removeClass('disabled');
222                             }
223                         });
224                     }
225                 })
226             },
227             /**
228              * Hides all elements with the given name.
229              */
230             _hideElement : function(name, hidden) {
231                 var els = this.elementsByName(name);
232                 els.each(function(){
233                     var e = els.ancestor('.fitem');
234                     if (e) {
235                         e.setStyles({
236                             display : (hidden)?'none':''
237                         })
238                     }
239                 });
240             },
241             _dependency_notchecked : function(elements, value) {
242                 var lock = false;
243                 elements.each(function(){
244                     if (this.getAttribute('type').toLowerCase()=='hidden' && !this.siblings('input[type=checkbox][name="' + this.get('name') + '"]').isEmpty()) {
245                         // This is the hidden input that is part of an advcheckbox.
246                         return;
247                     }
248                     if (this.getAttribute('type').toLowerCase()=='radio' && this.get('value') != value) {
249                         return;
250                     }
251                     lock = lock || !Y.Node.getDOMNode(this).checked;
252                 });
253                 return {
254                     lock : lock,
255                     hide : false
256                 }
257             },
258             _dependency_checked : function(elements, value) {
259                 var lock = false;
260                 elements.each(function(){
261                     if (this.getAttribute('type').toLowerCase()=='hidden' && !this.siblings('input[type=checkbox][name="' + this.get('name') + '"]').isEmpty()) {
262                         // This is the hidden input that is part of an advcheckbox.
263                         return;
264                     }
265                     if (this.getAttribute('type').toLowerCase()=='radio' && this.get('value') != value) {
266                         return;
267                     }
268                     lock = lock || Y.Node.getDOMNode(this).checked;
269                 });
270                 return {
271                     lock : lock,
272                     hide : false
273                 }
274             },
275             _dependency_noitemselected : function(elements, value) {
276                 var lock = false;
277                 elements.each(function(){
278                     lock = lock || this.get('selectedIndex') == -1;
279                 });
280                 return {
281                     lock : lock,
282                     hide : false
283                 }
284             },
285             _dependency_eq : function(elements, value) {
286                 var lock = false;
287                 var hidden_val = false;
288                 elements.each(function(){
289                     if (this.getAttribute('type').toLowerCase()=='radio' && !Y.Node.getDOMNode(this).checked) {
290                         return;
291                     } else if (this.getAttribute('type').toLowerCase() == 'hidden' && !this.siblings('input[type=checkbox][name="' + this.get('name') + '"]').isEmpty()) {
292                         // This is the hidden input that is part of an advcheckbox.
293                         hidden_val = (this.get('value') == value);
294                         return;
295                     } else if (this.getAttribute('type').toLowerCase() == 'checkbox' && !Y.Node.getDOMNode(this).checked) {
296                         lock = lock || hidden_val;
297                         return;
298                     }
299                     //check for filepicker status
300                     if (this.getAttribute('class').toLowerCase() == 'filepickerhidden') {
301                         var elementname = this.getAttribute('name');
302                         if (elementname && M.form_filepicker.instances[elementname].fileadded) {
303                             lock = false;
304                         } else {
305                             lock = true;
306                         }
307                     } else {
308                         lock = lock || this.get('value') == value;
309                     }
310                 });
311                 return {
312                     lock : lock,
313                     hide : false
314                 }
315             },
316             _dependency_hide : function(elements, value) {
317                 return {
318                     lock : false,
319                     hide : true
320                 }
321             },
322             _dependency_default : function(elements, value, ev) {
323                 var lock = false;
324                 var hidden_val = false;
325                 elements.each(function(){
326                     if (this.getAttribute('type').toLowerCase()=='radio' && !Y.Node.getDOMNode(this).checked) {
327                         return;
328                     } else if (this.getAttribute('type').toLowerCase() == 'hidden' && !this.siblings('input[type=checkbox][name="' + this.get('name') + '"]').isEmpty()) {
329                         // This is the hidden input that is part of an advcheckbox.
330                         hidden_val = (this.get('value') != value);
331                         return;
332                     } else if (this.getAttribute('type').toLowerCase() == 'checkbox' && !Y.Node.getDOMNode(this).checked) {
333                         lock = lock || hidden_val;
334                         return;
335                     }
336                     //check for filepicker status
337                     if (this.getAttribute('class').toLowerCase() == 'filepickerhidden') {
338                         var elementname = this.getAttribute('name');
339                         if (elementname && M.form_filepicker.instances[elementname].fileadded) {
340                             lock = true;
341                         } else {
342                             lock = false;
343                         }
344                     } else {
345                         lock = lock || this.get('value') != value;
346                     }
347                 });
348                 return {
349                     lock : lock,
350                     hide : false
351                 }
352             }
353         };
354         Y.extend(dependencyManager, Y.Base, dependencyManager.prototype, {
355             NAME : 'mform-dependency-manager'
356         });
358         return dependencyManager;
359     })();
361     return new M.form.dependencyManager();
362 };