source: Dev/trunk/src/client/dijit/form/NumberTextBox.js @ 532

Last change on this file since 532 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

File size: 11.1 KB
Line 
1define([
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});
Note: See TracBrowser for help on using the repository browser.