_NodeMixin.js 9.7 KB

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