_TextBoxMixin.js.uncompressed.js 16 KB

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