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