1 | define([ |
---|
2 | "dojo/_base/lang", |
---|
3 | "dojo/_base/array", |
---|
4 | "dojo/on", |
---|
5 | "dojo/dom", |
---|
6 | "dojo/dom-attr", |
---|
7 | "dojo/query", |
---|
8 | "./_Mixin", |
---|
9 | "dijit/form/_FormWidget", |
---|
10 | "dijit/_base/manager", |
---|
11 | "dojo/_base/declare" |
---|
12 | ], function(lang, array, on, dom, domAttr, query, _Mixin, _FormWidget, manager, declare){ |
---|
13 | var fm = lang.getObject("dojox.form.manager", true), |
---|
14 | aa = fm.actionAdapter, |
---|
15 | keys = fm._keys, |
---|
16 | |
---|
17 | ce = fm.changeEvent = function(node){ |
---|
18 | // summary: |
---|
19 | // Function that returns a valid "onchange" event for a given form node. |
---|
20 | // node: Node |
---|
21 | // Form node. |
---|
22 | |
---|
23 | var eventName = "click"; |
---|
24 | switch(node.tagName.toLowerCase()){ |
---|
25 | case "textarea": |
---|
26 | eventName = "keyup"; |
---|
27 | break; |
---|
28 | case "select": |
---|
29 | eventName = "change"; |
---|
30 | break; |
---|
31 | case "input": |
---|
32 | switch(node.type.toLowerCase()){ |
---|
33 | case "text": |
---|
34 | case "password": |
---|
35 | eventName = "keyup"; |
---|
36 | break; |
---|
37 | } |
---|
38 | break; |
---|
39 | // button, input/button, input/checkbox, input/radio, |
---|
40 | // input/file, input/image, input/submit, input/reset |
---|
41 | // use "onclick" (the default) |
---|
42 | } |
---|
43 | return eventName; // String |
---|
44 | }, |
---|
45 | |
---|
46 | registerNode = function(node, groupNode){ |
---|
47 | var name = domAttr.get(node, "name"); |
---|
48 | groupNode = groupNode || this.domNode; |
---|
49 | if(name && !(name in this.formWidgets)){ |
---|
50 | // verify that it is not part of any widget |
---|
51 | for(var n = node; n && n !== groupNode; n = n.parentNode){ |
---|
52 | if(domAttr.get(n, "widgetId") && manager.byNode(n).isInstanceOf(_FormWidget)){ |
---|
53 | // this is a child of some widget --- bail out |
---|
54 | return null; |
---|
55 | } |
---|
56 | } |
---|
57 | // register the node |
---|
58 | if(node.tagName.toLowerCase() == "input" && node.type.toLowerCase() == "radio"){ |
---|
59 | var a = this.formNodes[name]; |
---|
60 | a = a && a.node; |
---|
61 | if(a && lang.isArray(a)){ |
---|
62 | a.push(node); |
---|
63 | }else{ |
---|
64 | this.formNodes[name] = {node: [node], connections: []}; |
---|
65 | } |
---|
66 | }else{ |
---|
67 | this.formNodes[name] = {node: node, connections: []}; |
---|
68 | } |
---|
69 | }else{ |
---|
70 | name = null; |
---|
71 | } |
---|
72 | return name; |
---|
73 | }, |
---|
74 | |
---|
75 | getObserversFromNode = function(name){ |
---|
76 | var observers = {}; |
---|
77 | aa(function(_, n){ |
---|
78 | var o = domAttr.get(n, "data-dojo-observer") || domAttr.get(n, "observer"); |
---|
79 | if(o && typeof o == "string"){ |
---|
80 | array.forEach(o.split(","), function(o){ |
---|
81 | o = lang.trim(o); |
---|
82 | if(o && lang.isFunction(this[o])){ |
---|
83 | observers[o] = 1; |
---|
84 | } |
---|
85 | }, this); |
---|
86 | } |
---|
87 | }).call(this, null, this.formNodes[name].node); |
---|
88 | return keys(observers); |
---|
89 | }, |
---|
90 | |
---|
91 | connectNode = function(name, observers){ |
---|
92 | var t = this.formNodes[name], c = t.connections; |
---|
93 | if(c.length){ |
---|
94 | array.forEach(c, function(item){ item.remove(); }); |
---|
95 | c = t.connections = []; |
---|
96 | } |
---|
97 | aa(function(_, n){ |
---|
98 | // the next line is a crude workaround for Button that fires onClick instead of onChange |
---|
99 | var eventName = ce(n); |
---|
100 | array.forEach(observers, function(o){ |
---|
101 | c.push(on(n, eventName, lang.hitch(this, function(evt){ |
---|
102 | if(this.watching){ |
---|
103 | this[o](this.formNodeValue(name), name, n, evt); |
---|
104 | } |
---|
105 | }))); |
---|
106 | }, this); |
---|
107 | }).call(this, null, t.node); |
---|
108 | }; |
---|
109 | |
---|
110 | return declare("dojox.form.manager._NodeMixin", null, { |
---|
111 | // summary: |
---|
112 | // Mixin to orchestrate dynamic forms (works with DOM nodes). |
---|
113 | // description: |
---|
114 | // This mixin provides a foundation for an enhanced form |
---|
115 | // functionality: unified access to individual form elements, |
---|
116 | // unified "change" event processing, and general event |
---|
117 | // processing. It complements dojox/form/manager/_Mixin |
---|
118 | // extending the functionality to DOM nodes. |
---|
119 | |
---|
120 | destroy: function(){ |
---|
121 | // summary: |
---|
122 | // Called when the widget is being destroyed |
---|
123 | |
---|
124 | for(var name in this.formNodes){ |
---|
125 | array.forEach(this.formNodes[name].connections, function(item){ |
---|
126 | item.remove(); |
---|
127 | }); |
---|
128 | } |
---|
129 | this.formNodes = {}; |
---|
130 | |
---|
131 | this.inherited(arguments); |
---|
132 | }, |
---|
133 | |
---|
134 | // register/unregister widgets and nodes |
---|
135 | |
---|
136 | registerNode: function(node){ |
---|
137 | // summary: |
---|
138 | // Register a node with the form manager |
---|
139 | // node: String|Node |
---|
140 | // A node, or its id |
---|
141 | // returns: Object |
---|
142 | // Returns self |
---|
143 | if(typeof node == "string"){ |
---|
144 | node = dom.byId(node); |
---|
145 | } |
---|
146 | var name = registerNode.call(this, node); |
---|
147 | if(name){ |
---|
148 | connectNode.call(this, name, getObserversFromNode.call(this, name)); |
---|
149 | } |
---|
150 | return this; |
---|
151 | }, |
---|
152 | |
---|
153 | unregisterNode: function(name){ |
---|
154 | // summary: |
---|
155 | // Removes the node by name from internal tables unregistering |
---|
156 | // connected observers |
---|
157 | // name: String |
---|
158 | // Name of the to unregister |
---|
159 | // returns: Object |
---|
160 | // Returns self |
---|
161 | if(name in this.formNodes){ |
---|
162 | array.forEach(this.formNodes[name].connections, function(item){ |
---|
163 | item.remove(); |
---|
164 | }); |
---|
165 | delete this.formNodes[name]; |
---|
166 | } |
---|
167 | return this; |
---|
168 | }, |
---|
169 | |
---|
170 | registerNodeDescendants: function(node){ |
---|
171 | // summary: |
---|
172 | // Register node's descendants (form nodes) with the form manager |
---|
173 | // node: String|Node |
---|
174 | // A widget, or its widgetId, or its DOM node |
---|
175 | // returns: Object |
---|
176 | // Returns self |
---|
177 | |
---|
178 | if(typeof node == "string"){ |
---|
179 | node = dom.byId(node); |
---|
180 | } |
---|
181 | |
---|
182 | query("input, select, textarea, button", node). |
---|
183 | map(function(n){ |
---|
184 | return registerNode.call(this, n, node); |
---|
185 | }, this). |
---|
186 | forEach(function(name){ |
---|
187 | if(name){ |
---|
188 | connectNode.call(this, name, getObserversFromNode.call(this, name)); |
---|
189 | } |
---|
190 | }, this); |
---|
191 | |
---|
192 | return this; |
---|
193 | }, |
---|
194 | |
---|
195 | unregisterNodeDescendants: function(node){ |
---|
196 | // summary: |
---|
197 | // Unregister node's descendants (form nodes) with the form manager |
---|
198 | // node: String|Node |
---|
199 | // A widget, or its widgetId, or its DOM node |
---|
200 | // returns: Object |
---|
201 | // Returns self |
---|
202 | |
---|
203 | if(typeof node == "string"){ |
---|
204 | node = dom.byId(node); |
---|
205 | } |
---|
206 | |
---|
207 | query("input, select, textarea, button", node). |
---|
208 | map(function(n){ return domAttr.get(node, "name") || null; }). |
---|
209 | forEach(function(name){ |
---|
210 | if(name){ |
---|
211 | this.unregisterNode(name); |
---|
212 | } |
---|
213 | }, this); |
---|
214 | |
---|
215 | return this; |
---|
216 | }, |
---|
217 | |
---|
218 | // value accessors |
---|
219 | |
---|
220 | formNodeValue: function(elem, value){ |
---|
221 | // summary: |
---|
222 | // Set or get a form element by name. |
---|
223 | // elem: String|Node|Array |
---|
224 | // Form element's name, DOM node, or array or radio nodes. |
---|
225 | // value: Object? |
---|
226 | // Optional. The value to set. |
---|
227 | // returns: Object |
---|
228 | // For a getter it returns the value, for a setter it returns |
---|
229 | // self. If the elem is not valid, null will be returned. |
---|
230 | |
---|
231 | var isSetter = arguments.length == 2 && value !== undefined, result; |
---|
232 | |
---|
233 | if(typeof elem == "string"){ |
---|
234 | elem = this.formNodes[elem]; |
---|
235 | if(elem){ |
---|
236 | elem = elem.node; |
---|
237 | } |
---|
238 | } |
---|
239 | |
---|
240 | if(!elem){ |
---|
241 | return null; // Object |
---|
242 | } |
---|
243 | |
---|
244 | if(lang.isArray(elem)){ |
---|
245 | // input/radio array |
---|
246 | if(isSetter){ |
---|
247 | array.forEach(elem, function(node){ |
---|
248 | node.checked = ""; |
---|
249 | }); |
---|
250 | array.forEach(elem, function(node){ |
---|
251 | node.checked = node.value === value ? "checked" : ""; |
---|
252 | }); |
---|
253 | return this; // self |
---|
254 | } |
---|
255 | // getter |
---|
256 | array.some(elem, function(node){ |
---|
257 | if(node.checked){ |
---|
258 | result = node; |
---|
259 | return true; |
---|
260 | } |
---|
261 | return false; |
---|
262 | }); |
---|
263 | return result ? result.value : ""; // String |
---|
264 | } |
---|
265 | // all other elements |
---|
266 | switch(elem.tagName.toLowerCase()){ |
---|
267 | case "select": |
---|
268 | if(elem.multiple){ |
---|
269 | // multiple is allowed |
---|
270 | if(isSetter){ |
---|
271 | if(lang.isArray(value)){ |
---|
272 | var dict = {}; |
---|
273 | array.forEach(value, function(v){ |
---|
274 | dict[v] = 1; |
---|
275 | }); |
---|
276 | query("> option", elem).forEach(function(opt){ |
---|
277 | opt.selected = opt.value in dict; |
---|
278 | }); |
---|
279 | return this; // self |
---|
280 | } |
---|
281 | // singular property |
---|
282 | query("> option", elem).forEach(function(opt){ |
---|
283 | opt.selected = opt.value === value; |
---|
284 | }); |
---|
285 | return this; // self |
---|
286 | } |
---|
287 | // getter |
---|
288 | result = query("> option", elem).filter(function(opt){ |
---|
289 | return opt.selected; |
---|
290 | }).map(function(opt){ |
---|
291 | return opt.value; |
---|
292 | }); |
---|
293 | return result.length == 1 ? result[0] : result; // Object |
---|
294 | } |
---|
295 | // singular |
---|
296 | if(isSetter){ |
---|
297 | query("> option", elem).forEach(function(opt){ |
---|
298 | opt.selected = opt.value === value; |
---|
299 | }); |
---|
300 | return this; // self |
---|
301 | } |
---|
302 | // getter |
---|
303 | return elem.value || ""; // String |
---|
304 | case "button": |
---|
305 | if(isSetter){ |
---|
306 | elem.innerHTML = "" + value; |
---|
307 | return this; |
---|
308 | } |
---|
309 | // getter |
---|
310 | return elem.innerHTML; |
---|
311 | case "input": |
---|
312 | if(elem.type.toLowerCase() == "checkbox"){ |
---|
313 | // input/checkbox element |
---|
314 | if(isSetter){ |
---|
315 | elem.checked = value ? "checked" : ""; |
---|
316 | return this; |
---|
317 | } |
---|
318 | // getter |
---|
319 | return Boolean(elem.checked); |
---|
320 | } |
---|
321 | } |
---|
322 | // the rest of inputs |
---|
323 | if(isSetter){ |
---|
324 | elem.value = "" + value; |
---|
325 | return this; |
---|
326 | } |
---|
327 | // getter |
---|
328 | return elem.value; |
---|
329 | }, |
---|
330 | |
---|
331 | // inspectors |
---|
332 | |
---|
333 | inspectFormNodes: function(inspector, state, defaultValue){ |
---|
334 | // summary: |
---|
335 | // Run an inspector function on controlled form elements returning a result object. |
---|
336 | // inspector: Function |
---|
337 | // A function to be called on a form element. Takes three arguments: a name, a node or |
---|
338 | // an array of nodes, and a supplied value. Runs in the context of the form manager. |
---|
339 | // Returns a value that will be collected and returned as a state. |
---|
340 | // state: Object? |
---|
341 | // Optional. If a name-value dictionary --- only listed names will be processed. |
---|
342 | // If an array, all names in the array will be processed with defaultValue. |
---|
343 | // If omitted or null, all form elements will be processed with defaultValue. |
---|
344 | // defaultValue: Object? |
---|
345 | // Optional. The default state (true, if omitted). |
---|
346 | |
---|
347 | var name, result = {}; |
---|
348 | |
---|
349 | if(state){ |
---|
350 | if(lang.isArray(state)){ |
---|
351 | array.forEach(state, function(name){ |
---|
352 | if(name in this.formNodes){ |
---|
353 | result[name] = inspector.call(this, name, this.formNodes[name].node, defaultValue); |
---|
354 | } |
---|
355 | }, this); |
---|
356 | }else{ |
---|
357 | for(name in state){ |
---|
358 | if(name in this.formNodes){ |
---|
359 | result[name] = inspector.call(this, name, this.formNodes[name].node, state[name]); |
---|
360 | } |
---|
361 | } |
---|
362 | } |
---|
363 | }else{ |
---|
364 | for(name in this.formNodes){ |
---|
365 | result[name] = inspector.call(this, name, this.formNodes[name].node, defaultValue); |
---|
366 | } |
---|
367 | } |
---|
368 | |
---|
369 | return result; // Object |
---|
370 | } |
---|
371 | }); |
---|
372 | }); |
---|