TextBox.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  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.TextBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dijit.form.TextBox"] = true;
  8. dojo.provide("dijit.form.TextBox");
  9. dojo.require("dijit.form._FormWidget");
  10. dojo.declare(
  11. "dijit.form.TextBox",
  12. dijit.form._FormValueWidget,
  13. {
  14. // summary:
  15. // A base class for textbox form inputs
  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. templateString: dojo.cache("dijit.form", "templates/TextBox.html", "<div class=\"dijit dijitReset dijitInline dijitLeft\" id=\"widget_${id}\" role=\"presentation\"\n\t><div class=\"dijitReset dijitInputField dijitInputContainer\"\n\t\t><input class=\"dijitReset dijitInputInner\" dojoAttachPoint='textbox,focusNode' autocomplete=\"off\"\n\t\t\t${!nameAttrSetting} type='${type}'\n\t/></div\n></div>\n"),
  39. _singleNodeTemplate: '<input class="dijit dijitReset dijitLeft dijitInputField" dojoAttachPoint="textbox,focusNode" autocomplete="off" type="${type}" ${!nameAttrSetting} />',
  40. _buttonInputDisabled: dojo.isIE ? "disabled" : "", // allows IE to disallow focus, but Firefox cannot be disabled for mousedown events
  41. baseClass: "dijitTextBox",
  42. attributeMap: dojo.delegate(dijit.form._FormValueWidget.prototype.attributeMap, {
  43. maxLength: "focusNode"
  44. }),
  45. postMixInProperties: function(){
  46. var type = this.type.toLowerCase();
  47. if(this.templateString && this.templateString.toLowerCase() == "input" || ((type == "hidden" || type == "file") && this.templateString == dijit.form.TextBox.prototype.templateString)){
  48. this.templateString = this._singleNodeTemplate;
  49. }
  50. this.inherited(arguments);
  51. },
  52. _setPlaceHolderAttr: function(v){
  53. this._set("placeHolder", v);
  54. if(!this._phspan){
  55. this._attachPoints.push('_phspan');
  56. /* dijitInputField class gives placeHolder same padding as the input field
  57. * parent node already has dijitInputField class but it doesn't affect this <span>
  58. * since it's position: absolute.
  59. */
  60. this._phspan = dojo.create('span',{className:'dijitPlaceHolder dijitInputField'},this.textbox,'after');
  61. }
  62. this._phspan.innerHTML="";
  63. this._phspan.appendChild(document.createTextNode(v));
  64. this._updatePlaceHolder();
  65. },
  66. _updatePlaceHolder: function(){
  67. if(this._phspan){
  68. this._phspan.style.display=(this.placeHolder&&!this._focused&&!this.textbox.value)?"":"none";
  69. }
  70. },
  71. _getValueAttr: function(){
  72. // summary:
  73. // Hook so get('value') works as we like.
  74. // description:
  75. // For `dijit.form.TextBox` this basically returns the value of the <input>.
  76. //
  77. // For `dijit.form.MappedTextBox` subclasses, which have both
  78. // a "displayed value" and a separate "submit value",
  79. // This treats the "displayed value" as the master value, computing the
  80. // submit value from it via this.parse().
  81. return this.parse(this.get('displayedValue'), this.constraints);
  82. },
  83. _setValueAttr: function(value, /*Boolean?*/ priorityChange, /*String?*/ formattedValue){
  84. // summary:
  85. // Hook so set('value', ...) works.
  86. //
  87. // description:
  88. // Sets the value of the widget to "value" which can be of
  89. // any type as determined by the widget.
  90. //
  91. // value:
  92. // The visual element value is also set to a corresponding,
  93. // but not necessarily the same, value.
  94. //
  95. // formattedValue:
  96. // If specified, used to set the visual element value,
  97. // otherwise a computed visual value is used.
  98. //
  99. // priorityChange:
  100. // If true, an onChange event is fired immediately instead of
  101. // waiting for the next blur event.
  102. var filteredValue;
  103. if(value !== undefined){
  104. // TODO: this is calling filter() on both the display value and the actual value.
  105. // I added a comment to the filter() definition about this, but it should be changed.
  106. filteredValue = this.filter(value);
  107. if(typeof formattedValue != "string"){
  108. if(filteredValue !== null && ((typeof filteredValue != "number") || !isNaN(filteredValue))){
  109. formattedValue = this.filter(this.format(filteredValue, this.constraints));
  110. }else{ formattedValue = ''; }
  111. }
  112. }
  113. if(formattedValue != null && formattedValue != undefined && ((typeof formattedValue) != "number" || !isNaN(formattedValue)) && this.textbox.value != formattedValue){
  114. this.textbox.value = formattedValue;
  115. this._set("displayedValue", this.get("displayedValue"));
  116. }
  117. this._updatePlaceHolder();
  118. this.inherited(arguments, [filteredValue, priorityChange]);
  119. },
  120. // displayedValue: String
  121. // For subclasses like ComboBox where the displayed value
  122. // (ex: Kentucky) and the serialized value (ex: KY) are different,
  123. // this represents the displayed value.
  124. //
  125. // Setting 'displayedValue' through set('displayedValue', ...)
  126. // updates 'value', and vice-versa. Otherwise 'value' is updated
  127. // from 'displayedValue' periodically, like onBlur etc.
  128. //
  129. // TODO: move declaration to MappedTextBox?
  130. // Problem is that ComboBox references displayedValue,
  131. // for benefit of FilteringSelect.
  132. displayedValue: "",
  133. getDisplayedValue: function(){
  134. // summary:
  135. // Deprecated. Use get('displayedValue') instead.
  136. // tags:
  137. // deprecated
  138. dojo.deprecated(this.declaredClass+"::getDisplayedValue() is deprecated. Use set('displayedValue') instead.", "", "2.0");
  139. return this.get('displayedValue');
  140. },
  141. _getDisplayedValueAttr: function(){
  142. // summary:
  143. // Hook so get('displayedValue') works.
  144. // description:
  145. // Returns the displayed value (what the user sees on the screen),
  146. // after filtering (ie, trimming spaces etc.).
  147. //
  148. // For some subclasses of TextBox (like ComboBox), the displayed value
  149. // is different from the serialized value that's actually
  150. // sent to the server (see dijit.form.ValidationTextBox.serialize)
  151. // TODO: maybe we should update this.displayedValue on every keystroke so that we don't need
  152. // this method
  153. // TODO: this isn't really the displayed value when the user is typing
  154. return this.filter(this.textbox.value);
  155. },
  156. setDisplayedValue: function(/*String*/ value){
  157. // summary:
  158. // Deprecated. Use set('displayedValue', ...) instead.
  159. // tags:
  160. // deprecated
  161. dojo.deprecated(this.declaredClass+"::setDisplayedValue() is deprecated. Use set('displayedValue', ...) instead.", "", "2.0");
  162. this.set('displayedValue', value);
  163. },
  164. _setDisplayedValueAttr: function(/*String*/ value){
  165. // summary:
  166. // Hook so set('displayedValue', ...) works.
  167. // description:
  168. // Sets the value of the visual element to the string "value".
  169. // The widget value is also set to a corresponding,
  170. // but not necessarily the same, value.
  171. if(value === null || value === undefined){ value = '' }
  172. else if(typeof value != "string"){ value = String(value) }
  173. this.textbox.value = value;
  174. // sets the serialized value to something corresponding to specified displayedValue
  175. // (if possible), and also updates the textbox.value, for example converting "123"
  176. // to "123.00"
  177. this._setValueAttr(this.get('value'), undefined);
  178. this._set("displayedValue", this.get('displayedValue'));
  179. },
  180. format: function(/*String*/ value, /*Object*/ constraints){
  181. // summary:
  182. // Replacable function to convert a value to a properly formatted string.
  183. // tags:
  184. // protected extension
  185. return ((value == null || value == undefined) ? "" : (value.toString ? value.toString() : value));
  186. },
  187. parse: function(/*String*/ value, /*Object*/ constraints){
  188. // summary:
  189. // Replacable function to convert a formatted string to a value
  190. // tags:
  191. // protected extension
  192. return value; // String
  193. },
  194. _refreshState: function(){
  195. // summary:
  196. // After the user types some characters, etc., this method is
  197. // called to check the field for validity etc. The base method
  198. // in `dijit.form.TextBox` does nothing, but subclasses override.
  199. // tags:
  200. // protected
  201. },
  202. _onInput: function(e){
  203. if(e && e.type && /key/i.test(e.type) && e.keyCode){
  204. switch(e.keyCode){
  205. case dojo.keys.SHIFT:
  206. case dojo.keys.ALT:
  207. case dojo.keys.CTRL:
  208. case dojo.keys.TAB:
  209. return;
  210. }
  211. }
  212. if(this.intermediateChanges){
  213. var _this = this;
  214. // the setTimeout allows the key to post to the widget input box
  215. setTimeout(function(){ _this._handleOnChange(_this.get('value'), false); }, 0);
  216. }
  217. this._refreshState();
  218. // In case someone is watch()'ing for changes to displayedValue
  219. this._set("displayedValue", this.get("displayedValue"));
  220. },
  221. postCreate: function(){
  222. if(dojo.isIE){ // IE INPUT tag fontFamily has to be set directly using STYLE
  223. // the setTimeout gives IE a chance to render the TextBox and to deal with font inheritance
  224. setTimeout(dojo.hitch(this, function(){
  225. var s = dojo.getComputedStyle(this.domNode);
  226. if(s){
  227. var ff = s.fontFamily;
  228. if(ff){
  229. var inputs = this.domNode.getElementsByTagName("INPUT");
  230. if(inputs){
  231. for(var i=0; i < inputs.length; i++){
  232. inputs[i].style.fontFamily = ff;
  233. }
  234. }
  235. }
  236. }
  237. }), 0);
  238. }
  239. // setting the value here is needed since value="" in the template causes "undefined"
  240. // and setting in the DOM (instead of the JS object) helps with form reset actions
  241. this.textbox.setAttribute("value", this.textbox.value); // DOM and JS values should be the same
  242. this.inherited(arguments);
  243. if(dojo.isMoz || dojo.isOpera){
  244. this.connect(this.textbox, "oninput", "_onInput");
  245. }else{
  246. this.connect(this.textbox, "onkeydown", "_onInput");
  247. this.connect(this.textbox, "onkeyup", "_onInput");
  248. this.connect(this.textbox, "onpaste", "_onInput");
  249. this.connect(this.textbox, "oncut", "_onInput");
  250. }
  251. },
  252. _blankValue: '', // if the textbox is blank, what value should be reported
  253. filter: function(val){
  254. // summary:
  255. // Auto-corrections (such as trimming) that are applied to textbox
  256. // value on blur or form submit.
  257. // description:
  258. // For MappedTextBox subclasses, this is called twice
  259. // - once with the display value
  260. // - once the value as set/returned by set('value', ...)
  261. // and get('value'), ex: a Number for NumberTextBox.
  262. //
  263. // In the latter case it does corrections like converting null to NaN. In
  264. // the former case the NumberTextBox.filter() method calls this.inherited()
  265. // to execute standard trimming code in TextBox.filter().
  266. //
  267. // TODO: break this into two methods in 2.0
  268. //
  269. // tags:
  270. // protected extension
  271. if(val === null){ return this._blankValue; }
  272. if(typeof val != "string"){ return val; }
  273. if(this.trim){
  274. val = dojo.trim(val);
  275. }
  276. if(this.uppercase){
  277. val = val.toUpperCase();
  278. }
  279. if(this.lowercase){
  280. val = val.toLowerCase();
  281. }
  282. if(this.propercase){
  283. val = val.replace(/[^\s]+/g, function(word){
  284. return word.substring(0,1).toUpperCase() + word.substring(1);
  285. });
  286. }
  287. return val;
  288. },
  289. _setBlurValue: function(){
  290. this._setValueAttr(this.get('value'), true);
  291. },
  292. _onBlur: function(e){
  293. if(this.disabled){ return; }
  294. this._setBlurValue();
  295. this.inherited(arguments);
  296. if(this._selectOnClickHandle){
  297. this.disconnect(this._selectOnClickHandle);
  298. }
  299. if(this.selectOnClick && dojo.isMoz){
  300. this.textbox.selectionStart = this.textbox.selectionEnd = undefined; // clear selection so that the next mouse click doesn't reselect
  301. }
  302. this._updatePlaceHolder();
  303. },
  304. _onFocus: function(/*String*/ by){
  305. if(this.disabled || this.readOnly){ return; }
  306. // Select all text on focus via click if nothing already selected.
  307. // Since mouse-up will clear the selection need to defer selection until after mouse-up.
  308. // Don't do anything on focus by tabbing into the widget since there's no associated mouse-up event.
  309. if(this.selectOnClick && by == "mouse"){
  310. this._selectOnClickHandle = this.connect(this.domNode, "onmouseup", function(){
  311. // Only select all text on first click; otherwise users would have no way to clear
  312. // the selection.
  313. this.disconnect(this._selectOnClickHandle);
  314. // Check if the user selected some text manually (mouse-down, mouse-move, mouse-up)
  315. // and if not, then select all the text
  316. var textIsNotSelected;
  317. if(dojo.isIE){
  318. var range = dojo.doc.selection.createRange();
  319. var parent = range.parentElement();
  320. textIsNotSelected = parent == this.textbox && range.text.length == 0;
  321. }else{
  322. textIsNotSelected = this.textbox.selectionStart == this.textbox.selectionEnd;
  323. }
  324. if(textIsNotSelected){
  325. dijit.selectInputText(this.textbox);
  326. }
  327. });
  328. }
  329. this._updatePlaceHolder();
  330. // call this.inherited() before refreshState(), since this.inherited() will possibly scroll the viewport
  331. // (to scroll the TextBox into view), which will affect how _refreshState() positions the tooltip
  332. this.inherited(arguments);
  333. this._refreshState();
  334. },
  335. reset: function(){
  336. // Overrides dijit._FormWidget.reset().
  337. // Additionally resets the displayed textbox value to ''
  338. this.textbox.value = '';
  339. this.inherited(arguments);
  340. }
  341. }
  342. );
  343. dijit.selectInputText = function(/*DomNode*/ element, /*Number?*/ start, /*Number?*/ stop){
  344. // summary:
  345. // Select text in the input element argument, from start (default 0), to stop (default end).
  346. // TODO: use functions in _editor/selection.js?
  347. var _window = dojo.global;
  348. var _document = dojo.doc;
  349. element = dojo.byId(element);
  350. if(isNaN(start)){ start = 0; }
  351. if(isNaN(stop)){ stop = element.value ? element.value.length : 0; }
  352. dijit.focus(element);
  353. if(_document["selection"] && dojo.body()["createTextRange"]){ // IE
  354. if(element.createTextRange){
  355. var r = element.createTextRange();
  356. r.collapse(true);
  357. r.moveStart("character", -99999); // move to 0
  358. r.moveStart("character", start); // delta from 0 is the correct position
  359. r.moveEnd("character", stop-start);
  360. r.select();
  361. }
  362. }else if(_window["getSelection"]){
  363. if(element.setSelectionRange){
  364. element.setSelectionRange(start, stop);
  365. }
  366. }
  367. };
  368. }