_TextBoxMixin.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. define("dijit/form/_TextBoxMixin", [
  2. "dojo/_base/array", // array.forEach
  3. "dojo/_base/declare", // declare
  4. "dojo/dom", // dom.byId
  5. "dojo/_base/event", // event.stop
  6. "dojo/keys", // keys.ALT keys.CAPS_LOCK keys.CTRL keys.META keys.SHIFT
  7. "dojo/_base/lang", // lang.mixin
  8. ".." // for exporting dijit._setSelectionRange, dijit.selectInputText
  9. ], function(array, declare, dom, event, keys, lang, dijit){
  10. // module:
  11. // dijit/form/_TextBoxMixin
  12. // summary:
  13. // A mixin for textbox form input widgets
  14. var _TextBoxMixin = declare("dijit.form._TextBoxMixin", null, {
  15. // summary:
  16. // A mixin for textbox form input widgets
  17. // trim: Boolean
  18. // Removes leading and trailing whitespace if true. Default is false.
  19. trim: false,
  20. // uppercase: Boolean
  21. // Converts all characters to uppercase if true. Default is false.
  22. uppercase: false,
  23. // lowercase: Boolean
  24. // Converts all characters to lowercase if true. Default is false.
  25. lowercase: false,
  26. // propercase: Boolean
  27. // Converts the first character of each word to uppercase if true.
  28. propercase: false,
  29. // maxLength: String
  30. // HTML INPUT tag maxLength declaration.
  31. maxLength: "",
  32. // selectOnClick: [const] Boolean
  33. // If true, all text will be selected when focused with mouse
  34. selectOnClick: false,
  35. // placeHolder: String
  36. // Defines a hint to help users fill out the input field (as defined in HTML 5).
  37. // This should only contain plain text (no html markup).
  38. placeHolder: "",
  39. _getValueAttr: function(){
  40. // summary:
  41. // Hook so get('value') works as we like.
  42. // description:
  43. // For `dijit.form.TextBox` this basically returns the value of the <input>.
  44. //
  45. // For `dijit.form.MappedTextBox` subclasses, which have both
  46. // a "displayed value" and a separate "submit value",
  47. // This treats the "displayed value" as the master value, computing the
  48. // submit value from it via this.parse().
  49. return this.parse(this.get('displayedValue'), this.constraints);
  50. },
  51. _setValueAttr: function(value, /*Boolean?*/ priorityChange, /*String?*/ formattedValue){
  52. // summary:
  53. // Hook so set('value', ...) works.
  54. //
  55. // description:
  56. // Sets the value of the widget to "value" which can be of
  57. // any type as determined by the widget.
  58. //
  59. // value:
  60. // The visual element value is also set to a corresponding,
  61. // but not necessarily the same, value.
  62. //
  63. // formattedValue:
  64. // If specified, used to set the visual element value,
  65. // otherwise a computed visual value is used.
  66. //
  67. // priorityChange:
  68. // If true, an onChange event is fired immediately instead of
  69. // waiting for the next blur event.
  70. var filteredValue;
  71. if(value !== undefined){
  72. // TODO: this is calling filter() on both the display value and the actual value.
  73. // I added a comment to the filter() definition about this, but it should be changed.
  74. filteredValue = this.filter(value);
  75. if(typeof formattedValue != "string"){
  76. if(filteredValue !== null && ((typeof filteredValue != "number") || !isNaN(filteredValue))){
  77. formattedValue = this.filter(this.format(filteredValue, this.constraints));
  78. }else{ formattedValue = ''; }
  79. }
  80. }
  81. if(formattedValue != null && formattedValue != undefined && ((typeof formattedValue) != "number" || !isNaN(formattedValue)) && this.textbox.value != formattedValue){
  82. this.textbox.value = formattedValue;
  83. this._set("displayedValue", this.get("displayedValue"));
  84. }
  85. if(this.textDir == "auto"){
  86. this.applyTextDir(this.focusNode, formattedValue);
  87. }
  88. this.inherited(arguments, [filteredValue, priorityChange]);
  89. },
  90. // displayedValue: String
  91. // For subclasses like ComboBox where the displayed value
  92. // (ex: Kentucky) and the serialized value (ex: KY) are different,
  93. // this represents the displayed value.
  94. //
  95. // Setting 'displayedValue' through set('displayedValue', ...)
  96. // updates 'value', and vice-versa. Otherwise 'value' is updated
  97. // from 'displayedValue' periodically, like onBlur etc.
  98. //
  99. // TODO: move declaration to MappedTextBox?
  100. // Problem is that ComboBox references displayedValue,
  101. // for benefit of FilteringSelect.
  102. displayedValue: "",
  103. _getDisplayedValueAttr: function(){
  104. // summary:
  105. // Hook so get('displayedValue') works.
  106. // description:
  107. // Returns the displayed value (what the user sees on the screen),
  108. // after filtering (ie, trimming spaces etc.).
  109. //
  110. // For some subclasses of TextBox (like ComboBox), the displayed value
  111. // is different from the serialized value that's actually
  112. // sent to the server (see dijit.form.ValidationTextBox.serialize)
  113. // TODO: maybe we should update this.displayedValue on every keystroke so that we don't need
  114. // this method
  115. // TODO: this isn't really the displayed value when the user is typing
  116. return this.filter(this.textbox.value);
  117. },
  118. _setDisplayedValueAttr: function(/*String*/ value){
  119. // summary:
  120. // Hook so set('displayedValue', ...) works.
  121. // description:
  122. // Sets the value of the visual element to the string "value".
  123. // The widget value is also set to a corresponding,
  124. // but not necessarily the same, value.
  125. if(value === null || value === undefined){ value = '' }
  126. else if(typeof value != "string"){ value = String(value) }
  127. this.textbox.value = value;
  128. // sets the serialized value to something corresponding to specified displayedValue
  129. // (if possible), and also updates the textbox.value, for example converting "123"
  130. // to "123.00"
  131. this._setValueAttr(this.get('value'), undefined);
  132. this._set("displayedValue", this.get('displayedValue'));
  133. // textDir support
  134. if(this.textDir == "auto"){
  135. this.applyTextDir(this.focusNode, value);
  136. }
  137. },
  138. format: function(value /*=====, constraints =====*/){
  139. // summary:
  140. // Replaceable function to convert a value to a properly formatted string.
  141. // value: String
  142. // constraints: Object
  143. // tags:
  144. // protected extension
  145. return ((value == null || value == undefined) ? "" : (value.toString ? value.toString() : value));
  146. },
  147. parse: function(value /*=====, constraints =====*/){
  148. // summary:
  149. // Replaceable function to convert a formatted string to a value
  150. // value: String
  151. // constraints: Object
  152. // tags:
  153. // protected extension
  154. return value; // String
  155. },
  156. _refreshState: function(){
  157. // summary:
  158. // After the user types some characters, etc., this method is
  159. // called to check the field for validity etc. The base method
  160. // in `dijit.form.TextBox` does nothing, but subclasses override.
  161. // tags:
  162. // protected
  163. },
  164. /*=====
  165. onInput: function(event){
  166. // summary:
  167. // Connect to this function to receive notifications of various user data-input events.
  168. // Return false to cancel the event and prevent it from being processed.
  169. // event:
  170. // keydown | keypress | cut | paste | input
  171. // tags:
  172. // callback
  173. },
  174. =====*/
  175. onInput: function(){},
  176. __skipInputEvent: false,
  177. _onInput: function(){
  178. // summary:
  179. // Called AFTER the input event has happened
  180. // set text direction according to textDir that was defined in creation
  181. if(this.textDir == "auto"){
  182. this.applyTextDir(this.focusNode, this.focusNode.value);
  183. }
  184. this._refreshState();
  185. // In case someone is watch()'ing for changes to displayedValue
  186. this._set("displayedValue", this.get("displayedValue"));
  187. },
  188. postCreate: function(){
  189. // setting the value here is needed since value="" in the template causes "undefined"
  190. // and setting in the DOM (instead of the JS object) helps with form reset actions
  191. this.textbox.setAttribute("value", this.textbox.value); // DOM and JS values should be the same
  192. this.inherited(arguments);
  193. // normalize input events to reduce spurious event processing
  194. // onkeydown: do not forward modifier keys
  195. // set charOrCode to numeric keycode
  196. // onkeypress: do not forward numeric charOrCode keys (already sent through onkeydown)
  197. // onpaste & oncut: set charOrCode to 229 (IME)
  198. // oninput: if primary event not already processed, set charOrCode to 229 (IME), else do not forward
  199. var handleEvent = function(e){
  200. var charCode = e.charOrCode || e.keyCode || 229;
  201. if(e.type == "keydown"){
  202. switch(charCode){ // ignore "state" keys
  203. case keys.SHIFT:
  204. case keys.ALT:
  205. case keys.CTRL:
  206. case keys.META:
  207. case keys.CAPS_LOCK:
  208. return;
  209. default:
  210. if(charCode >= 65 && charCode <= 90){ return; } // keydown for A-Z can be processed with keypress
  211. }
  212. }
  213. if(e.type == "keypress" && typeof charCode != "string"){ return; }
  214. if(e.type == "input"){
  215. if(this.__skipInputEvent){ // duplicate event
  216. this.__skipInputEvent = false;
  217. return;
  218. }
  219. }else{
  220. this.__skipInputEvent = true;
  221. }
  222. // create fake event to set charOrCode and to know if preventDefault() was called
  223. var faux = lang.mixin({}, e, {
  224. charOrCode: charCode,
  225. wasConsumed: false,
  226. preventDefault: function(){
  227. faux.wasConsumed = true;
  228. e.preventDefault();
  229. },
  230. stopPropagation: function(){ e.stopPropagation(); }
  231. });
  232. // give web page author a chance to consume the event
  233. if(this.onInput(faux) === false){
  234. event.stop(faux); // return false means stop
  235. }
  236. if(faux.wasConsumed){ return; } // if preventDefault was called
  237. setTimeout(lang.hitch(this, "_onInput", faux), 0); // widget notification after key has posted
  238. };
  239. array.forEach([ "onkeydown", "onkeypress", "onpaste", "oncut", "oninput", "oncompositionend" ], function(event){
  240. this.connect(this.textbox, event, handleEvent);
  241. }, this);
  242. },
  243. _blankValue: '', // if the textbox is blank, what value should be reported
  244. filter: function(val){
  245. // summary:
  246. // Auto-corrections (such as trimming) that are applied to textbox
  247. // value on blur or form submit.
  248. // description:
  249. // For MappedTextBox subclasses, this is called twice
  250. // - once with the display value
  251. // - once the value as set/returned by set('value', ...)
  252. // and get('value'), ex: a Number for NumberTextBox.
  253. //
  254. // In the latter case it does corrections like converting null to NaN. In
  255. // the former case the NumberTextBox.filter() method calls this.inherited()
  256. // to execute standard trimming code in TextBox.filter().
  257. //
  258. // TODO: break this into two methods in 2.0
  259. //
  260. // tags:
  261. // protected extension
  262. if(val === null){ return this._blankValue; }
  263. if(typeof val != "string"){ return val; }
  264. if(this.trim){
  265. val = lang.trim(val);
  266. }
  267. if(this.uppercase){
  268. val = val.toUpperCase();
  269. }
  270. if(this.lowercase){
  271. val = val.toLowerCase();
  272. }
  273. if(this.propercase){
  274. val = val.replace(/[^\s]+/g, function(word){
  275. return word.substring(0,1).toUpperCase() + word.substring(1);
  276. });
  277. }
  278. return val;
  279. },
  280. _setBlurValue: function(){
  281. this._setValueAttr(this.get('value'), true);
  282. },
  283. _onBlur: function(e){
  284. if(this.disabled){ return; }
  285. this._setBlurValue();
  286. this.inherited(arguments);
  287. if(this._selectOnClickHandle){
  288. this.disconnect(this._selectOnClickHandle);
  289. }
  290. },
  291. _isTextSelected: function(){
  292. return this.textbox.selectionStart == this.textbox.selectionEnd;
  293. },
  294. _onFocus: function(/*String*/ by){
  295. if(this.disabled || this.readOnly){ return; }
  296. // Select all text on focus via click if nothing already selected.
  297. // Since mouse-up will clear the selection need to defer selection until after mouse-up.
  298. // Don't do anything on focus by tabbing into the widget since there's no associated mouse-up event.
  299. if(this.selectOnClick && by == "mouse"){
  300. this._selectOnClickHandle = this.connect(this.domNode, "onmouseup", function(){
  301. // Only select all text on first click; otherwise users would have no way to clear
  302. // the selection.
  303. this.disconnect(this._selectOnClickHandle);
  304. // Check if the user selected some text manually (mouse-down, mouse-move, mouse-up)
  305. // and if not, then select all the text
  306. if(this._isTextSelected()){
  307. _TextBoxMixin.selectInputText(this.textbox);
  308. }
  309. });
  310. }
  311. // call this.inherited() before refreshState(), since this.inherited() will possibly scroll the viewport
  312. // (to scroll the TextBox into view), which will affect how _refreshState() positions the tooltip
  313. this.inherited(arguments);
  314. this._refreshState();
  315. },
  316. reset: function(){
  317. // Overrides dijit._FormWidget.reset().
  318. // Additionally resets the displayed textbox value to ''
  319. this.textbox.value = '';
  320. this.inherited(arguments);
  321. },
  322. _setTextDirAttr: function(/*String*/ textDir){
  323. // summary:
  324. // Setter for textDir.
  325. // description:
  326. // Users shouldn't call this function; they should be calling
  327. // set('textDir', value)
  328. // tags:
  329. // private
  330. // only if new textDir is different from the old one
  331. // and on widgets creation.
  332. if(!this._created
  333. || this.textDir != textDir){
  334. this._set("textDir", textDir);
  335. // so the change of the textDir will take place immediately.
  336. this.applyTextDir(this.focusNode, this.focusNode.value);
  337. }
  338. }
  339. });
  340. _TextBoxMixin._setSelectionRange = dijit._setSelectionRange = function(/*DomNode*/ element, /*Number?*/ start, /*Number?*/ stop){
  341. if(element.setSelectionRange){
  342. element.setSelectionRange(start, stop);
  343. }
  344. };
  345. _TextBoxMixin.selectInputText = dijit.selectInputText = function(/*DomNode*/ element, /*Number?*/ start, /*Number?*/ stop){
  346. // summary:
  347. // Select text in the input element argument, from start (default 0), to stop (default end).
  348. // TODO: use functions in _editor/selection.js?
  349. element = dom.byId(element);
  350. if(isNaN(start)){ start = 0; }
  351. if(isNaN(stop)){ stop = element.value ? element.value.length : 0; }
  352. try{
  353. element.focus();
  354. _TextBoxMixin._setSelectionRange(element, start, stop);
  355. }catch(e){ /* squelch random errors (esp. on IE) from unexpected focus changes or DOM nodes being hidden */ }
  356. };
  357. return _TextBoxMixin;
  358. });