PDA

View Full Version : Numberfield decimalPrecision (currency) - Maybe a bug



catapult
5 Mar 2010, 4:16 AM
I've been wondering for a while why it is that if I set the decimalPrecision of a numberfield to 2, as for currency, it drops the trailing zero. So, I decided to have a further look into the problem. This forum post set me off http://www.extjs.com/forum/showthread.php?t=20040

Looking at the source code for the numberfield I believe this is the offending code:


fixPrecision : function(value){
var nan = isNaN(value);
if(!this.allowDecimals || this.decimalPrecision == -1 || nan || !value){
return nan ? '' : value;
}
return parseFloat(parseFloat(value).toFixed(this.decimalPrecision));
},


As you can see that the fixPrecision function is parsing a value as a float and setting it to a decimal precision. It then parses that value to a float again (I don't understand why) which then chops off any trailing zero.

I did some basic testing of the parseFloat() function and this is what I found:


<script type="text/javascript">
var value=1234.333643;
var float=parseFloat(value).toFixed(2);
alert(float);
</script>
alerts "1234.33" Rounding down.




<script type="text/javascript">
var value=1234.306643;
var float=parseFloat(value).toFixed(2);
alert(float);
</script>

alerts "1234.31" Rounding up.




<script type="text/javascript">
var value=1234.303643;
var float=parseFloat(value).toFixed(2);
alert(float);
</script>

alerts "1234.30" Rounding down and keeping the trailing zero.


Now the problem:


<script type="text/javascript">
var value=1234.303643;
var float=parseFloat(parseFloat(value).toFixed(2));
alert(float);
</script>

alerts "1234.3" Rounding down and dropping the trailing zero.

My understanding is the parseFloat() does what is says and parses a value as a floating point value and that toFixed() rounds the floating point value to specified decimal precision. So why the extra parseFloat()?

I thought I would ask here if anyone can see a flaw with my theory before I start reporting this as a bug.

Looks like it could be an easy fix though.

Animal
5 Mar 2010, 4:50 AM
This is an FR for Field renderers.

http://www.extjs.com/forum/showthread.php?p=329298#post329298

It's not a bug. Setting an <input>'s value to 1.30 results in "1.3" appearing in it.

a toString function won't append the arbitrary number of trailing zeroes that you are currently thinking of!

catapult
8 Mar 2010, 8:07 AM
Thanks for that Animal.

I have actually created myself a plugin which does pretty much what I want. It's basically a numberfield with a few modifications. I am calling it into my application with my other plugins just after the base extjs files and generating currency fields when I need them.

I can set it to use any character as the thousand separator and the decimal precision works with trailing zeros.

I don't suppose it's the most elegant way of skinning the cat but it works for me.



Ext.namespace('Ext.ux');

/**
* Ext.ux.Currency Extension Class
*
* @author David Codona, aka Catapult
* @version 1.0
*
* @class Ext.ux.Currency
* @extends Ext.form.TextField
* @constructor
* @param {Object} config Configuration options
*/
/**
* Displays a currency value with a specified thousandSeparator and/or decimalSeparator.
*/

Ext.ux.Currency = function(config) {

// call parent constructor
Ext.ux.Currency.superclass.constructor.call(this, config);

}; // end of Ext.ux.Currency constructor

Ext.extend(Ext.ux.Currency, Ext.form.TextField, {
/**
* @cfg {String} thousandSeparator Character to allow as the decimal separator (defaults to ',')
*/
thousandSeparator : ',',
/**
* @cfg {String} fieldClass The default CSS class for the field (defaults to "x-form-field x-form-num-field")
*/
fieldClass: "x-form-field x-form-num-field",
/**
* @cfg {Boolean} allowDecimals False to disallow decimal values (defaults to true)
*/
allowDecimals : true,
/**
* @cfg {String} decimalSeparator Character(s) to allow as the decimal separator (defaults to '.')
*/
decimalSeparator : ".",
/**
* @cfg {Number} decimalPrecision The maximum precision to display after the decimal separator (defaults to 2)
*/
decimalPrecision : 2,
/**
* @cfg {Boolean} allowNegative False to prevent entering a negative sign (defaults to true)
*/
allowNegative : true,
/**
* @cfg {Number} minValue The minimum allowed value (defaults to Number.NEGATIVE_INFINITY)
*/
minValue : Number.NEGATIVE_INFINITY,
/**
* @cfg {Number} maxValue The maximum allowed value (defaults to Number.MAX_VALUE)
*/
maxValue : Number.MAX_VALUE,
/**
* @cfg {String} minText Error text to display if the minimum value validation fails (defaults to "The minimum value for this field is {minValue}")
*/
minText : "The minimum value for this field is {0}",
/**
* @cfg {String} maxText Error text to display if the maximum value validation fails (defaults to "The maximum value for this field is {maxValue}")
*/
maxText : "The maximum value for this field is {0}",
/**
* @cfg {String} nanText Error text to display if the value is not a valid number. For example, this can happen
* if a valid character like '.' or '-' is left in the field with no number (defaults to "{value} is not a valid number")
*/
nanText : "{0} is not a valid number",
/**
* @cfg {String} baseChars The base set of characters to evaluate as valid numbers (defaults to '0123456789').
*/
baseChars : "0123456789",

// private
initEvents : function(){
var allowed = this.baseChars + '';
allowed += this.thousandSeparator + '';
if (this.allowDecimals)
{
allowed += this.decimalSeparator;
}
if (this.allowNegative)
{
allowed += '-';
}
this.maskRe = new RegExp('[' + Ext.escapeRe(allowed) + ']');
Ext.ux.Currency.superclass.initEvents.call(this);
},
// private
validateValue : function(value){
var num = this.parseValue(value);
if(!Ext.ux.Currency.superclass.validateValue.call(this, value))
{
return false;
}
if(value.length < 1)
{
// if it's blank and textfield didn't flag it then it's valid
return true;
}
if(isNaN(num))
{
this.markInvalid(String.format(this.nanText, value));
return false;
}
if(num < this.minValue)
{
this.markInvalid(String.format(this.minText, this.minValue));
return false;
}
if(num > this.maxValue)
{
this.markInvalid(String.format(this.maxText, this.maxValue));
return false;
}
return true;
},
getValue : function(){
var value = String(Ext.ux.Currency.superclass.getValue.call(this)).replace(this.thousandSeparator, "");
return this.fixPrecision(this.parseValue(value));
},
setValue : function(v){
v = Ext.isNumber(v) ? v : parseFloat(String(v).replace(this.decimalSeparator, "."));
v = isNaN(v) ? '' : String(v).replace(".", this.decimalSeparator);
v = this.fixPrecision(v);
v = this.currencyConvert(v);
return Ext.ux.Currency.superclass.setValue.call(this, v);
},
/**
* Replaces any existing {@link #minValue} with the new value.
* @param {Number} value The minimum value
*/
setMinValue : function(value){
this.minValue = Ext.num(value, Number.NEGATIVE_INFINITY);
},
/**
* Replaces any existing {@link #maxValue} with the new value.
* @param {Number} value The maximum value
*/
setMaxValue : function(value)
{
this.maxValue = Ext.num(value, Number.MAX_VALUE);
},
// private
parseValue : function(value){
value = String(value).replace(this.decimalSeparator, ".");
value = String(value).replace(this.thousandSeparator, "");
value = parseFloat(value);
return isNaN(value) ? '' : value;
},
// private
fixPrecision : function(value){
var nan = isNaN(value);
if(!this.allowDecimals || this.decimalPrecision <= -1 || nan || !value)
{
return nan ? '' : value;
}
return parseFloat(value).toFixed(this.decimalPrecision);
},
beforeBlur : function(){
var v = this.getRawValue();
if(this.validateValue(v))
{
v = this.parseValue(v);
if(!Ext.isEmpty(v))
{
this.setValue(v);
}
}
},
// private
currencyConvert : function(v){
var delimiter = this.thousandSeparator;
var a = v.split('.',2)
if(this.decimalPrecision>0)
{
var d = a[1];
}
else
{
var d = "";
}
var i = parseInt(a[0]);
if(isNaN(i))
{
return '';
}
var minus = '';
if(i < 0)
{
minus = '-';
}
i = Math.abs(i);
var n = new String(i);
var a = [];
while(n.length > 3)
{
var nn = n.substr(n.length-3);
a.unshift(nn);
n = n.substr(0,n.length-3);
}
if(n.length > 0)
{
a.unshift(n);
}
n = a.join(delimiter);
if(d.length < 1)
{
amount = n;
}
else
{
amount = n + '.' + d;
}
amount = minus + amount;
return amount;
}
}); // end of extend
// end of file



Here's how I create the currency field:


new Ext.ux.Currency({
thousandSeparator : ',',
fieldLabel : 'Currency Field',
width : 100,
allowDecimals : true,
decimalPrecision : 2,
allowNegative : true,
//readOnly : true,
id : 'histexcessplother',
name : 'histexcessplother',
cls : 'currency'
});



I also have a css file which I put all my overrides in so I added:


/* Sets currency field text alignment to right */
.currency {
text-align: right;
}


I would be interested in any feed back as this is the first plugin I have created.

Animal
8 Mar 2010, 8:31 AM
That's not a plugin. It's a subclass of TextField.

It probably does what you need, but what if there are other types of input which need decimal formatting?

Maybe volumes, or some kind of scientific values. Maybe a field needs "?" putting after it or something like that?

This is why a plugin is more flexible. You have kind of created an island with your functionality all in a subclass of its own. No other classes can use it.

Check out http://www.extjs.com/blog/2009/11/11/advanced-plugin-development-with-ext-js/ for more about this.

It's up to you. it's just something for you to consider in the future so as not to "Paint yourself into a corner" with specialized subclasses.

catapult
8 Mar 2010, 8:40 AM
You're quite right it is a subclass. I have plugin on the brain as that's how I'm using it within my app.

I have a lot of currency fields within my app so this works fine for me. I will take on board what you have said for next time.

Thanks

darin123
13 Mar 2010, 5:43 AM
Setting decimalSeparator to ',' doesn't work for me.