_Mixin.js 15 KB

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