I'm currently working on a GWT project where we use the Sencha GXT libs. I'm currently trying to create a CurrencyField with a custom currency format. Some examples of how I want my custom currency format:
€ 123,45
€ 98.765.432,10
€ 400,00
So, as you can see I want a prefix euro-sign; a space between the currency symbol and the decimal; dots for thousand seperators; and commas for decimals (and of course two decimals behind the comma).
In GXT however it seems it isn't possible to create your own custom format. I know with the regular java.text.NumberFormat
I can do something like this:
NumberFormat format = NumberFormat.getCurrencyInstance();
DecimalFormatSymbols formatSymbols = new DecimalFormatSymbols();
formatSymbols.setCurrencySymbol("€");
formatSymbols.setGroupingSeparator('.');
formatSymbols.setMonetaryDecimalSeparator(',');
((DecimalFormat)format).setDecimalFormatSymbols(formatSymbols);
In GXT however, the com.google.gwt.i18n.client.NumberFormat
has to be used for the setFormat
-method of the NumberField<BigDecimal>
. I know some customization can be used with the CurrencyData
like this:
private CurrencyData createCurrencyData() {
// CurrencyData docs: http://www.gwtproject.org/javadoc/latest/com/google/gwt/i18n/client/CurrencyData.html
return new CurrencyData() {
@Override
public String getCurrencySymbol() {
return "€";
}
@Override
public String getSimpleCurrencySymbol() {
return "€";
}
@Override
public String getPortableCurrencySymbol() {
return "€";
}
@Override
public String getCurrencyCode() {
return "EUR"; // ISO4217 for this currency
}
@Override
public int getDefaultFractionDigits() {
return 2; // Amount of decimal positions
}
@Override
public boolean isSymbolPrefix() {
return true; // true to place currency symbol before the decimal
}
@Override
public boolean isSymbolPositionFixed() {
return true; // true to use the same currency symbol position regardless of locale (determined by the isSymbolPrefix-method)
}
@Override
public boolean isSpacingFixed() {
return true; // true to put a space between the currency symbol and the decimal
}
@Override
public boolean isSpaceForced() {
return true; // true to use the same spacing regardless of locale (determined by the isSpacingFixed-method)
}
@Override
public boolean isDeprecated() {
return false;
}
};
}
Which I now use like this:
NumberField<BigDecimal> currencyField = new NumberField<BigDecimal>(
new NumberPropertyEditor.BigDecimalPropertyEditor());
currencyField.setFormat(NumberFormat.getCurrencyFormat(createCurrencyData()));
Two problems however:
€123,456.78
; instead of € 123.456,78
(also, even though the CurrencyData#isSpacingForced
should apparently be used for a space between the currency-symbol and decimal, it doesn't work on the NumberField
..)400
, it gives an error instead of auto-formatting the user-input: 400 does not have either positive or negative affixes is not a valid number
.Changes we made:
We now placed the Dutch locale in our .gwt.xml
file:
<extend-property name="locale" values="nl_NL"/>
<set-property name="locale" value="nl_NL"/>
<set-property-fallback name="locale" value="nl_NL"/>
And we use a custom format like this:
final BigDecimalField currencyField = new BigDecimalField();
currencyField.setFormat(CustomNumberFormat.getCurrencyFormat());
With the CustomNumberFormat-class like this:
import java.util.Map;
import com.google.gwt.core.client.GWT;
import com.google.gwt.i18n.client.CurrencyData;
import com.google.gwt.i18n.client.CurrencyList;
import com.google.gwt.i18n.client.NumberFormat;
import com.google.gwt.i18n.client.constants.CurrencyCodeMapConstants;
public class CustomNumberFormat extends NumberFormat {
private static CurrencyCodeMapConstants currencyCodeMapConstants = GWT.create(CurrencyCodeMapConstants.class);
private static char currencySymbol;
private static NumberFormat cachedCurrencyFormat;
/**
* Get the default currency format
* @return the default currency format
*/
public static NumberFormat getCurrencyFormat() {
if (cachedCurrencyFormat == null) {
cachedCurrencyFormat = getCurrencyFormat(CurrencyList.get().getDefault().getCurrencyCode());
}
return cachedCurrencyFormat;
}
/**
* Get the currency format
* @param currencyCode the code to use
* @return the {@link NumberFormat} to use
*/
public static NumberFormat getCurrencyFormat(final String currencyCode) {
return new CustomNumberFormat(defaultNumberConstants.currencyPattern(), lookupCurrency(currencyCode), false, true);
}
/**
* Lookup the currency data
* @param currencyCode the currency code e.g. EUR
* @return the {@link CurrencyData}
*/
private static CurrencyData lookupCurrency(final String currencyCode) {
final CurrencyData currencyData = CurrencyList.get().lookup(currencyCode);
final Map<String, String> currencyMap = currencyCodeMapConstants.currencyMap();
final String code = currencyData.getCurrencyCode();
final String symbol = currencyMap.get(currencyCode);
final int fractionDigits = currencyData.getDefaultFractionDigits();
final String portableSymbol = currencyData.getPortableCurrencySymbol();
return toCurrencyData(code, symbol, fractionDigits, portableSymbol);
}
/**
*
* @param code the currency code e.g. EUR
* @param symbol the currency symbol e.g. the euro sign
* @param fractionDigits the number of fraction digits
* @param portableSymbol the portable symbol
* @return the {@link CurrencyData} to use
*/
public static native CurrencyData toCurrencyData(String code, String symbol, int fractionDigits,
String portableSymbol) /*-{
//CHECKSTYLE:OFF
return [ code, symbol, fractionDigits, portableSymbol ];
//CHECKSTYLE:ON
}-*/;
private boolean currencyFormat = false;
/**
*
* @param pattern the currency pattern
* @param cdata the {@link CurrencyData}
* @param userSuppliedPattern <code>true</code> if the pattern is supplied by the user
*/
protected CustomNumberFormat(final String pattern, final CurrencyData cdata, final boolean userSuppliedPattern,
final boolean currencyFormat) {
super(pattern, cdata, userSuppliedPattern);
this.currencyFormat = currencyFormat;
}
/* (non-Javadoc)
* @see com.google.gwt.i18n.client.NumberFormat#format(boolean, java.lang.StringBuilder, int)
*/
@Override
protected void format(final boolean isNegative, final StringBuilder digits, final int scale) {
super.format(isNegative, digits, scale);
if (this.currencyFormat) {
final char decimalSeparator = defaultNumberConstants.monetarySeparator().charAt(0);
if (digits.toString().endsWith(decimalSeparator + "00")) {
digits.delete(digits.length() - 3, digits.length());
}
if (isNegative) {
digits.delete(digits.length() - 1, digits.length()); // Delete leading "-"
digits.insert(0, "- "); // Insert "- " at the front
}
}
}
/**
* Parse a String. The String does not start with the expected prefix so we add it first
* @param text the text to parse
* @param inOutPos an offset telling us
* @return the parsed value
* @throws NumberFormatException if the text cannot be parsed
*/
@Override
public double parse(final String text, final int[] inOutPos) throws NumberFormatException {
//add the positive prefix (euro-sign plus space)
final String temp = getPositivePrefix() + text;
//parse the adjusted string
final double val = super.parse(temp, inOutPos);
//now here is the tricky bit... during parsing the inOutPos offset was updated based on the modified String
//but a check is maded to see if the resulting offset is equal to the length of the String we have been passed
//so we need to update inOutPos by removing the length of the positive prefix
inOutPos[0] -= getPositivePrefix().length();
return val;
}
}
Now we are getting the following results:
€ 123,45 when entering 123,45
€ 98.765.432,10 when entering 98765432,1
- € 400,00 when entering -400