_FormWidget.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  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["dijit.form._FormWidget"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dijit.form._FormWidget"] = true;
  8. dojo.provide("dijit.form._FormWidget");
  9. dojo.require("dojo.window");
  10. dojo.require("dijit._Widget");
  11. dojo.require("dijit._Templated");
  12. dojo.require("dijit._CssStateMixin");
  13. dojo.declare("dijit.form._FormWidget", [dijit._Widget, dijit._Templated, dijit._CssStateMixin],
  14. {
  15. // summary:
  16. // Base class for widgets corresponding to native HTML elements such as <checkbox> or <button>,
  17. // which can be children of a <form> node or a `dijit.form.Form` widget.
  18. //
  19. // description:
  20. // Represents a single HTML element.
  21. // All these widgets should have these attributes just like native HTML input elements.
  22. // You can set them during widget construction or afterwards, via `dijit._Widget.attr`.
  23. //
  24. // They also share some common methods.
  25. // name: [const] String
  26. // Name used when submitting form; same as "name" attribute or plain HTML elements
  27. name: "",
  28. // alt: String
  29. // Corresponds to the native HTML <input> element's attribute.
  30. alt: "",
  31. // value: String
  32. // Corresponds to the native HTML <input> element's attribute.
  33. value: "",
  34. // type: String
  35. // Corresponds to the native HTML <input> element's attribute.
  36. type: "text",
  37. // tabIndex: Integer
  38. // Order fields are traversed when user hits the tab key
  39. tabIndex: "0",
  40. // disabled: Boolean
  41. // Should this widget respond to user input?
  42. // In markup, this is specified as "disabled='disabled'", or just "disabled".
  43. disabled: false,
  44. // intermediateChanges: Boolean
  45. // Fires onChange for each value change or only on demand
  46. intermediateChanges: false,
  47. // scrollOnFocus: Boolean
  48. // On focus, should this widget scroll into view?
  49. scrollOnFocus: true,
  50. // These mixins assume that the focus node is an INPUT, as many but not all _FormWidgets are.
  51. attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, {
  52. value: "focusNode",
  53. id: "focusNode",
  54. tabIndex: "focusNode",
  55. alt: "focusNode",
  56. title: "focusNode"
  57. }),
  58. postMixInProperties: function(){
  59. // Setup name=foo string to be referenced from the template (but only if a name has been specified)
  60. // Unfortunately we can't use attributeMap to set the name due to IE limitations, see #8660
  61. // Regarding escaping, see heading "Attribute values" in
  62. // http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.3.2
  63. this.nameAttrSetting = this.name ? ('name="' + this.name.replace(/'/g, "&quot;") + '"') : '';
  64. this.inherited(arguments);
  65. },
  66. postCreate: function(){
  67. this.inherited(arguments);
  68. this.connect(this.domNode, "onmousedown", "_onMouseDown");
  69. },
  70. _setDisabledAttr: function(/*Boolean*/ value){
  71. this._set("disabled", value);
  72. dojo.attr(this.focusNode, 'disabled', value);
  73. if(this.valueNode){
  74. dojo.attr(this.valueNode, 'disabled', value);
  75. }
  76. dijit.setWaiState(this.focusNode, "disabled", value);
  77. if(value){
  78. // reset these, because after the domNode is disabled, we can no longer receive
  79. // mouse related events, see #4200
  80. this._set("hovering", false);
  81. this._set("active", false);
  82. // clear tab stop(s) on this widget's focusable node(s) (ComboBox has two focusable nodes)
  83. var attachPointNames = "tabIndex" in this.attributeMap ? this.attributeMap.tabIndex : "focusNode";
  84. dojo.forEach(dojo.isArray(attachPointNames) ? attachPointNames : [attachPointNames], function(attachPointName){
  85. var node = this[attachPointName];
  86. // complex code because tabIndex=-1 on a <div> doesn't work on FF
  87. if(dojo.isWebKit || dijit.hasDefaultTabStop(node)){ // see #11064 about webkit bug
  88. node.setAttribute('tabIndex', "-1");
  89. }else{
  90. node.removeAttribute('tabIndex');
  91. }
  92. }, this);
  93. }else{
  94. if(this.tabIndex != ""){
  95. this.focusNode.setAttribute('tabIndex', this.tabIndex);
  96. }
  97. }
  98. },
  99. setDisabled: function(/*Boolean*/ disabled){
  100. // summary:
  101. // Deprecated. Use set('disabled', ...) instead.
  102. dojo.deprecated("setDisabled("+disabled+") is deprecated. Use set('disabled',"+disabled+") instead.", "", "2.0");
  103. this.set('disabled', disabled);
  104. },
  105. _onFocus: function(e){
  106. if(this.scrollOnFocus){
  107. dojo.window.scrollIntoView(this.domNode);
  108. }
  109. this.inherited(arguments);
  110. },
  111. isFocusable: function(){
  112. // summary:
  113. // Tells if this widget is focusable or not. Used internally by dijit.
  114. // tags:
  115. // protected
  116. return !this.disabled && this.focusNode && (dojo.style(this.domNode, "display") != "none");
  117. },
  118. focus: function(){
  119. // summary:
  120. // Put focus on this widget
  121. if(!this.disabled){
  122. dijit.focus(this.focusNode);
  123. }
  124. },
  125. compare: function(/*anything*/ val1, /*anything*/ val2){
  126. // summary:
  127. // Compare 2 values (as returned by get('value') for this widget).
  128. // tags:
  129. // protected
  130. if(typeof val1 == "number" && typeof val2 == "number"){
  131. return (isNaN(val1) && isNaN(val2)) ? 0 : val1 - val2;
  132. }else if(val1 > val2){
  133. return 1;
  134. }else if(val1 < val2){
  135. return -1;
  136. }else{
  137. return 0;
  138. }
  139. },
  140. onChange: function(newValue){
  141. // summary:
  142. // Callback when this widget's value is changed.
  143. // tags:
  144. // callback
  145. },
  146. // _onChangeActive: [private] Boolean
  147. // Indicates that changes to the value should call onChange() callback.
  148. // This is false during widget initialization, to avoid calling onChange()
  149. // when the initial value is set.
  150. _onChangeActive: false,
  151. _handleOnChange: function(/*anything*/ newValue, /*Boolean?*/ priorityChange){
  152. // summary:
  153. // Called when the value of the widget is set. Calls onChange() if appropriate
  154. // newValue:
  155. // the new value
  156. // priorityChange:
  157. // For a slider, for example, dragging the slider is priorityChange==false,
  158. // but on mouse up, it's priorityChange==true. If intermediateChanges==false,
  159. // onChange is only called form priorityChange=true events.
  160. // tags:
  161. // private
  162. if(this._lastValueReported == undefined && (priorityChange === null || !this._onChangeActive)){
  163. // this block executes not for a change, but during initialization,
  164. // and is used to store away the original value (or for ToggleButton, the original checked state)
  165. this._resetValue = this._lastValueReported = newValue;
  166. }
  167. this._pendingOnChange = this._pendingOnChange
  168. || (typeof newValue != typeof this._lastValueReported)
  169. || (this.compare(newValue, this._lastValueReported) != 0);
  170. if((this.intermediateChanges || priorityChange || priorityChange === undefined) && this._pendingOnChange){
  171. this._lastValueReported = newValue;
  172. this._pendingOnChange = false;
  173. if(this._onChangeActive){
  174. if(this._onChangeHandle){
  175. clearTimeout(this._onChangeHandle);
  176. }
  177. // setTimout allows hidden value processing to run and
  178. // also the onChange handler can safely adjust focus, etc
  179. this._onChangeHandle = setTimeout(dojo.hitch(this,
  180. function(){
  181. this._onChangeHandle = null;
  182. this.onChange(newValue);
  183. }), 0); // try to collapse multiple onChange's fired faster than can be processed
  184. }
  185. }
  186. },
  187. create: function(){
  188. // Overrides _Widget.create()
  189. this.inherited(arguments);
  190. this._onChangeActive = true;
  191. },
  192. destroy: function(){
  193. if(this._onChangeHandle){ // destroy called before last onChange has fired
  194. clearTimeout(this._onChangeHandle);
  195. this.onChange(this._lastValueReported);
  196. }
  197. this.inherited(arguments);
  198. },
  199. setValue: function(/*String*/ value){
  200. // summary:
  201. // Deprecated. Use set('value', ...) instead.
  202. dojo.deprecated("dijit.form._FormWidget:setValue("+value+") is deprecated. Use set('value',"+value+") instead.", "", "2.0");
  203. this.set('value', value);
  204. },
  205. getValue: function(){
  206. // summary:
  207. // Deprecated. Use get('value') instead.
  208. dojo.deprecated(this.declaredClass+"::getValue() is deprecated. Use get('value') instead.", "", "2.0");
  209. return this.get('value');
  210. },
  211. _onMouseDown: function(e){
  212. // If user clicks on the button, even if the mouse is released outside of it,
  213. // this button should get focus (to mimics native browser buttons).
  214. // This is also needed on chrome because otherwise buttons won't get focus at all,
  215. // which leads to bizarre focus restore on Dialog close etc.
  216. if(!e.ctrlKey && dojo.mouseButtons.isLeft(e) && this.isFocusable()){ // !e.ctrlKey to ignore right-click on mac
  217. // Set a global event to handle mouseup, so it fires properly
  218. // even if the cursor leaves this.domNode before the mouse up event.
  219. var mouseUpConnector = this.connect(dojo.body(), "onmouseup", function(){
  220. if (this.isFocusable()) {
  221. this.focus();
  222. }
  223. this.disconnect(mouseUpConnector);
  224. });
  225. }
  226. }
  227. });
  228. dojo.declare("dijit.form._FormValueWidget", dijit.form._FormWidget,
  229. {
  230. // summary:
  231. // Base class for widgets corresponding to native HTML elements such as <input> or <select> that have user changeable values.
  232. // description:
  233. // Each _FormValueWidget represents a single input value, and has a (possibly hidden) <input> element,
  234. // to which it serializes it's input value, so that form submission (either normal submission or via FormBind?)
  235. // works as expected.
  236. // Don't attempt to mixin the 'type', 'name' attributes here programatically -- they must be declared
  237. // directly in the template as read by the parser in order to function. IE is known to specifically
  238. // require the 'name' attribute at element creation time. See #8484, #8660.
  239. // TODO: unclear what that {value: ""} is for; FormWidget.attributeMap copies value to focusNode,
  240. // so maybe {value: ""} is so the value *doesn't* get copied to focusNode?
  241. // Seems like we really want value removed from attributeMap altogether
  242. // (although there's no easy way to do that now)
  243. // readOnly: Boolean
  244. // Should this widget respond to user input?
  245. // In markup, this is specified as "readOnly".
  246. // Similar to disabled except readOnly form values are submitted.
  247. readOnly: false,
  248. attributeMap: dojo.delegate(dijit.form._FormWidget.prototype.attributeMap, {
  249. value: "",
  250. readOnly: "focusNode"
  251. }),
  252. _setReadOnlyAttr: function(/*Boolean*/ value){
  253. dojo.attr(this.focusNode, 'readOnly', value);
  254. dijit.setWaiState(this.focusNode, "readonly", value);
  255. this._set("readOnly", value);
  256. },
  257. postCreate: function(){
  258. this.inherited(arguments);
  259. if(dojo.isIE < 9 || (dojo.isIE && dojo.isQuirks)){ // IE won't stop the event with keypress
  260. this.connect(this.focusNode || this.domNode, "onkeydown", this._onKeyDown);
  261. }
  262. // Update our reset value if it hasn't yet been set (because this.set()
  263. // is only called when there *is* a value)
  264. if(this._resetValue === undefined){
  265. this._lastValueReported = this._resetValue = this.value;
  266. }
  267. },
  268. _setValueAttr: function(/*anything*/ newValue, /*Boolean?*/ priorityChange){
  269. // summary:
  270. // Hook so set('value', value) works.
  271. // description:
  272. // Sets the value of the widget.
  273. // If the value has changed, then fire onChange event, unless priorityChange
  274. // is specified as null (or false?)
  275. this._handleOnChange(newValue, priorityChange);
  276. },
  277. _handleOnChange: function(/*anything*/ newValue, /*Boolean?*/ priorityChange){
  278. // summary:
  279. // Called when the value of the widget has changed. Saves the new value in this.value,
  280. // and calls onChange() if appropriate. See _FormWidget._handleOnChange() for details.
  281. this._set("value", newValue);
  282. this.inherited(arguments);
  283. },
  284. undo: function(){
  285. // summary:
  286. // Restore the value to the last value passed to onChange
  287. this._setValueAttr(this._lastValueReported, false);
  288. },
  289. reset: function(){
  290. // summary:
  291. // Reset the widget's value to what it was at initialization time
  292. this._hasBeenBlurred = false;
  293. this._setValueAttr(this._resetValue, true);
  294. },
  295. _onKeyDown: function(e){
  296. if(e.keyCode == dojo.keys.ESCAPE && !(e.ctrlKey || e.altKey || e.metaKey)){
  297. var te;
  298. if(dojo.isIE){
  299. e.preventDefault(); // default behavior needs to be stopped here since keypress is too late
  300. te = document.createEventObject();
  301. te.keyCode = dojo.keys.ESCAPE;
  302. te.shiftKey = e.shiftKey;
  303. e.srcElement.fireEvent('onkeypress', te);
  304. }
  305. }
  306. },
  307. _layoutHackIE7: function(){
  308. // summary:
  309. // Work around table sizing bugs on IE7 by forcing redraw
  310. if(dojo.isIE == 7){ // fix IE7 layout bug when the widget is scrolled out of sight
  311. var domNode = this.domNode;
  312. var parent = domNode.parentNode;
  313. var pingNode = domNode.firstChild || domNode; // target node most unlikely to have a custom filter
  314. var origFilter = pingNode.style.filter; // save custom filter, most likely nothing
  315. var _this = this;
  316. while(parent && parent.clientHeight == 0){ // search for parents that haven't rendered yet
  317. (function ping(){
  318. var disconnectHandle = _this.connect(parent, "onscroll",
  319. function(e){
  320. _this.disconnect(disconnectHandle); // only call once
  321. pingNode.style.filter = (new Date()).getMilliseconds(); // set to anything that's unique
  322. setTimeout(function(){ pingNode.style.filter = origFilter }, 0); // restore custom filter, if any
  323. }
  324. );
  325. })();
  326. parent = parent.parentNode;
  327. }
  328. }
  329. }
  330. });
  331. }