Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Vaadin 8 Converter behaves different than Vaadin 7 Converter (doesn't update UI)?

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 object and immediately show € 987,00 in the TextField, as soon as you type 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:

enter image description here


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:

  1. If we add piggyBank.setAmount(456); at line 1, it will show € 456,00 in the UI's TextField initially.
  2. If we add 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).
  3. If we add 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;
    }
}
like image 220
Kevin Cruijssen Avatar asked Nov 07 '22 21:11

Kevin Cruijssen


1 Answers

This problem has been fixed in Vaadin 8.12.1. Ref. https://github.com/vaadin/framework/pull/12132

like image 110
Tatu Lund Avatar answered Nov 15 '22 05:11

Tatu Lund