source: Dev/branches/rest-dojo-ui/client/dijit/form/NumberTextBox.js @ 274

Last change on this file since 274 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

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