_Mixin.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  1. /*
  2. Copyright (c) 2004-2012, The Dojo Foundation All Rights Reserved.
  3. Available via Academic Free License >= 2.1 OR the modified BSD license.
  4. see: http://dojotoolkit.org/license for details
  5. */
  6. if(!dojo._hasResource["dojox.form.manager._Mixin"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dojox.form.manager._Mixin"] = true;
  8. dojo.provide("dojox.form.manager._Mixin");
  9. dojo.require("dijit._Widget");
  10. (function(){
  11. var fm = dojox.form.manager,
  12. aa = fm.actionAdapter = function(action){
  13. // summary:
  14. // Adapter that automates application of actions to arrays.
  15. // action: Function:
  16. // Function that takes three parameters: a name, an object
  17. // (usually node or widget), and a value. This action will
  18. // be applied to all elements of array.
  19. return function(name, elems, value){
  20. if(dojo.isArray(elems)){
  21. dojo.forEach(elems, function(elem){
  22. action.call(this, name, elem, value);
  23. }, this);
  24. }else{
  25. action.apply(this, arguments);
  26. }
  27. };
  28. },
  29. ia = fm.inspectorAdapter = function(inspector){
  30. // summary:
  31. // Adapter that applies an inspector only to the first item of the array.
  32. // inspector: Function:
  33. // Function that takes three parameters: a name, an object
  34. // (usually node or widget), and a value.
  35. return function(name, elem, value){
  36. return inspector.call(this, name, dojo.isArray(elem) ? elem[0] : elem, value);
  37. };
  38. },
  39. skipNames = {domNode: 1, containerNode: 1, srcNodeRef: 1, bgIframe: 1},
  40. keys = fm._keys = function(o){
  41. // similar to dojox.lang.functional.keys
  42. var list = [], key;
  43. for(key in o){
  44. if(o.hasOwnProperty(key)){
  45. list.push(key);
  46. }
  47. }
  48. return list;
  49. },
  50. registerWidget = function(widget){
  51. var name = widget.get("name");
  52. if(name && widget instanceof dijit.form._FormWidget){
  53. if(name in this.formWidgets){
  54. var a = this.formWidgets[name].widget;
  55. if(dojo.isArray(a)){
  56. a.push(widget);
  57. }else{
  58. this.formWidgets[name].widget = [a, widget];
  59. }
  60. }else{
  61. this.formWidgets[name] = {widget: widget, connections: []};
  62. }
  63. }else{
  64. name = null;
  65. }
  66. return name;
  67. },
  68. getObserversFromWidget = function(name){
  69. var observers = {};
  70. aa(function(_, w){
  71. var o = w.get("observer");
  72. if(o && typeof o == "string"){
  73. dojo.forEach(o.split(","), function(o){
  74. o = dojo.trim(o);
  75. if(o && dojo.isFunction(this[o])){
  76. observers[o] = 1;
  77. }
  78. }, this);
  79. }
  80. }).call(this, null, this.formWidgets[name].widget);
  81. return keys(observers);
  82. },
  83. connectWidget = function(name, observers){
  84. var t = this.formWidgets[name], w = t.widget, c = t.connections;
  85. if(c.length){
  86. dojo.forEach(c, dojo.disconnect);
  87. c = t.connections = [];
  88. }
  89. if(dojo.isArray(w)){
  90. // radio buttons
  91. dojo.forEach(w, function(w){
  92. dojo.forEach(observers, function(o){
  93. c.push(dojo.connect(w, "onChange", this, function(evt){
  94. // TODO: for some reason for radio button widgets
  95. // w.checked != w.focusNode.checked when value changes.
  96. // We test the underlying value to be 100% sure.
  97. if(this.watching && dojo.attr(w.focusNode, "checked")){
  98. this[o](w.get("value"), name, w, evt);
  99. }
  100. }));
  101. }, this);
  102. }, this);
  103. }else{
  104. // the rest
  105. // the next line is a crude workaround for dijit.form.Button that fires onClick instead of onChange
  106. var eventName = w.declaredClass == "dijit.form.Button" ?
  107. "onClick" : "onChange";
  108. dojo.forEach(observers, function(o){
  109. c.push(dojo.connect(w, eventName, this, function(evt){
  110. if(this.watching){
  111. this[o](w.get("value"), name, w, evt);
  112. }
  113. }));
  114. }, this);
  115. }
  116. };
  117. dojo.declare("dojox.form.manager._Mixin", null, {
  118. // summary:
  119. // Mixin to orchestrate dynamic forms.
  120. // description:
  121. // This mixin provideas a foundation for an enhanced form
  122. // functionality: unified access to individual form elements,
  123. // unified "onchange" event processing, general event
  124. // processing, I/O orchestration, and common form-related
  125. // functionality. See additional mixins in dojox.form.manager
  126. // namespace.
  127. watching: true,
  128. startup: function(){
  129. // summary:
  130. // Called after all the widgets have been instantiated and their
  131. // dom nodes have been inserted somewhere under dojo.doc.body.
  132. if(this._started){ return; }
  133. this.formWidgets = {};
  134. this.formNodes = {};
  135. this.registerWidgetDescendants(this);
  136. this.inherited(arguments);
  137. },
  138. destroy: function(){
  139. // summary:
  140. // Called when the widget is being destroyed
  141. for(var name in this.formWidgets){
  142. dojo.forEach(this.formWidgets[name].connections, dojo.disconnect);
  143. }
  144. this.formWidgets = {};
  145. this.inherited(arguments);
  146. },
  147. // register/unregister widgets and nodes
  148. registerWidget: function(widget){
  149. // summary:
  150. // Register a widget with the form manager
  151. // widget: String|Node|dijit.form._FormWidget:
  152. // A widget, or its widgetId, or its DOM node
  153. // returns: Object:
  154. // Returns self
  155. if(typeof widget == "string"){
  156. widget = dijit.byId(widget);
  157. }else if(widget.tagName && widget.cloneNode){
  158. widget = dijit.byNode(widget);
  159. }
  160. var name = registerWidget.call(this, widget);
  161. if(name){
  162. connectWidget.call(this, name, getObserversFromWidget.call(this, name));
  163. }
  164. return this;
  165. },
  166. unregisterWidget: function(name){
  167. // summary:
  168. // Removes the widget by name from internal tables unregistering
  169. // connected observers
  170. // name: String:
  171. // Name of the to unregister
  172. // returns: Object:
  173. // Returns self
  174. if(name in this.formWidgets){
  175. dojo.forEach(this.formWidgets[name].connections, this.disconnect, this);
  176. delete this.formWidgets[name];
  177. }
  178. return this;
  179. },
  180. registerWidgetDescendants: function(widget){
  181. // summary:
  182. // Register widget's descendants with the form manager
  183. // widget: String|Node|dijit._Widget:
  184. // A widget, or its widgetId, or its DOM node
  185. // returns: Object:
  186. // Returns self
  187. // convert to widget, if required
  188. if(typeof widget == "string"){
  189. widget = dijit.byId(widget);
  190. }else if(widget.tagName && widget.cloneNode){
  191. widget = dijit.byNode(widget);
  192. }
  193. // build the map of widgets
  194. var widgets = dojo.map(widget.getDescendants(), registerWidget, this);
  195. // process observers for widgets
  196. dojo.forEach(widgets, function(name){
  197. if(name){
  198. connectWidget.call(this, name, getObserversFromWidget.call(this, name));
  199. }
  200. }, this);
  201. // do the same with nodes, if available
  202. return this.registerNodeDescendants ?
  203. this.registerNodeDescendants(widget.domNode) : this;
  204. },
  205. unregisterWidgetDescendants: function(widget){
  206. // summary:
  207. // Unregister widget's descendants with the form manager
  208. // widget: String|Node|dijit._Widget:
  209. // A widget, or its widgetId, or its DOM node
  210. // returns: Object:
  211. // Returns self
  212. // convert to widget, if required
  213. if(typeof widget == "string"){
  214. widget = dijit.byId(widget);
  215. }else if(widget.tagName && widget.cloneNode){
  216. widget = dijit.byNode(widget);
  217. }
  218. // unregister widgets by names
  219. dojo.forEach(
  220. dojo.map(
  221. widget.getDescendants(),
  222. function(w){
  223. return w instanceof dijit.form._FormWidget && w.get("name") || null;
  224. }
  225. ),
  226. function(name){
  227. if(name){
  228. this.unregisterNode(name);
  229. }
  230. },
  231. this
  232. );
  233. // do the same with nodes, if available
  234. return this.unregisterNodeDescendants ?
  235. this.unregisterNodeDescendants(widget.domNode) : this;
  236. },
  237. // value accessors
  238. formWidgetValue: function(elem, value){
  239. // summary:
  240. // Set or get a form widget by name.
  241. // elem: String|Object|Array:
  242. // Form element's name, widget object, or array or radio widgets.
  243. // value: Object?:
  244. // Optional. The value to set.
  245. // returns: Object:
  246. // For a getter it returns the value, for a setter it returns
  247. // self. If the elem is not valid, null will be returned.
  248. var isSetter = arguments.length == 2 && value !== undefined, result;
  249. if(typeof elem == "string"){
  250. elem = this.formWidgets[elem];
  251. if(elem){
  252. elem = elem.widget;
  253. }
  254. }
  255. if(!elem){
  256. return null; // Object
  257. }
  258. if(dojo.isArray(elem)){
  259. // input/radio array of widgets
  260. if(isSetter){
  261. dojo.forEach(elem, function(widget){
  262. widget.set("checked", false, !this.watching);
  263. }, this);
  264. dojo.forEach(elem, function(widget){
  265. widget.set("checked", widget.value === value, !this.watching);
  266. }, this);
  267. return this; // self
  268. }
  269. // getter
  270. dojo.some(elem, function(widget){
  271. // TODO: for some reason for radio button widgets
  272. // w.checked != w.focusNode.checked when value changes.
  273. // We test the underlying value to be 100% sure.
  274. if(dojo.attr(widget.focusNode, "checked")){
  275. //if(widget.get("checked")){
  276. result = widget;
  277. return true;
  278. }
  279. return false;
  280. });
  281. return result ? result.get("value") : ""; // String
  282. }
  283. // checkbox widget is a special case :-(
  284. if(elem.declaredClass == "dijit.form.CheckBox"){
  285. if(isSetter){
  286. elem.set("value", Boolean(value), !this.watching);
  287. return this; // self
  288. }
  289. return Boolean(elem.get("value")); // Object
  290. }
  291. // all other elements
  292. if(isSetter){
  293. elem.set("value", value, !this.watching);
  294. return this; // self
  295. }
  296. return elem.get("value"); // Object
  297. },
  298. formPointValue: function(elem, value){
  299. // summary:
  300. // Set or get a node context by name (using dojoAttachPoint).
  301. // elem: String|Object|Array:
  302. // A node.
  303. // value: Object?:
  304. // Optional. The value to set.
  305. // returns: Object:
  306. // For a getter it returns the value, for a setter it returns
  307. // self. If the elem is not valid, null will be returned.
  308. if(elem && typeof elem == "string"){
  309. elem = this[elem];
  310. }
  311. if(!elem || !elem.tagName || !elem.cloneNode){
  312. return null; // Object
  313. }
  314. if(!dojo.hasClass(elem, "dojoFormValue")){
  315. // accessing the value of the attached point not marked with CSS class 'dojoFormValue'
  316. return null;
  317. }
  318. if(arguments.length == 2 && value !== undefined){
  319. // setter
  320. elem.innerHTML = value;
  321. return this; // self
  322. }
  323. // getter
  324. return elem.innerHTML; // String
  325. },
  326. // inspectors
  327. inspectFormWidgets: function(inspector, state, defaultValue){
  328. // summary:
  329. // Run an inspector function on controlled widgets returning a result object.
  330. // inspector: Function:
  331. // A function to be called on a widget. Takes three arguments: a name, a widget object
  332. // or an array of widget objects, and a supplied value. Runs in the context of
  333. // the form manager. Returns a value that will be collected and returned as a state.
  334. // state: Object?:
  335. // Optional. If a name-value dictionary --- only listed names will be processed.
  336. // If an array, all names in the array will be processed with defaultValue.
  337. // If omitted or null, all widgets will be processed with defaultValue.
  338. // defaultValue: Object?:
  339. // Optional. The default state (true, if omitted).
  340. var name, result = {};
  341. if(state){
  342. if(dojo.isArray(state)){
  343. dojo.forEach(state, function(name){
  344. if(name in this.formWidgets){
  345. result[name] = inspector.call(this, name, this.formWidgets[name].widget, defaultValue);
  346. }
  347. }, this);
  348. }else{
  349. for(name in state){
  350. if(name in this.formWidgets){
  351. result[name] = inspector.call(this, name, this.formWidgets[name].widget, state[name]);
  352. }
  353. }
  354. }
  355. }else{
  356. for(name in this.formWidgets){
  357. result[name] = inspector.call(this, name, this.formWidgets[name].widget, defaultValue);
  358. }
  359. }
  360. return result; // Object
  361. },
  362. inspectAttachedPoints: function(inspector, state, defaultValue){
  363. // summary:
  364. // Run an inspector function on "dojoAttachPoint" nodes returning a result object.
  365. // inspector: Function:
  366. // A function to be called on a node. Takes three arguments: a name, a node or
  367. // an array of nodes, and a supplied value. Runs in the context of the form manager.
  368. // Returns a value that will be collected and returned as a state.
  369. // state: Object?:
  370. // Optional. If a name-value dictionary --- only listed names will be processed.
  371. // If an array, all names in the array will be processed with defaultValue.
  372. // If omitted or null, all attached point nodes will be processed with defaultValue.
  373. // defaultValue: Object?:
  374. // Optional. The default state (true, if omitted).
  375. var name, result = {};
  376. if(state){
  377. if(dojo.isArray(state)){
  378. dojo.forEach(state, function(name){
  379. var elem = this[name];
  380. if(elem && elem.tagName && elem.cloneNode){
  381. result[name] = inspector.call(this, name, elem, defaultValue);
  382. }
  383. }, this);
  384. }else{
  385. for(name in state){
  386. var elem = this[name];
  387. if(elem && elem.tagName && elem.cloneNode){
  388. result[name] = inspector.call(this, name, elem, state[name]);
  389. }
  390. }
  391. }
  392. }else{
  393. for(name in this){
  394. if(!(name in skipNames)){
  395. var elem = this[name];
  396. if(elem && elem.tagName && elem.cloneNode){
  397. result[name] = inspector.call(this, name, elem, defaultValue);
  398. }
  399. }
  400. }
  401. }
  402. return result; // Object
  403. },
  404. inspect: function(inspector, state, defaultValue){
  405. // summary:
  406. // Run an inspector function on controlled elements returning a result object.
  407. // inspector: Function:
  408. // A function to be called on a widget, form element, and an attached node.
  409. // Takes three arguments: a name, a node (domNode in the case of widget) or
  410. // an array of such objects, and a supplied value. Runs in the context of
  411. // the form manager. Returns a value that will be collected and returned as a state.
  412. // state: Object?:
  413. // Optional. If a name-value dictionary --- only listed names will be processed.
  414. // If an array, all names in the array will be processed with defaultValue.
  415. // If omitted or null, all controlled elements will be processed with defaultValue.
  416. // defaultValue: Object?:
  417. // Optional. The default state (true, if omitted).
  418. var result = this.inspectFormWidgets(function(name, widget, value){
  419. if(dojo.isArray(widget)){
  420. return inspector.call(this, name, dojo.map(widget, function(w){ return w.domNode; }), value);
  421. }
  422. return inspector.call(this, name, widget.domNode, value);
  423. }, state, defaultValue);
  424. if(this.inspectFormNodes){
  425. dojo.mixin(result, this.inspectFormNodes(inspector, state, defaultValue));
  426. }
  427. return dojo.mixin(result, this.inspectAttachedPoints(inspector, state, defaultValue)); // Object
  428. }
  429. });
  430. })();
  431. // These arguments can be specified for widgets which are used in forms.
  432. // Since any widget can be specified as sub widgets, mix it into the base
  433. // widget class. (This is a hack, but it's effective.)
  434. dojo.extend(dijit._Widget, {
  435. observer: ""
  436. });
  437. }