_FormWidgetMixin.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. define("dijit/form/_FormWidgetMixin", [
  2. "dojo/_base/array", // array.forEach
  3. "dojo/_base/declare", // declare
  4. "dojo/dom-attr", // domAttr.set
  5. "dojo/dom-style", // domStyle.get
  6. "dojo/_base/lang", // lang.hitch lang.isArray
  7. "dojo/mouse", // mouse.isLeft
  8. "dojo/_base/sniff", // has("webkit")
  9. "dojo/_base/window", // win.body
  10. "dojo/window", // winUtils.scrollIntoView
  11. "../a11y" // a11y.hasDefaultTabStop
  12. ], function(array, declare, domAttr, domStyle, lang, mouse, has, win, winUtils, a11y){
  13. // module:
  14. // dijit/form/_FormWidgetMixin
  15. // summary:
  16. // Mixin 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. return declare("dijit.form._FormWidgetMixin", null, {
  19. // summary:
  20. // Mixin for widgets corresponding to native HTML elements such as <checkbox> or <button>,
  21. // which can be children of a <form> node or a `dijit.form.Form` widget.
  22. //
  23. // description:
  24. // Represents a single HTML element.
  25. // All these widgets should have these attributes just like native HTML input elements.
  26. // You can set them during widget construction or afterwards, via `dijit._Widget.attr`.
  27. //
  28. // They also share some common methods.
  29. // name: [const] String
  30. // Name used when submitting form; same as "name" attribute or plain HTML elements
  31. name: "",
  32. // alt: String
  33. // Corresponds to the native HTML <input> element's attribute.
  34. alt: "",
  35. // value: String
  36. // Corresponds to the native HTML <input> element's attribute.
  37. value: "",
  38. // type: [const] String
  39. // Corresponds to the native HTML <input> element's attribute.
  40. type: "text",
  41. // tabIndex: Integer
  42. // Order fields are traversed when user hits the tab key
  43. tabIndex: "0",
  44. _setTabIndexAttr: "focusNode", // force copy even when tabIndex default value, needed since Button is <span>
  45. // disabled: Boolean
  46. // Should this widget respond to user input?
  47. // In markup, this is specified as "disabled='disabled'", or just "disabled".
  48. disabled: false,
  49. // intermediateChanges: Boolean
  50. // Fires onChange for each value change or only on demand
  51. intermediateChanges: false,
  52. // scrollOnFocus: Boolean
  53. // On focus, should this widget scroll into view?
  54. scrollOnFocus: true,
  55. // Override _WidgetBase mapping id to this.domNode, needs to be on focusNode so <label> etc.
  56. // works with screen reader
  57. _setIdAttr: "focusNode",
  58. _setDisabledAttr: function(/*Boolean*/ value){
  59. this._set("disabled", value);
  60. domAttr.set(this.focusNode, 'disabled', value);
  61. if(this.valueNode){
  62. domAttr.set(this.valueNode, 'disabled', value);
  63. }
  64. this.focusNode.setAttribute("aria-disabled", value ? "true" : "false");
  65. if(value){
  66. // reset these, because after the domNode is disabled, we can no longer receive
  67. // mouse related events, see #4200
  68. this._set("hovering", false);
  69. this._set("active", false);
  70. // clear tab stop(s) on this widget's focusable node(s) (ComboBox has two focusable nodes)
  71. var attachPointNames = "tabIndex" in this.attributeMap ? this.attributeMap.tabIndex :
  72. ("_setTabIndexAttr" in this) ? this._setTabIndexAttr : "focusNode";
  73. array.forEach(lang.isArray(attachPointNames) ? attachPointNames : [attachPointNames], function(attachPointName){
  74. var node = this[attachPointName];
  75. // complex code because tabIndex=-1 on a <div> doesn't work on FF
  76. if(has("webkit") || a11y.hasDefaultTabStop(node)){ // see #11064 about webkit bug
  77. node.setAttribute('tabIndex', "-1");
  78. }else{
  79. node.removeAttribute('tabIndex');
  80. }
  81. }, this);
  82. }else{
  83. if(this.tabIndex != ""){
  84. this.set('tabIndex', this.tabIndex);
  85. }
  86. }
  87. },
  88. _onFocus: function(/*String*/ by){
  89. // If user clicks on the widget, even if the mouse is released outside of it,
  90. // this widget's focusNode should get focus (to mimic native browser hehavior).
  91. // Browsers often need help to make sure the focus via mouse actually gets to the focusNode.
  92. if(by == "mouse" && this.isFocusable()){
  93. // IE exhibits strange scrolling behavior when refocusing a node so only do it when !focused.
  94. var focusConnector = this.connect(this.focusNode, "onfocus", function(){
  95. this.disconnect(mouseUpConnector);
  96. this.disconnect(focusConnector);
  97. });
  98. // Set a global event to handle mouseup, so it fires properly
  99. // even if the cursor leaves this.domNode before the mouse up event.
  100. var mouseUpConnector = this.connect(win.body(), "onmouseup", function(){
  101. this.disconnect(mouseUpConnector);
  102. this.disconnect(focusConnector);
  103. // if here, then the mousedown did not focus the focusNode as the default action
  104. if(this.focused){
  105. this.focus();
  106. }
  107. });
  108. }
  109. if(this.scrollOnFocus){
  110. this.defer(function(){ winUtils.scrollIntoView(this.domNode); }); // without defer, the input caret position can change on mouse click
  111. }
  112. this.inherited(arguments);
  113. },
  114. isFocusable: function(){
  115. // summary:
  116. // Tells if this widget is focusable or not. Used internally by dijit.
  117. // tags:
  118. // protected
  119. return !this.disabled && this.focusNode && (domStyle.get(this.domNode, "display") != "none");
  120. },
  121. focus: function(){
  122. // summary:
  123. // Put focus on this widget
  124. if(!this.disabled && this.focusNode.focus){
  125. try{ this.focusNode.focus(); }catch(e){}/*squelch errors from hidden nodes*/
  126. }
  127. },
  128. compare: function(/*anything*/ val1, /*anything*/ val2){
  129. // summary:
  130. // Compare 2 values (as returned by get('value') for this widget).
  131. // tags:
  132. // protected
  133. if(typeof val1 == "number" && typeof val2 == "number"){
  134. return (isNaN(val1) && isNaN(val2)) ? 0 : val1 - val2;
  135. }else if(val1 > val2){
  136. return 1;
  137. }else if(val1 < val2){
  138. return -1;
  139. }else{
  140. return 0;
  141. }
  142. },
  143. onChange: function(/*===== newValue =====*/){
  144. // summary:
  145. // Callback when this widget's value is changed.
  146. // tags:
  147. // callback
  148. },
  149. // _onChangeActive: [private] Boolean
  150. // Indicates that changes to the value should call onChange() callback.
  151. // This is false during widget initialization, to avoid calling onChange()
  152. // when the initial value is set.
  153. _onChangeActive: false,
  154. _handleOnChange: function(/*anything*/ newValue, /*Boolean?*/ priorityChange){
  155. // summary:
  156. // Called when the value of the widget is set. Calls onChange() if appropriate
  157. // newValue:
  158. // the new value
  159. // priorityChange:
  160. // For a slider, for example, dragging the slider is priorityChange==false,
  161. // but on mouse up, it's priorityChange==true. If intermediateChanges==false,
  162. // onChange is only called form priorityChange=true events.
  163. // tags:
  164. // private
  165. if(this._lastValueReported == undefined && (priorityChange === null || !this._onChangeActive)){
  166. // this block executes not for a change, but during initialization,
  167. // and is used to store away the original value (or for ToggleButton, the original checked state)
  168. this._resetValue = this._lastValueReported = newValue;
  169. }
  170. this._pendingOnChange = this._pendingOnChange
  171. || (typeof newValue != typeof this._lastValueReported)
  172. || (this.compare(newValue, this._lastValueReported) != 0);
  173. if((this.intermediateChanges || priorityChange || priorityChange === undefined) && this._pendingOnChange){
  174. this._lastValueReported = newValue;
  175. this._pendingOnChange = false;
  176. if(this._onChangeActive){
  177. if(this._onChangeHandle){
  178. this._onChangeHandle.remove();
  179. }
  180. // defer allows hidden value processing to run and
  181. // also the onChange handler can safely adjust focus, etc
  182. this._onChangeHandle = this.defer(
  183. function(){
  184. this._onChangeHandle = null;
  185. this.onChange(newValue);
  186. }); // try to collapse multiple onChange's fired faster than can be processed
  187. }
  188. }
  189. },
  190. create: function(){
  191. // Overrides _Widget.create()
  192. this.inherited(arguments);
  193. this._onChangeActive = true;
  194. },
  195. destroy: function(){
  196. if(this._onChangeHandle){ // destroy called before last onChange has fired
  197. this._onChangeHandle.remove();
  198. this.onChange(this._lastValueReported);
  199. }
  200. this.inherited(arguments);
  201. }
  202. });
  203. });