275 lines
10 KiB
JavaScript
275 lines
10 KiB
JavaScript
define("dijit/form/NumberTextBox", [
|
|
"dojo/_base/declare", // declare
|
|
"dojo/_base/lang", // lang.hitch lang.mixin
|
|
"dojo/number", // number._realNumberRegexp number.format number.parse number.regexp
|
|
"./RangeBoundTextBox"
|
|
], function(declare, lang, number, RangeBoundTextBox){
|
|
|
|
// module:
|
|
// dijit/form/NumberTextBox
|
|
|
|
|
|
var NumberTextBoxMixin = declare("dijit.form.NumberTextBoxMixin", null, {
|
|
// summary:
|
|
// A mixin for all number textboxes
|
|
// tags:
|
|
// protected
|
|
|
|
// Override ValidationTextBox.pattern.... we use a reg-ex generating function rather
|
|
// than a straight regexp to deal with locale (plus formatting options too?)
|
|
pattern: number.regexp,
|
|
|
|
/*=====
|
|
// constraints: NumberTextBox.__Constraints
|
|
// Despite the name, this parameter specifies both constraints on the input
|
|
// (including minimum/maximum allowed values) as well as
|
|
// formatting options like places (the number of digits to display after
|
|
// the decimal point).
|
|
constraints: {},
|
|
======*/
|
|
|
|
// value: Number
|
|
// The value of this NumberTextBox as a Javascript Number (i.e., not a String).
|
|
// If the displayed value is blank, the value is NaN, and if the user types in
|
|
// an gibberish value (like "hello world"), the value is undefined
|
|
// (i.e. get('value') returns undefined).
|
|
//
|
|
// Symmetrically, set('value', NaN) will clear the displayed value,
|
|
// whereas set('value', undefined) will have no effect.
|
|
value: NaN,
|
|
|
|
// editOptions: [protected] Object
|
|
// Properties to mix into constraints when the value is being edited.
|
|
// This is here because we edit the number in the format "12345", which is
|
|
// different than the display value (ex: "12,345")
|
|
editOptions: { pattern: '#.######' },
|
|
|
|
/*=====
|
|
_formatter: function(value, options){
|
|
// summary:
|
|
// _formatter() is called by format(). It's the base routine for formatting a number,
|
|
// as a string, for example converting 12345 into "12,345".
|
|
// value: Number
|
|
// The number to be converted into a string.
|
|
// options: number.__FormatOptions?
|
|
// Formatting options
|
|
// tags:
|
|
// protected extension
|
|
|
|
return "12345"; // String
|
|
},
|
|
=====*/
|
|
_formatter: number.format,
|
|
|
|
postMixInProperties: function(){
|
|
this.inherited(arguments);
|
|
this._set("type", "text"); // in case type="number" was specified which messes up parse/format
|
|
},
|
|
|
|
_setConstraintsAttr: function(/*Object*/ constraints){
|
|
var places = typeof constraints.places == "number"? constraints.places : 0;
|
|
if(places){ places++; } // decimal rounding errors take away another digit of precision
|
|
if(typeof constraints.max != "number"){
|
|
constraints.max = 9 * Math.pow(10, 15-places);
|
|
}
|
|
if(typeof constraints.min != "number"){
|
|
constraints.min = -9 * Math.pow(10, 15-places);
|
|
}
|
|
this.inherited(arguments, [ constraints ]);
|
|
if(this.focusNode && this.focusNode.value && !isNaN(this.value)){
|
|
this.set('value', this.value);
|
|
}
|
|
},
|
|
|
|
_onFocus: function(){
|
|
if(this.disabled){ return; }
|
|
var val = this.get('value');
|
|
if(typeof val == "number" && !isNaN(val)){
|
|
var formattedValue = this.format(val, this.constraints);
|
|
if(formattedValue !== undefined){
|
|
this.textbox.value = formattedValue;
|
|
}
|
|
}
|
|
this.inherited(arguments);
|
|
},
|
|
|
|
format: function(/*Number*/ value, /*number.__FormatOptions*/ constraints){
|
|
// summary:
|
|
// Formats the value as a Number, according to constraints.
|
|
// tags:
|
|
// protected
|
|
|
|
var formattedValue = String(value);
|
|
if(typeof value != "number"){ return formattedValue; }
|
|
if(isNaN(value)){ return ""; }
|
|
// check for exponential notation that dojo/number.format() chokes on
|
|
if(!("rangeCheck" in this && this.rangeCheck(value, constraints)) && constraints.exponent !== false && /\de[-+]?\d/i.test(formattedValue)){
|
|
return formattedValue;
|
|
}
|
|
if(this.editOptions && this.focused){
|
|
constraints = lang.mixin({}, constraints, this.editOptions);
|
|
}
|
|
return this._formatter(value, constraints);
|
|
},
|
|
|
|
/*=====
|
|
_parser: function(value, constraints){
|
|
// summary:
|
|
// Parses the string value as a Number, according to constraints.
|
|
// value: String
|
|
// String representing a number
|
|
// constraints: number.__ParseOptions
|
|
// Formatting options
|
|
// tags:
|
|
// protected
|
|
|
|
return 123.45; // Number
|
|
},
|
|
=====*/
|
|
_parser: number.parse,
|
|
|
|
parse: function(/*String*/ value, /*number.__FormatOptions*/ constraints){
|
|
// summary:
|
|
// Replaceable function to convert a formatted string to a number value
|
|
// tags:
|
|
// protected extension
|
|
|
|
var v = this._parser(value, lang.mixin({}, constraints, (this.editOptions && this.focused) ? this.editOptions : {}));
|
|
if(this.editOptions && this.focused && isNaN(v)){
|
|
v = this._parser(value, constraints); // parse w/o editOptions: not technically needed but is nice for the user
|
|
}
|
|
return v;
|
|
},
|
|
|
|
_getDisplayedValueAttr: function(){
|
|
var v = this.inherited(arguments);
|
|
return isNaN(v) ? this.textbox.value : v;
|
|
},
|
|
|
|
filter: function(/*Number*/ value){
|
|
// summary:
|
|
// This is called with both the display value (string), and the actual value (a number).
|
|
// When called with the actual value it does corrections so that '' etc. are represented as NaN.
|
|
// Otherwise it dispatches to the superclass's filter() method.
|
|
//
|
|
// See `dijit/form/TextBox.filter()` for more details.
|
|
return (value == null /* or undefined */ || value === '') ? NaN : this.inherited(arguments); // set('value', null||''||undefined) should fire onChange(NaN)
|
|
},
|
|
|
|
serialize: function(/*Number*/ value, /*Object?*/ options){
|
|
// summary:
|
|
// Convert value (a Number) into a canonical string (ie, how the number literal is written in javascript/java/C/etc.)
|
|
// tags:
|
|
// protected
|
|
return (typeof value != "number" || isNaN(value)) ? '' : this.inherited(arguments);
|
|
},
|
|
|
|
_setBlurValue: function(){
|
|
var val = lang.hitch(lang.mixin({}, this, { focused: true }), "get")('value'); // parse with editOptions
|
|
this._setValueAttr(val, true);
|
|
},
|
|
|
|
_setValueAttr: function(/*Number*/ value, /*Boolean?*/ priorityChange, /*String?*/ formattedValue){
|
|
// summary:
|
|
// Hook so set('value', ...) works.
|
|
if(value !== undefined && formattedValue === undefined){
|
|
formattedValue = String(value);
|
|
if(typeof value == "number"){
|
|
if(isNaN(value)){ formattedValue = '' }
|
|
// check for exponential notation that number.format chokes on
|
|
else if(("rangeCheck" in this && this.rangeCheck(value, this.constraints)) || this.constraints.exponent === false || !/\de[-+]?\d/i.test(formattedValue)){
|
|
formattedValue = undefined; // lets format compute a real string value
|
|
}
|
|
}else if(!value){ // 0 processed in if branch above, ''|null|undefined flows through here
|
|
formattedValue = '';
|
|
value = NaN;
|
|
}else{ // non-numeric values
|
|
value = undefined;
|
|
}
|
|
}
|
|
this.inherited(arguments, [value, priorityChange, formattedValue]);
|
|
},
|
|
|
|
_getValueAttr: function(){
|
|
// summary:
|
|
// Hook so get('value') works.
|
|
// Returns Number, NaN for '', or undefined for unparseable text
|
|
var v = this.inherited(arguments); // returns Number for all values accepted by parse() or NaN for all other displayed values
|
|
|
|
// If the displayed value of the textbox is gibberish (ex: "hello world"), this.inherited() above
|
|
// returns NaN; this if() branch converts the return value to undefined.
|
|
// Returning undefined prevents user text from being overwritten when doing _setValueAttr(_getValueAttr()).
|
|
// A blank displayed value is still returned as NaN.
|
|
if(isNaN(v) && this.textbox.value !== ''){
|
|
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?)
|
|
var n = Number(this.textbox.value);
|
|
return isNaN(n) ? undefined : n; // return exponential Number or undefined for random text (may not be possible to do with the above RegExp check)
|
|
}else{
|
|
return undefined; // gibberish
|
|
}
|
|
}else{
|
|
return v; // Number or NaN for ''
|
|
}
|
|
},
|
|
|
|
isValid: function(/*Boolean*/ isFocused){
|
|
// Overrides dijit/form/RangeBoundTextBox.isValid() to check that the editing-mode value is valid since
|
|
// it may not be formatted according to the regExp validation rules
|
|
if(!this.focused || this._isEmpty(this.textbox.value)){
|
|
return this.inherited(arguments);
|
|
}else{
|
|
var v = this.get('value');
|
|
if(!isNaN(v) && this.rangeCheck(v, this.constraints)){
|
|
if(this.constraints.exponent !== false && /\de[-+]?\d/i.test(this.textbox.value)){ // exponential, parse doesn't like it
|
|
return true; // valid exponential number in range
|
|
}else{
|
|
return this.inherited(arguments);
|
|
}
|
|
}else{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
var NumberTextBox = declare("dijit.form.NumberTextBox", [RangeBoundTextBox, NumberTextBoxMixin], {
|
|
// summary:
|
|
// A TextBox for entering numbers, with formatting and range checking
|
|
// description:
|
|
// NumberTextBox is a textbox for entering and displaying numbers, supporting
|
|
// the following main features:
|
|
//
|
|
// 1. Enforce minimum/maximum allowed values (as well as enforcing that the user types
|
|
// a number rather than a random string)
|
|
// 2. NLS support (altering roles of comma and dot as "thousands-separator" and "decimal-point"
|
|
// depending on locale).
|
|
// 3. Separate modes for editing the value and displaying it, specifically that
|
|
// the thousands separator character (typically comma) disappears when editing
|
|
// but reappears after the field is blurred.
|
|
// 4. Formatting and constraints regarding the number of places (digits after the decimal point)
|
|
// allowed on input, and number of places displayed when blurred (see `constraints` parameter).
|
|
|
|
baseClass: "dijitTextBox dijitNumberTextBox"
|
|
});
|
|
|
|
NumberTextBox.Mixin = NumberTextBoxMixin; // for monkey patching
|
|
|
|
/*=====
|
|
NumberTextBox.__Constraints = declare([RangeBoundTextBox.__Constraints, number.__FormatOptions, number.__ParseOptions], {
|
|
// summary:
|
|
// Specifies both the rules on valid/invalid values (minimum, maximum,
|
|
// number of required decimal places), and also formatting options for
|
|
// displaying the value when the field is not focused.
|
|
// example:
|
|
// Minimum/maximum:
|
|
// To specify a field between 0 and 120:
|
|
// | {min:0,max:120}
|
|
// To specify a field that must be an integer:
|
|
// | {fractional:false}
|
|
// To specify a field where 0 to 3 decimal places are allowed on input:
|
|
// | {places:'0,3'}
|
|
});
|
|
=====*/
|
|
|
|
return NumberTextBox;
|
|
});
|