8

In a Wicket app, I have a decimal number text field:

 TextField<BigDecimal> f = 
     new TextField<BigDecimal>("f", new PropertyModel<BigDecimal>(model, "share"));

I want it to always accept both . (dot) and , (comma) as decimal separator (regardless of browser's locale settings).

For showing the value, session's locale is used [which in our case is forced to be "fi" (-> comma)], but here I'm interested in what the field accepts as input.

My question is, do I have to change the field to TextField<String>, and convert to domain object's type (BigDecimal) manually? Or is there some way to use TextField<BigDecimal> (which allows e.g. making use of Wicket's MinimumValidator or RangeValidator), and still have it accept both decimal separators?

Jonik
  • 80,077
  • 70
  • 264
  • 372
  • 5
    Not having documentation with me right now, but you can register Converters globally in your application class. So you could register your own Converter implementation for BigDecimal and accept both variants in there – bert Jul 11 '11 at 09:36
  • @bert, thanks! Reading *Wicket in Action* (p 163), that might indeed be the best way to approach this... Consider writing that as an answer, too. – Jonik Jul 11 '11 at 09:42
  • 2
    Just a word of warning: in some locales, both characters can occur in the same number, i.e. `.` is used for grouping thousands and `,` as decimal separator. If you want a universal number parser, make sure your code can handle numbers like `32.519.100,28`. – biziclop Jul 11 '11 at 10:58
  • @biziclop: Yes, good point. (In our case we do not need a universal solution, so we can get away with something simpler like what I posted as an answer.) – Jonik Jul 11 '11 at 11:33

1 Answers1

15

Thanks to @bert's comment, and the Wicket in Action book, I found an approach that works. In the Application class specify a custom converter for BigDecimals:

@Override
protected IConverterLocator newConverterLocator() {
    ConverterLocator converterLocator = new ConverterLocator();
    converterLocator.set(BigDecimal.class, new CustomBigDecimalConverter());
    return converterLocator;
}

And in the custom converter, convertToObject needs to be overridden. NB: this is sufficient for our needs; think about your requirements and adapt as needed!

public class CustomBigDecimalConverter extends BigDecimalConverter {

    @Override
    public BigDecimal convertToObject(String value, Locale locale) {
        // NB: this isn't universal & your mileage problably varies!
        // (Specifically, this breaks if '.' is used as thousands separator)
        if ("fi".equals(locale.getLanguage())) {
            value = value.replace('.', ',');
        }
        return super.convertToObject(value, locale);
    }
}

Edit: Offtopic, but I want to document this too. We needed our app to support a scale of 4 decimal places, and our custom BigDecimal converter nicely solves that problem too.

  @Override
    public String convertToString(Object value, Locale locale) {
        NumberFormat fmt = getNumberFormat(locale);
        fmt.setMaximumFractionDigits(4); // By default this is 3.
        return fmt.format(value);
    }

After this customisation, a decimal number like 2.0005 will be shown as 2.0005 instead of 2.

Jonik
  • 80,077
  • 70
  • 264
  • 372