PDA

View Full Version : [2.x] US Dollar Form Input Field



NOSLOW
16 May 2008, 8:03 AM
This extension seems to be working pretty well for with one exception: the change event is not firing and I can't for the life of me figure out why :-/. As a workaround, I'm using the blur event instead.

Here's what it does (for the most part :) ):
Formats number as US Currency with comma, 2 decimal places ($123,456.78)
Submits unformatted number value (123456.78)
Removes formatting during editing (works as numberfield does - extends numberfield)Caveats:
Requires monitorValid: false on field's parent formpanel.
Must manually initialize fields after a form load.
Newbie attempt at an extension (don't say I didn't warn you ;)).Known Bugs:
Change event doesn't fire (UPDATE: Fixed! Thanks Animal!)Live Demo: http://jsbin.com/ifamo



Ext.namespace('Ext.ux');

Ext.override(Ext.Component, {
/* override by Animal (http://extjs.com/forum/showthread.php?t=26484)
that allows us to find the dollar fields parent form. Very nice.
*/
findParentBy: function(fn) {
for (var p = this.ownerCt; (p != null) && !fn(p); p = p.ownerCt);
return p;
},

findParentByType: function(xtype) {
return typeof xtype == 'function' ?
this.findParentBy(function(p){
return p.constructor === xtype;
}) :
this.findParentBy(function(p){
return p.constructor.xtype === xtype;
});
}
});

Ext.util.Format.usMoneyNull = function(val) {
//-- allows clearing of field value (so that $0.00 only shows if you explicitly entered zero for a value).
if (val == null||val == '') {
return '';
} else if (val > 999999999999) {
return '';
} else {
return Ext.util.Format.usMoney(val);
}
}

Ext.ux.dollarField = function(config) {
//-- Add any numberfield default settings here:
var defaultConfig = {
allowDecimals: true,
allowNegative: false,
decimalPrecision: 2,
maxValue: 1000000000,
minValue: 0,
value: null,
selectOnFocus: true,
itemCls: 'rmoney' // to right-align dollar field, define style: .rmoney .x-form-field {text-align:right;}
};

Ext.ux.dollarField.superclass.constructor.call(this, Ext.apply(defaultConfig, config));

this.on('change',this._onChange,this);
this.on('initself',this._onInitSelf);
this.on('focus',this._onFocus);
this.on('blur',this._onBlur);
this.on('render',this._onRender);
//this.on('valid',this._onValid);
this.dollarNumericValue=config.value || null;
}

Ext.extend(Ext.ux.dollarField, Ext.form.NumberField, {
dollarNumericValue: null,

initSelf: function(){
// When form loads, bare number is loaded and displayed. Call this (via listener, typically)
// to format value to dollar.
if (this.value === null || this.value === '') {
this.dollarNumericValue = null;
} else {
this.dollarNumericValue = this.value;
}
this.setRawValue(this.formatter(this.dollarNumericValue));

// prevent field from reporting itself as "dirty" after form load (isDirty check):
this.originalValue = this.dollarNumericValue;
},

getValue:function(){
//return this.value+"".replace(/[^0-9.-]/g,"")-0; strip out any formatting characters from string.
if (this.value === '' || this.value === null) {
return null;
} else if (isNaN(this.value)) {
this.value = 0;
} else {
return Number(this.value);
}
},

_onChange:function(field, newVal, oldVal){
// n will always be unformatted numeric as STRING! So "-0" to force numeric type:
if (newVal === '') {
this.dollarNumericValue = null
} else {
this.dollarNumericValue = newVal-0;
}
},
_onRender:function(cmp){
this.setRawValue(this.formatter(this.dollarNumericValue));
if (this.isFormField) { // Is this check necessary?
var parentForm = this.findParentByType('form');

/*
Note: If client-side validation is enabled, unformatted numbers get posted on save. Good!

(The field's "isValid" method just does this for some reason, which works to our advantage.)

BUT (there's always a "but"), we then need to apply back the dollar formatting so that the
numbers aren't left displayed as plain.
*/

// Format dollar after successful form save:
parentForm.on('actioncomplete', function(){cmp.initSelf();});

// Format back to dollar after failed save attempt.
parentForm.on('actionfailed', function(){cmp.initSelf();});

// doLayout is called on initial page load.
parentForm.on('afterLayout', function(){cmp.initSelf();});

// Formats dollar after client-side validation fails...maybe...still investigating this.
// parentForm.on('beforeaction', function(){cmp.initSelf();});

/*
Depends on order of any success/actioncomplete/actionfailed listeners???
Or maybe you are checking if the form isValid yourself by listening to the
form's beforeaction, type action.type=='submit' and then returning false to
cancel the action. Still looking into all this.

More on clientValidation:
Before the form submits (doAction 'submit' with clientValidation not set to false...
from docs: "If undefined, pre-submission field validation is performed.") the call
to form "isValid" will turn the dollarfield back to a plain, unformatted number,
which will get posted.
*/

}
},

formatter: function(value){
if (value === 0) {
return Ext.util.Format.usMoneyNull("0"); // returns '$0.00' instead of ''.
} else {
return Ext.util.Format.usMoneyNull(value);
}
},

_onBlur: function(field){
/*
always update dollarNumericValue with the actual RawValue (which right here (onBlur) will
*always* be numeric (remember: when focused, it's unformatted numeric entry...just like
numberfield does. So onBlur, grab that numeric value and save it to this.dollarNumericValue,
then apply formatting back to RawValue for display.
*/

if (field.getRawValue() == '') {
this.dollarNumericValue=null;
} else {
this.dollarNumericValue=field.getRawValue()-0;
}

field.setRawValue(this.formatter(this.dollarNumericValue));

if (this.dollarNumericValue !== this.value) {
/* for some reason, when zero'ing out a value (or clearing), the onChange event is not firing
and this.value is not getting set to the new zero or blank value. This fixes that:
*/
this.value = this.dollarNumericValue;
}

},

_onFocus: function(field){
if (this.dollarNumericValue === null||this.dollarNumericValue === '') {
field.setRawValue('');
} else {
// remove formatting by restoring RawValue to dollarNumericValue.
field.setRawValue((this.dollarNumericValue-0).toFixed(this.decimalPrecision));
}
},

initAllSiblings: function() {
/*
Perhaps useful to call this manually when having trouble getting the event listeners straightened
out as described above
*/
if (this.isFormField) { // Is this check necessary?
var parentForm = this.findParentByType('form');

var dollarFields = parentForm.findByType('dollarfield');
Ext.each(dollarFields, function(dollarfield) {dollarfield.initSelf();});
}
}

/*
_onValid:function(field){
if (!field.hasFocus) {

//could do this here instead of onBlur, but form would end up posting the formatted dollar
//value instead of the numeric value.

this.setRawValue(this.formatter(this.dollarNumericValue));
}
},
*/
});

Ext.ComponentMgr.registerType('dollarfield', Ext.ux.dollarField);
test page:


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="../adapter/ext/ext-base.js"></script>
<script type="text/javascript" src="../ext-all-debug.js"></script>
<link rel="stylesheet" type="text/css" href="../resources/css/ext-all.css" />

<link rel="stylesheet" type="text/css" href="index.css" />
<script type="text/javascript" src="Ext.ux.numberfield.dollar.js"></script>
<style>
.rmoney .x-form-field {text-align:right;}
.lmoney .x-form-field {text-align:left;}
</style>
<script type="text/javascript">

Ext.onReady(function(){

Ext.QuickTips.init();

// turn on validation errors beside the field globally
Ext.form.Field.prototype.msgTarget = 'side';

var bd = Ext.getBody();

/*
* ================ Simple form =======================
*/
bd.createChild({tag: 'h2', html: 'Dollar Field Demo'});
bd.createChild({tag: 'p', html: 'Notice that unformatted dollar value gets submitted on save.'});

/* firebug: form = Ext.getCmp('dollar-form') */

var simple = new Ext.FormPanel({
id: 'dollar-form',
labelWidth: 75, // label settings here cascade unless overridden
url: 'dollarFieldDemo.html',
method: 'POST',
frame:true,
title: 'Simple Form',
bodyStyle:'padding:5px 5px 0',
width: 350,
defaults: {width: 230},
defaultType: 'textfield',

items: [{
fieldLabel: 'First Name',
name: 'first',
allowBlank:false
},{
id: 'dollar',
xtype: 'dollarfield',
fieldLabel: 'US Dollar val',
name: 'dollar',
value: 12345.67
},{
id: 'dollar2',
xtype: 'dollarfield',
fieldLabel: 'US Dollar val2',
name: 'dollar2',
value: 0
}
],

buttons: [{
text: 'Save',
handler: function() {Ext.getCmp('dollar-form').getForm().submit({waitMsg: 'Saving...', method: 'POST'});}
},{
text: 'Cancel'
}]
});

simple.render(document.body);

});

</script>
</head>

<body>
</body>
</html>

NOSLOW
17 May 2008, 7:51 AM
With the lack of responses, I'm beginning to believe that this extension is a bad idea.

Does anyone have any advice for me on what to do about formatting dollar fields on a form? Is there something already out there I should be using rather than attempting to roll my own?

jsakalos
19 May 2008, 4:37 AM
It's not bad idea at all! I'd like to have a field with configurable currency, decimal separator, thousands separator, etc.

Skimming over your code gives me a feeling that it is too complicated but maybe I'm wrong as I've never done this before...

Animal
19 May 2008, 4:58 AM
The "blur" event fires first.

You set oValue equal to the value. (I assume you mean old value)

But then your getValue returns oValue.

Then, in Field's blur handler it only fires "change" if the value from getValue is not equal to the startValue (You always have startValue available which is the old value). These are now the same, so it never fires.

Take out this line:



this.oValue=field.getRawValue();


And replace oValue with startValue

NOSLOW
19 May 2008, 8:57 AM
Thanks for the responses, guys. Before I dive back into this with Animal's suggestions, I noticed one thing right away:

According to the docs for the NumberField (http://extjs.com/deploy/dev/docs/?class=Ext.form.NumberField), the change event "Fires just before the field blurs if the field value has changed."

Is that a mistake in the documentation? [EDIT] Just did a test and confirmed that change event does fire before the blur event for the numberfield.

Now let me see if I can figure this problem out. Stay tuned...

NOSLOW
19 May 2008, 12:13 PM
Code is fixed and updated for improved readability.

Animal, you steered me in the right direction. I renamed the important variable to be me more descriptive, which helped me in fixing the code:

"oValue" is now "dollarNumericValue".

Which means that it should *always* be of type numeric, and never a string (and null values allowed).

I wasn't aware of the startValue...good to know. The problem was in the the GetValue and onBlur event handlers, as you described, although your suggested line of code wasn't the problem. I made getValue() return this.value-0; which forces a numeric return value (as apposed to a string representation of a numeric value;)), and the line of code you told me to remove stays, but also forces numeric value:



this.dollarNumericValue=field.getRawValue()-0;


Thanks again!

Animal
19 May 2008, 11:48 PM
Well done, glad you got it fixed.

Using the expression



return Number(this.value);


coerces the value into a numeric.

But either way, if this.value managed to be a string that was unparseable as a number, it would return the NaN value, so if that is ever possible, it would be something to check for.

NOSLOW
20 May 2008, 3:38 AM
I was wondering if it could ever be NaN. That's a good idea to add that check just to be safe.

NOSLOW
21 May 2008, 4:38 AM
So I hadn't really tested the null value handling until today, and it needed more work. The code above has been updated to fix the problems it had with not handling null values properly.

Note that null values are especially important here because it distinguishes between a zero value and no value. An interesting thing I wasn't really paying attention to was that if you didn't initialize the numberfield with a null value, it defaults to be undefined -- a value I hadn't been checking for. The easy fix to that was to add value: null in the default config.B)

krause
25 Jun 2008, 3:06 PM
Is there a place I can find this and other great extensions other than hunting for them in the forums and finding out which of the posts contains the latest version?

krause
25 Jun 2008, 4:10 PM
I tried this extension and while testing I came across a problem. If you are a quick typer and click tab to go to the next field in the form very quickly just after entering an amount, the formatting fails. You can actually see how it gets formatted and an instant later it reverts back to the original entered format. It is as if the ochange/onblur events where occuring in reverse order. If you wait a second before hitting tab, it works great.

spaque99
26 Jun 2008, 9:20 AM
It seems to be because it does a validation on blur and also a validation on keyup. I tried setting validationEvent:false and it resolved the issue.

Hope this helps...

Seb

krause
26 Jun 2008, 10:01 AM
It works like a charm, thank you very much. I just hope this doesn't break other functionality.
I added this property to the defaultConfig of the dollarField like so:


var defaultConfig = {
allowDecimals: true,
allowNegative: false,
decimalPrecision: 2,
maxValue: 1000000000,
minValue: 0,
value: null,
selectOnFocus: true,
validationEvent: false,
itemCls: 'rmoney' // to right-align dollar field, define style: .rmoney .x-form-field {text-align:right;}
};

krause
26 Jun 2008, 10:15 AM
I found an anomaly in the code.
You have the following line:
this.on('initself',this._onInitSelf);
but there is no such method on the object. The method is named initSelf (without the leading uderscore). Also note that the 's' on self is lowercase.
The component seems to work fine though since the initSelf method is being called from the _onRender method.
I think we can just get rid of that line.

krause
26 Jun 2008, 2:35 PM
It seems to be because it does a validation on blur and also a validation on keyup. I tried setting validationEvent:false and it resolved the issue.

Hope this helps...

Seb


I already found the limitation: you loose the nice as-you-type validation (i.e. you type two decimal points, or a missplaced minus).
I found away around this without disabling this behaviour.
I overrided the Ext.form.Field.validate() method to verify if the field has focus. If it does, it simply calls the superclass validate() method, else it just returns true. Since onblur already validates the field, there is no need for further validations after that even if there are some pending validations triggered by the keyups.
Following is the method I added to the dollarField objet:


validate : function(){
if (this.hasFocus) {
// If we loose focus, we don't validate, onBlur already validates.
// If we allow this call after blur we loose the formatting.
return Ext.ux.dollarField.superclass.validate.call(this);
}
return true;
},I also modified the _onBlur and _onFocus methods to set the this.hasFocus value (is there already a way of knowing this?).

I wonder if this validate behaviour should be the default behaviour of the Ext.form.Field object.

NOSLOW
1 Jul 2008, 11:36 AM
After numerous emails back and forth on this issue with Rich and Brian from the core team, they were able to help me get it to the point that it works without problems in my application.

Here's the version that I'm using now, and there seems to be no problems:



Ext.namespace('Ext.ux');

Ext.override(Ext.Component, {
/* override by Animal (http://extjs.com/forum/showthread.php?t=26484)
that allows us to find the dollar fields parent form. Very nice.
*/
findParentBy: function(fn) {
for (var p = this.ownerCt; (p !== null) && !fn(p); p = p.ownerCt);
return p;
},

findParentByType: function(xtype) {
return typeof xtype == 'function' ?
this.findParentBy(function(p) {
return p.constructor === xtype;
}) :
this.findParentBy(function(p) {
return p.constructor.xtype === xtype;
});
}
});

Ext.util.Format.usMoneyNull = function(v, cents) {
// -- allows clearing of field value (so that $0.00 only shows if you
// explicitly entered zero for a value).
if (v === null || v === '') {
return '';
} else if (v > 999999999999) {
return '';
} else {
// Modified version from Ext.util.Format.usMoney checks the cents
// parameter to determine whether or not to display decimal places
v = Math.round((v - 0) * 100) / 100;
v = (v == Math.floor(v)) ? v + ".00" : ((v * 10 == Math.floor(v * 10)) ? v + "0" : v);
v = String(v);
var ps = v.split('.');
var whole = ps[0];
var sub = ps[1] ? '.' + ps[1] : '.00';
var r = /(\d+)(\d{3})/;
while (r.test(whole)) {
whole = whole.replace(r, '$1' + ',' + '$2');
}
v = (cents) ? whole + sub : whole;
if (v.charAt(0) == '-') {
return '-$' + v.substr(1);
}
return "$" + v;
}
};


/* NumberField Implementation */
Ext.ux.dollarField = function(config) {
//-- Add any numberfield default settings here:
var defaultConfig = {
allowDecimals: true,
allowNegative: false,
decimalPrecision: 2,
maxValue: 1000000000,
minValue: 0,
selectOnFocus: true,
value: null,
itemCls: 'rmoney' // to right-align dollar field, define style: .rmoney .x-form-field {text-align:right;}
};

Ext.ux.dollarField.superclass.constructor.call(this, Ext.apply(defaultConfig, config));

this.on('change',this._onChange,this);
this.on('focus',this._onFocus);
this.on('blur',this._onBlur);
this.on('render',this._onRender);
this.dollarNumericValue=config.value || null;
};

Ext.extend(Ext.ux.dollarField, Ext.form.NumberField, {
dollarNumericValue: null,

initSelf: function() {
// When form loads, bare number is loaded and displayed. Call this (via listener, typically)
// to format value to dollar.
if (this.value === null || this.value === '') {
this.dollarNumericValue = null;
} else {
this.dollarNumericValue = this.value;
}
this.setRawValue(this.formatter(this.dollarNumericValue));

// prevent field from reporting itself as "dirty" after form load (isDirty check):
this.originalValue = this.dollarNumericValue;
},

getValue:function() {
//return this.value+"".replace(/[^0-9.-]/g,"")-0; strip out any formatting characters from string.
if (this.value === '' || this.value === null) {
return null;
} else if (isNaN(this.value)) {
this.value = 0;
} else {
return Number(this.value);
}
},

_onChange:function(field, newVal, oldVal) {
// n will always be unformatted numeric as STRING! So "-0" to force numeric type:
if (newVal === '') {
this.dollarNumericValue = null;
} else {
this.dollarNumericValue = newVal-0;
}
},

_onBeforeAction: function(form, action) {
this.setRawValue(this.getValue());
},

_onRender:function(cmp) {
this.setRawValue(this.formatter(this.dollarNumericValue));
if (this.isFormField) { // Is this check necessary?
var parentForm = this.findParentByType('form');

/*
Note: If client-side validation is enabled, unformatted numbers get posted on save. Good!

(The field's "isValid" method just does this for some reason, which works to our advantage.)

BUT (there's always a "but"), we then need to apply back the dollar formatting so that the
numbers aren't left displayed as plain.
*/

// Format dollar after successful form save:
parentForm.on('actioncomplete', function() {cmp.initSelf();});

// Format back to dollar after failed save attempt.
parentForm.on('actionfailed', function() {cmp.initSelf();});

// doLayout is called on initial page load.
parentForm.on('afterLayout', function() {cmp.initSelf();});

parentForm.on('beforeaction', this._onBeforeAction, this);

// Formats dollar after client-side validation fails...maybe...still investigating this.
// parentForm.on('beforeaction', function() {cmp.initSelf();});

/*
Depends on order of any success/actioncomplete/actionfailed listeners???
Or maybe you are checking if the form isValid yourself by listening to the
form's beforeaction, type action.type=='submit' and then returning false to
cancel the action. Still looking into all this.

More on clientValidation:
Before the form submits (doAction 'submit' with clientValidation not set to false...
from docs: "If undefined, pre-submission field validation is performed.") the call
to form "isValid" will turn the dollarfield back to a plain, unformatted number,
which will get posted.
*/

}
},

formatter: function(value) {
var showCents = (this.decimalPrecision !== 0);

// usMoneyNull formatter *always* includes 2 decimal places at the end (".00"). If decimalPrecision is set to 0, lob off the decimals.

if (value === 0) {
return Ext.util.Format.usMoneyNull("0", showCents); // returns '$0.00/$0' instead of ''.
} else {
return Ext.util.Format.usMoneyNull(value, showCents);
}
},

_onBlur: function(field) {
/*
always update dollarNumericValue with the actual RawValue (which right here (onBlur) will
*always* be numeric (remember: when focused, it's unformatted numeric entry...just like
numberfield does. So onBlur, grab that numeric value and save it to this.dollarNumericValue,
then apply formatting back to RawValue for display.
*/

if (field.getRawValue().substring(0,1) != '$') {
if (field.getRawValue() === '') {
this.dollarNumericValue=null;
} else {
this.dollarNumericValue=field.getRawValue()-0;
}

field.setRawValue(this.formatter(this.dollarNumericValue));

if (this.dollarNumericValue !== this.value) {
/* for some reason, when zero'ing out a value (or clearing), the onChange event is not firing
and this.value is not getting set to the new zero or blank value. This fixes that:
*/
this.value = this.dollarNumericValue;
}

}
},

_onFocus: function(field) {
if (this.dollarNumericValue === null||this.dollarNumericValue === '') {
field.setRawValue('');
} else {
// remove formatting by restoring RawValue to dollarNumericValue.
field.setRawValue((this.dollarNumericValue-0).toFixed(this.decimalPrecision));
}
},

processValue: function(value) {
return value;
},

validateValue : function(value) {
if (!Ext.form.NumberField.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;
}
value = Number(this.value);
if (isNaN(value)) {
this.markInvalid(String.format(this.nanText, value));
return false;
}
var num = this.parseValue(value);
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;
},

initAllSiblings: function() {
/*
Sometimes need to call this manually after complex form loads
*/
if (this.isFormField) { // Is this check necessary?
var parentForm = this.findParentByType('form');

var dollarFields = parentForm.findByType('dollarfield');
Ext.each(dollarFields, function(dollarfield) {dollarfield.initSelf();});
}
},

clearDirty: function() {
/* needed for a calculated display-only fields whose submitted value is ignored */
this.originalValue = this.getRawValue();
}

});

Ext.reg('dollarfield', Ext.ux.dollarField);

NOSLOW
29 Jul 2008, 9:48 AM
Discovered that you could not zero out or blank out (set to null) a dollar value that was previously set. For some reason, the onChange event was not firing under these 2 scenarios (FF3 and IE7), but the onBlur was. The hackish fix was to add another check in the _onBlur handler to account for this.

Fixed in previous post.

d1rty
30 Jul 2008, 8:51 AM
I'm using the code from your post dated 07-01-2008 (with Ext 2.1) and i'm getting the following error thrown in findParentByType

p is undefined
return p.constructor.xtype === xtype;

NOSLOW
6 Aug 2008, 7:15 AM
The code in the post from 7-01-2008 was updated on 7-30-2008. Are you using that? I'm not having any problems with it.

leolima
22 Aug 2008, 10:06 AM
Hello, I

sethladd
9 Oct 2008, 5:40 AM
Thanks for sharing this new field. I am using Ext 2.2 and I am also getting this error:

p is undefined
return p.constructor.xtype === xtype;

If it helps, I have a form panel inside a tabpanel.

charleshimmer
9 Oct 2008, 8:33 AM
Great addition! Thanks for posting this. Does anybody know how to modify it so it formats as the user enters the number?

NOSLOW
10 Oct 2008, 8:46 AM
@sethladd: If you can post an example at jsbin.com (use this as an example: http://jsbin.com/ifamo) showing your error, I can take a look.

@charleshimmer: I wouldn't even know where to start on that one.

rtconner
15 Oct 2008, 3:00 PM
Works great, thanks for building this.

Just curious... why did you break Ext class name convention and use dollarField, instead of DollarField?

NOSLOW
16 Oct 2008, 5:49 AM
rtconner: I have no reason for breaking the Ext class naming convention other than it had not occurred to me that there was a convention that I should be following.

crxtech
21 Oct 2008, 8:06 AM
If you add allowBlank:false, it marks the field as invalid once rendered.


xtype: 'dollarfield',
fieldLabel: 'Pre-Authorized Amount',
labelStyle: 'font-weight:bold; width:160;',
id: 'AuthorizedAmount',
name: 'AuthorizedAmount',
maxLength: 15,
itemCls: 'height:75;',
allowBlank:false

juljupy
5 Nov 2008, 12:36 PM
Hi, thanks for your contribution...

It works perfect....

but i wanna make a contribution, if anyone needs it.

When i tried to use myfor.getForm().loadRecord(rec) function i got that when i focus dollar field to change the value, the information that it's contained in it got erased, so i made a modification in this part of the file code:


_onFocus: function(field) {
if (this.dollarNumericValue === null||this.dollarNumericValue === '') {
if(field.getValue()==''){
field.setRawValue('');
}
} else {
// remove formatting by restoring RawValue to dollarNumericValue.
field.setRawValue((this.dollarNumericValue-0).toFixed(this.decimalPrecision));
}
},
},

the code in red was the change i made... hope it helps someone

cdhutch
4 Dec 2008, 4:28 PM
With a tab panel, the findParent doesn't find the form. My work-around was to provide the parent form id as a param to the field constructor thus:



parentId: '',

findParentForm: function() {
return Ext.getCmp(this.parentId);
},

krause
7 Jan 2009, 10:32 AM
I tried latest version using the code in post for 07-01-2008 that was updated on 07-29-2008 (http://www.extjs.com/forum/showthread.php?p=189367#post189367) and it seems to resolve all previous issues reported.

I still have two minor issues:

1. There is no way to clear a field. If you use setValue('') it will clear the value, but when you focus on the field you will see the old value.

2. There is no way to set a value programmatically. If you use setValue(aValue) it will not get formatted, and when you focus the field you will get the old value.

3. (I almost forgot...) As crxtech tech reported (http://www.extjs.com/forum/showthread.php?p=241324#post241324) when first rendered, a required field is marked as invalid (which it shouldn't).

I added the following methods to the dollarField which are working fine for me:



setValue : function(v) {
Ext.ux.dollarField.superclass.setValue.call(this, v);
this._onBlur(this);
},

/**
* Clears field value without validating, i.e. if the field is required
* the field will not show an error at this point.
*/
clearValue : function() {
this.clearInvalid();
this.setRawValue('');
this.dollarNumericValue = null;
},



I you use setValue, now the value will get set and displayed properly (I reused the _onBlur method since it already does this... which is a bit dirty). If you setValue() with null or empty, the validation will kick in and mark the field as invalid.

If you use clearValue, the field will be cleared but will not be marked as invalid even if it is a required field.

There is still a strange behavior though: if the field is required and has an initial value and you call setValue(''), the field is cleared but the required field validation does not kick in... if you change the value manually and call setValue('') again the validation works ok.

As for point 3, I added a quick fix/hack which should definitely be addressed further, but at least seems to be working: In the validateValue method I inserted in the very top the following:



validateValue : function(value) {
if (this.uglyFix === undefined && value.length < 1) {
// This horrible fix is to avoid validation error when a
// required field (i.e. allowBlank: false) is rendered
// for the first time and its initial value is empty.
this.uglyFix = '';
return true;
}
if (!Ext.form.NumberField.superclass.validateValue.call(this, value)) {
...



Any feedback would be welcome.

krause
23 Jan 2009, 11:00 AM
I found another problem. When you enter a 0 or blank and you try to access the field's value in the onBlur event listener you get the previous value.

My solution: to override NumericField's beforeBlur method inside the dollarField code:



// private override
beforeBlur : function(){
var v = this.parseValue(this.getRawValue());
this.setValue(this.fixPrecision(v));
},


The original code in NumberField has if(v) which evaluates to false when v is 0 and thus the setValue method is not invoked.

As a result of this fix, the following code inside dollarField's _onBlur method is no longer neccessary:



if (this.dollarNumericValue !== this.value) {
/* for some reason, when zero'ing out a value (or clearing), the onChange event is not firing
and this.value is not getting set to the new zero or blank value. This fixes that:
*/
this.value = this.dollarNumericValue;
}


I also posted this as a bug in NumberField (NumberField coding error in Ext 2.2 (http://extjs.com/forum/showthread.php?p=277863#post277863) ).

Regards

claude_r_gauthier@hotmail
10 Jul 2009, 9:58 AM
I'm using Ext JS in a very dynamic way where I build Ext components and layout at runtime based on an XHR call, all of this is client-side processing. After the entire panel(s), form(s) and all layout items and objects are created, I render to the screen in a div tag.

When I tried using the dollarfield component, I would end up with a form where the unformatted value would appear.

I tried both set of codes in this thread, neither worked.

So, based on the first code in this thread, I altered it to suit my needs.

I'm not a purist, I'm not saying this is the BEST solution, but I'm in a hurry, and I'm very practical, so what I ended doing is basically modified the extension in such a way that I'm using a textField instead of a numberField, and to ensure the input is restricted, I created a numberFormat vtype.

I also modified a line and added a line in the initSelf: function.

So, this is working well for me.

I'm sharing the code for those who may have a use for it.

For those wishing to quickly identify the differences between my code and the original one posted, look for // CG:

Based on the changes I've done

Caveats:
Requires monitorValid: false on field's parent formpanel. (NOT REQUIRED) - no issues
Must manually initialize fields after a form load. (NOT REQUIRED) - fixed


applyExtExtensions: function () {

// this function is where I will create the currencyfield.. (i renamed it from dollarfield)
createCurrencyField();
// simple number format
var numberTest = /([0-9.,])/;
Ext.apply(Ext.form.VTypes, {
numberFormat: function (v) {
return numberTest.test(v);
},
numberFormatText: 'Invalid Numeric Format',
numberFormatMask: /([0-9.,])/
});

},

// the extended currencyfield function based on textfield

createCurrencyField: function () {

Ext.namespace('Ext.ux');
Ext.override(Ext.Component, {
findParentBy: function (fn) {
for (var p = this.ownerCt;
(p != null) && !fn(p); p = p.ownerCt);
return p;
},

findParentByType: function (xtype) {
returntypeof xtype == 'function' ? this.findParentBy(function (p) {
return p.constructor === xtype;
}) : this.findParentBy(function (p) {
return p.constructor.xtype === xtype;
});
}
});

Ext.util.Format.usMoneyNull = function (val) {
if (val == null || val == '') {
return '';
}
elseif(val > 999999999999) {
return '';
} else {
return Ext.util.Format.usMoney(val);
}
}

Ext.ux.currencyField = function (config) {

var defaultConfig = {
allowDecimals: true,
allowNegative: false,
decimalPrecision: 2,
maxValue: 1000000000,
minValue: 0,
value: null,
selectOnFocus: true,
itemCls: 'rmoney' // to right-align dollar field, define style: .rmoney .x-form-field {text-align:right;}
};

Ext.ux.currencyField.superclass.constructor.call(this, Ext.apply(defaultConfig, config));

this.on('change', this._onChange, this);
this.on('initself', this._onInitSelf);
this.on('focus', this._onFocus);
this.on('blur', this._onBlur);
this.on('render', this._onRender);
this.dollarNumericValue = config.value || null;
}

Ext.extend(Ext.ux.currencyField, Ext.form.TextField, {
dollarNumericValue: null,

initSelf: function () {
if (this.value === null || this.value === '') {
this.dollarNumericValue = null;
} else {
this.dollarNumericValue = this.value;
}
var val = this.formatter(this.dollarNumericValue);
this.setRawValue(this.dollarNumericValue); // CG: changed this
this.setValue(val); // CG: added this
this.originalValue = this.dollarNumericValue;
},

getValue: function () {
if (this.value === '' || this.value === null) {
returnnull;
}
elseif(isNaN(this.value)) {
this.value = 0;
} else {
return Number(this.value);
}
},

_onChange: function (field, newVal, oldVal) {
if (newVal === '') {
this.dollarNumericValue = null
}

else {

this.dollarNumericValue = newVal - 0;
}
},
_onRender: function (cmp) {
this.setRawValue(this.formatter(this.dollarNumericValue));
cmp.initSelf();
if (this.isFormField) { // Is this check necessary?
var parentForm = this.findParentByType('form');
parentForm.on('actioncomplete', function () {
cmp.initSelf();
});
parentForm.on('actionfailed', function () {
cmp.initSelf();
});
parentForm.on('afterLayout', function () {
cmp.initSelf();
});
}
},

formatter: function (value) {
if (value === 0) {
return Ext.util.Format.usMoneyNull("0"); // returns '$0.00' instead of ''.
}

else {

return Ext.util.Format.usMoneyNull(value);
}
},

_onBlur: function (field) {

if (field.getRawValue() == '') {
this.dollarNumericValue = null;
} else {
this.dollarNumericValue = field.getRawValue() - 0;
}

field.setRawValue(this.formatter(this.dollarNumericValue));

if (this.dollarNumericValue !== this.value) {
this.value = this.dollarNumericValue;
}

},

_onFocus: function (field) {
if (this.dollarNumericValue === null || this.dollarNumericValue === '') {
field.setRawValue('');
} else {
field.setRawValue((this.dollarNumericValue - 0).toFixed(this.decimalPrecision));
}
},

initAllSiblings: function () {

if (this.isFormField) {
var parentForm = this.findParentByType('form');

var currencyFields = parentForm.findByType('currencyField');
Ext.each(currencyFields, function (currencyField) {
currencyField.initSelf();
});
}
}
});

Ext.ComponentMgr.registerType('currencyfield', Ext.ux.currencyField);
}

// the implemented widget

currencyfield: function (x, fieldProfile, fieldData) {

var fieldId = fieldProfile.field;
var textObj = new Ext.ux.currencyField({
id: fieldId,
name: fieldId,
value: fieldData.value,
hidden: fieldData.hidden,
disabled: ct.formBuilder2.setFieldToDisabledOnLoad(fieldData),
hideMode: 'display',
ctCls: 'backgroundFieldColor',
vtype: 'numberFormat'

});
ct.formBuilder2.createListeners(fieldData.behaviors, "field", textObj);
return textObj;

},

tryanDLS
10 Jul 2009, 11:29 AM
http://extjs.com/learn/Ext_Forum_Help#How_to_post_code_properly

ExtMore
7 Sep 2009, 11:06 PM
Hi,

Very nice! I need a currency field too, and I suspect that this is a very common requirement. To me a currency field is a natural candidate to be part of ExtJS. :)

Anyways, I will try this latest evolution by Claude.

BTW.. I was skimming the code and noticed that getValue() doesn't return anything in case this.value is NaN. Does this cause any problems?



getValue: function () {
if (this.value === '' || this.value === null) {
returnnull;
}
else if(isNaN(this.value)) {
this.value = 0;
} else {
return Number(this.value);
}
},

NOSLOW
8 Sep 2009, 2:33 AM
Hi ExtMore,

The check for isNaN is for robustness because in theory, it should never be true. Since I don't usually write perfect code, I always check for isNaN before I attempt to use the Number() function on a value, whether it's expected or not.

JSCoder
8 Sep 2009, 10:31 PM
This component does not support negative dollar values? Why do I get $NaN when I enter -12.00?

redcs
25 Sep 2009, 12:45 AM
The code in the post from 7-01-2008 was updated on 7-30-2008. Are you using that? I'm not having any problems with it.

i'm sorry, but i didnt find update on 7-30-2008. :-/

MFX
7 Jun 2010, 12:28 PM
I have successfully implemented the extension using the code date 1July 2008. On screen it seems to work as expected but on page submit all of the dollar fields fail with a "not a valid number" error. I have set "monitorValid: false" but it is still failing. Can anyone tell me what I am doing wrong?

NOSLOW
7 Jun 2010, 1:56 PM
I suspect this change in ExtJS v3.2 messes with this extension because it's designed to handle null values: http://www.extjs.com/forum/showthread.php?96736-CLOSED-3.2-Change-in-the-way-NULLs-are-handled-for-numeric-field-types&p=457041#post457041

This extension just barely works for my v3.1.0-based project. I'm not surprised that it doesn't work for other people. In hind sight, I wish I never attempted this extension because it has only caused me grief. My advice: stay clear of this.

irina
3 Aug 2010, 9:48 AM
1. A value is not set when this field is rendered and the value starts with "$". I made changes in setValue() to make it work (changes are in red).

setValue : function(v) {
if(v !== null && v[0] == '$'){
v = v.substring(1,v.length);
}
Ext.ux.dollarField.superclass.setValue.call(this, v);
this._onBlur(this);
}2. The field is invalid when the original value started with "$" is modified. Two red lines should be added to validateValue() method:

validateValue : function(value) {
if(value[0] == '$'){
value = value.substring(1,value.length);
}
if (!Ext.form.NumberField.superclass.validateValue.call(this, value)) {
return false;
....
}

moegal
25 Jan 2011, 2:04 PM
I added the following to validateValue to handle numbers larger then 999



if(value[0] == '$'){
value = value.substring(1,value.length);
}
if (value.indexOf(',') != -1) {
value = value.replace(/,/gi, '');
}

brittongr
6 Mar 2011, 7:06 AM
I just added this extension: http://www.sencha.com/forum/showthread.php?125937-Number-field-with-currency-symbol-thousand-separator-with-international-support&p=577701
(http://www.sencha.com/forum/showthread.php?125937-Number-field-with-currency-symbol-thousand-separator-with-international-support&p=577701)
Maybe it can helps others...