NumberTextBox.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. define("dijit/form/NumberTextBox", [
  2. "dojo/_base/declare", // declare
  3. "dojo/_base/lang", // lang.hitch lang.mixin
  4. "dojo/number", // number._realNumberRegexp number.format number.parse number.regexp
  5. "./RangeBoundTextBox"
  6. ], function(declare, lang, number, RangeBoundTextBox){
  7. /*=====
  8. var RangeBoundTextBox = dijit.form.RangeBoundTextBox;
  9. =====*/
  10. // module:
  11. // dijit/form/NumberTextBox
  12. // summary:
  13. // A TextBox for entering numbers, with formatting and range checking
  14. /*=====
  15. declare(
  16. "dijit.form.NumberTextBox.__Constraints",
  17. [dijit.form.RangeBoundTextBox.__Constraints, number.__FormatOptions, number.__ParseOptions], {
  18. // summary:
  19. // Specifies both the rules on valid/invalid values (minimum, maximum,
  20. // number of required decimal places), and also formatting options for
  21. // displaying the value when the field is not focused.
  22. // example:
  23. // Minimum/maximum:
  24. // To specify a field between 0 and 120:
  25. // | {min:0,max:120}
  26. // To specify a field that must be an integer:
  27. // | {fractional:false}
  28. // To specify a field where 0 to 3 decimal places are allowed on input:
  29. // | {places:'0,3'}
  30. });
  31. =====*/
  32. var NumberTextBoxMixin = declare("dijit.form.NumberTextBoxMixin", null, {
  33. // summary:
  34. // A mixin for all number textboxes
  35. // tags:
  36. // protected
  37. // Override ValidationTextBox.regExpGen().... we use a reg-ex generating function rather
  38. // than a straight regexp to deal with locale (plus formatting options too?)
  39. regExpGen: number.regexp,
  40. /*=====
  41. // constraints: dijit.form.NumberTextBox.__Constraints
  42. // Despite the name, this parameter specifies both constraints on the input
  43. // (including minimum/maximum allowed values) as well as
  44. // formatting options like places (the number of digits to display after
  45. // the decimal point). See `dijit.form.NumberTextBox.__Constraints` for details.
  46. constraints: {},
  47. ======*/
  48. // value: Number
  49. // The value of this NumberTextBox as a Javascript Number (i.e., not a String).
  50. // If the displayed value is blank, the value is NaN, and if the user types in
  51. // an gibberish value (like "hello world"), the value is undefined
  52. // (i.e. get('value') returns undefined).
  53. //
  54. // Symmetrically, set('value', NaN) will clear the displayed value,
  55. // whereas set('value', undefined) will have no effect.
  56. value: NaN,
  57. // editOptions: [protected] Object
  58. // Properties to mix into constraints when the value is being edited.
  59. // This is here because we edit the number in the format "12345", which is
  60. // different than the display value (ex: "12,345")
  61. editOptions: { pattern: '#.######' },
  62. /*=====
  63. _formatter: function(value, options){
  64. // summary:
  65. // _formatter() is called by format(). It's the base routine for formatting a number,
  66. // as a string, for example converting 12345 into "12,345".
  67. // value: Number
  68. // The number to be converted into a string.
  69. // options: dojo.number.__FormatOptions?
  70. // Formatting options
  71. // tags:
  72. // protected extension
  73. return "12345"; // String
  74. },
  75. =====*/
  76. _formatter: number.format,
  77. postMixInProperties: function(){
  78. this.inherited(arguments);
  79. this._set("type", "text"); // in case type="number" was specified which messes up parse/format
  80. },
  81. _setConstraintsAttr: function(/*Object*/ constraints){
  82. var places = typeof constraints.places == "number"? constraints.places : 0;
  83. if(places){ places++; } // decimal rounding errors take away another digit of precision
  84. if(typeof constraints.max != "number"){
  85. constraints.max = 9 * Math.pow(10, 15-places);
  86. }
  87. if(typeof constraints.min != "number"){
  88. constraints.min = -9 * Math.pow(10, 15-places);
  89. }
  90. this.inherited(arguments, [ constraints ]);
  91. if(this.focusNode && this.focusNode.value && !isNaN(this.value)){
  92. this.set('value', this.value);
  93. }
  94. },
  95. _onFocus: function(){
  96. if(this.disabled){ return; }
  97. var val = this.get('value');
  98. if(typeof val == "number" && !isNaN(val)){
  99. var formattedValue = this.format(val, this.constraints);
  100. if(formattedValue !== undefined){
  101. this.textbox.value = formattedValue;
  102. }
  103. }
  104. this.inherited(arguments);
  105. },
  106. format: function(/*Number*/ value, /*dojo.number.__FormatOptions*/ constraints){
  107. // summary:
  108. // Formats the value as a Number, according to constraints.
  109. // tags:
  110. // protected
  111. var formattedValue = String(value);
  112. if(typeof value != "number"){ return formattedValue; }
  113. if(isNaN(value)){ return ""; }
  114. // check for exponential notation that dojo.number.format chokes on
  115. if(!("rangeCheck" in this && this.rangeCheck(value, constraints)) && constraints.exponent !== false && /\de[-+]?\d/i.test(formattedValue)){
  116. return formattedValue;
  117. }
  118. if(this.editOptions && this.focused){
  119. constraints = lang.mixin({}, constraints, this.editOptions);
  120. }
  121. return this._formatter(value, constraints);
  122. },
  123. /*=====
  124. _parser: function(value, constraints){
  125. // summary:
  126. // Parses the string value as a Number, according to constraints.
  127. // value: String
  128. // String representing a number
  129. // constraints: dojo.number.__ParseOptions
  130. // Formatting options
  131. // tags:
  132. // protected
  133. return 123.45; // Number
  134. },
  135. =====*/
  136. _parser: number.parse,
  137. parse: function(/*String*/ value, /*number.__FormatOptions*/ constraints){
  138. // summary:
  139. // Replaceable function to convert a formatted string to a number value
  140. // tags:
  141. // protected extension
  142. var v = this._parser(value, lang.mixin({}, constraints, (this.editOptions && this.focused) ? this.editOptions : {}));
  143. if(this.editOptions && this.focused && isNaN(v)){
  144. v = this._parser(value, constraints); // parse w/o editOptions: not technically needed but is nice for the user
  145. }
  146. return v;
  147. },
  148. _getDisplayedValueAttr: function(){
  149. var v = this.inherited(arguments);
  150. return isNaN(v) ? this.textbox.value : v;
  151. },
  152. filter: function(/*Number*/ value){
  153. // summary:
  154. // This is called with both the display value (string), and the actual value (a number).
  155. // When called with the actual value it does corrections so that '' etc. are represented as NaN.
  156. // Otherwise it dispatches to the superclass's filter() method.
  157. //
  158. // See `dijit.form.TextBox.filter` for more details.
  159. return (value === null || value === '' || value === undefined) ? NaN : this.inherited(arguments); // set('value', null||''||undefined) should fire onChange(NaN)
  160. },
  161. serialize: function(/*Number*/ value, /*Object?*/ options){
  162. // summary:
  163. // Convert value (a Number) into a canonical string (ie, how the number literal is written in javascript/java/C/etc.)
  164. // tags:
  165. // protected
  166. return (typeof value != "number" || isNaN(value)) ? '' : this.inherited(arguments);
  167. },
  168. _setBlurValue: function(){
  169. var val = lang.hitch(lang.mixin({}, this, { focused: true }), "get")('value'); // parse with editOptions
  170. this._setValueAttr(val, true);
  171. },
  172. _setValueAttr: function(/*Number*/ value, /*Boolean?*/ priorityChange, /*String?*/ formattedValue){
  173. // summary:
  174. // Hook so set('value', ...) works.
  175. if(value !== undefined && formattedValue === undefined){
  176. formattedValue = String(value);
  177. if(typeof value == "number"){
  178. if(isNaN(value)){ formattedValue = '' }
  179. // check for exponential notation that number.format chokes on
  180. else if(("rangeCheck" in this && this.rangeCheck(value, this.constraints)) || this.constraints.exponent === false || !/\de[-+]?\d/i.test(formattedValue)){
  181. formattedValue = undefined; // lets format compute a real string value
  182. }
  183. }else if(!value){ // 0 processed in if branch above, ''|null|undefined flows through here
  184. formattedValue = '';
  185. value = NaN;
  186. }else{ // non-numeric values
  187. value = undefined;
  188. }
  189. }
  190. this.inherited(arguments, [value, priorityChange, formattedValue]);
  191. },
  192. _getValueAttr: function(){
  193. // summary:
  194. // Hook so get('value') works.
  195. // Returns Number, NaN for '', or undefined for unparseable text
  196. var v = this.inherited(arguments); // returns Number for all values accepted by parse() or NaN for all other displayed values
  197. // If the displayed value of the textbox is gibberish (ex: "hello world"), this.inherited() above
  198. // returns NaN; this if() branch converts the return value to undefined.
  199. // Returning undefined prevents user text from being overwritten when doing _setValueAttr(_getValueAttr()).
  200. // A blank displayed value is still returned as NaN.
  201. if(isNaN(v) && this.textbox.value !== ''){
  202. if(this.constraints.exponent !== false && /\de[-+]?\d/i.test(this.textbox.value) && (new RegExp("^"+number._realNumberRegexp(lang.mixin({}, this.constraints))+"$").test(this.textbox.value))){ // check for exponential notation that parse() rejected (erroneously?)
  203. var n = Number(this.textbox.value);
  204. return isNaN(n) ? undefined : n; // return exponential Number or undefined for random text (may not be possible to do with the above RegExp check)
  205. }else{
  206. return undefined; // gibberish
  207. }
  208. }else{
  209. return v; // Number or NaN for ''
  210. }
  211. },
  212. isValid: function(/*Boolean*/ isFocused){
  213. // Overrides dijit.form.RangeBoundTextBox.isValid to check that the editing-mode value is valid since
  214. // it may not be formatted according to the regExp validation rules
  215. if(!this.focused || this._isEmpty(this.textbox.value)){
  216. return this.inherited(arguments);
  217. }else{
  218. var v = this.get('value');
  219. if(!isNaN(v) && this.rangeCheck(v, this.constraints)){
  220. if(this.constraints.exponent !== false && /\de[-+]?\d/i.test(this.textbox.value)){ // exponential, parse doesn't like it
  221. return true; // valid exponential number in range
  222. }else{
  223. return this.inherited(arguments);
  224. }
  225. }else{
  226. return false;
  227. }
  228. }
  229. }
  230. });
  231. /*=====
  232. NumberTextBoxMixin = dijit.form.NumberTextBoxMixin;
  233. =====*/
  234. var NumberTextBox = declare("dijit.form.NumberTextBox", [RangeBoundTextBox,NumberTextBoxMixin], {
  235. // summary:
  236. // A TextBox for entering numbers, with formatting and range checking
  237. // description:
  238. // NumberTextBox is a textbox for entering and displaying numbers, supporting
  239. // the following main features:
  240. //
  241. // 1. Enforce minimum/maximum allowed values (as well as enforcing that the user types
  242. // a number rather than a random string)
  243. // 2. NLS support (altering roles of comma and dot as "thousands-separator" and "decimal-point"
  244. // depending on locale).
  245. // 3. Separate modes for editing the value and displaying it, specifically that
  246. // the thousands separator character (typically comma) disappears when editing
  247. // but reappears after the field is blurred.
  248. // 4. Formatting and constraints regarding the number of places (digits after the decimal point)
  249. // allowed on input, and number of places displayed when blurred (see `constraints` parameter).
  250. baseClass: "dijitTextBox dijitNumberTextBox"
  251. });
  252. NumberTextBox.Mixin = NumberTextBoxMixin; // for monkey patching
  253. return NumberTextBox;
  254. });