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