TL;DR: Is there in Vaadin 8 something similar to Vaadin 7's converter to update the representation of an input field in the UI? I.e. removing all non-digits from the user-input immediately after the input-field lost focus, or converting a decimal to a currency?
Vaadin Forum Link : http://vaadin.com/forum#!/thread/15931682
We are currently in the process of migrating our Vaadin 7 project to vaadin 8.
In our Vaadin 7 application we had a few Converters. For example this CurrencyConverter:
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Locale;
import org.apache.commons.lang3.StringUtils;
import com.vaadin.v7.data.util.converter.StringToBigDecimalConverter;
import com.vaadin.v7.data.util.converter.Converter.ConversionException;
/**
* A converter that adds/removes the euro sign and
* formats currencies with two decimal places.
*/
public class EuroConverter extends StringToBigDecimalConverter {
@Override
public BigDecimal convertToModel(String value,
Class<? extends BigDecimal> targetType,
Locale locale) throws ConversionException {
if(StringUtils.isBlank(value)) {
return null;
}
value = value.replaceAll("[€\\s]", "").trim();
if(StringUtils.isBlank(value)) {
value = "0";
}
return super.convertToModel(value, targetType, locale);
}
@Override
public String convertToPresentation(BigDecimal value,
Class<? extends String> targetType,
Locale locale) throws ConversionException {
if(value == null) {
return null;
}
return "€ " + super.convertToPresentation(value, targetType, locale);
}
@Override
protected NumberFormat getFormat(Locale locale) {
// Always display currency with two decimals
NumberFormat format = super.getFormat(locale);
if(format instanceof DecimalFormat) {
((DecimalFormat)format).setMaximumFractionDigits(2);
((DecimalFormat)format).setMinimumFractionDigits(2);
}
return format;
}
}
When you load the page with this converter, and the initial value was null
, it would show nothing in the TextField. If the initial value that was previously saved was "1234"
, it would show € 1.234,00
in the TextField. Which is all good and well.
If you would change this initial value to "987"
, the binding with converter would save "987" in the object and immediately show € 987,00
in the TextField, as soon as you type in the TextField.
In our Vaadin 8 migration we've changed it to this:
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Locale;
import org.apache.commons.lang3.StringUtils;
import com.vaadin.data.Result;
import com.vaadin.data.ValueContext;
import com.vaadin.data.converter.StringToBigDecimalConverter;
/**
* A converter that adds/removes the euro sign and formats currencies with two
* decimal places.
*/
public class EuroConverter extends StringToBigDecimalConverter {
public EuroConverter() {
super("defaultErrorMessage");
}
public EuroConverter(String errorMessage) {
super(errorMessage);
}
@Override
public Result<BigDecimal> convertToModel(String value, ValueContext context) {
if(StringUtils.isBlank(value)){
return Result.ok(null);
}
value = value.replaceAll("[€\\s]", "").trim();
if(StringUtils.isBlank(value)){
value = "0";
}
return super.convertToModel(value, context);
}
@Override
public String convertToPresentation(BigDecimal value, ValueContext context) {
if(value == null){
return convertToPresentation(BigDecimal.ZERO, context);
}
return "€ " + super.convertToPresentation(value, context);
}
@Override
protected NumberFormat getFormat(Locale locale) {
// Always display currency with two decimals
NumberFormat format = super.getFormat(locale);
if(format instanceof DecimalFormat){
((DecimalFormat)format).setMaximumFractionDigits(2);
((DecimalFormat)format).setMinimumFractionDigits(2);
}
return format;
}
}
I would expect the same behavior as in Vaadin 7, but instead it behaves like this:
When you load the page with this converter, and the initial value was
null
, it would show nothing in the TextField. If the initial value that was previously saved was"1234"
, it would show€ 1.234,00
in the TextField. Which is all good and well.
If you would change this initial value to"987"
, the binding with converter would save "987" in the objectand immediately show, as soon as you type in the TextField.€ 987,00
in the TextField
It doesn't update the value in the TextField.. It will still display 987
. It almost seems as if Vaadin 8 converters are only used from user-input to domain-model through the binding, but not to change what has been put in.
Another change is that the converter is now put on the Binding itself in Vaadin 8, instead of the TextField in Vaadin 7.
Here is a gif with the behavior of Vaadin 7 / what I'm looking for in the top input field, and the current Vaadin 8 behavior in the bottom input field:
EDIT: Some things we've tried:
I create a Test Page with just a single TextField, single binding and single Converter:
TextField inputField = new TextField("Test");
Binder<PiggyBank> binder = new Binder<PiggyBank>();
// PiggyBank only has a property `amount` with getter & setter (see below)
PiggyBank piggyBank = new PiggyBank();
piggyBank.setAmount(BigDecimal.valueOf(1234))
binder.forField(inputField)
.withConverter(new EuroConverter())
.bind(PiggyBank::getAmount, PiggyBank::setAmount);
//1
binder.setBean(piggyBank);
//2
addComponent(inputField);
This will show € 1.234,00
initially, and when you type "987"
it will write 987
to the object (but will still show 987
instead of € 987,00
).
Some things to note:
piggyBank.setAmount(456);
at line 1, it will show € 456,00
in the UI's TextField initially.piggyBank.setAmount(456);
at line 2 instead of line 1, it will still show € 1.234,00
in the UI's TextField! So the converter's convertToPresentation
method isn't triggered at all anymore after the binder.setBean(piggyBank)
.piggyBank.setAmount(456);
at line 1, and remove binder.setBean(piggyBank);
it still shows € 1.234,00
instead of € 456,00
!We have been able to find an ugly work-around, which is as follows:
inputField.setValueChangeMode(ValueChangeMode.BLUR);
inputField.addBlurListener(new BlurListener() {
@Override
public void blur(BlurEvent event) {
if(binder.isValid()) {
binder.readBean(binder.getBean());
}
}
});
As you can see, we just read the bean every time the input field value changes. With this change the value is still correctly saved to the model, and is now also triggering the convertToPresentation
method of the converter after that, correctly displaying € 987,00
(although it still incorrectly displays € 1.234,00
instead of € 456,00
in the cases 2 and 3 of the numbered list above).
We've also tried inline lambda converters instead of an object; trying some things with binder.forMemberField
; trying some things with binder.bindInstanceFields
; trying to retrieve the set binders somehow, which isn't possible; etc. etc. We've tried all kind of things, but the Vaadin 8 Converter just doesn't work as it did in Vaadin 7, and we were unable to find an alternative or even proper work-around for this.
PiggyBank code on request in the comments:
public class PiggyBank {
private BigDecimal amount;
public BigDecimal getAmount() {
return amount;
}
public void setAmount(BigDecimal amount){
this.amount = amount;
}
}
This problem has been fixed in Vaadin 8.12.1. Ref. https://github.com/vaadin/framework/pull/12132
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With