TL;DR:Vaadin 8 中是否有类似于 Vaadin 7 的转换器来更新 UI 中输入字段的表示?即在输入字段失去焦点后立即从用户输入中删除所有非数字,或者将小数转换为货币?
Vaadin 论坛链接: http://vaadin.com/forum#!/thread/ 15931682
我们目前正在将 Vaadin 7 项目迁移到 vaadin 8。
在我们的 Vaadin 7 应用程序中,我们有一些转换器。例如这个 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;
}
}
当您使用此转换器加载页面时,初始值为null
,它不会在 TextField 中显示任何内容。如果之前保存的初始值为"1234"
,它将显示€ 1.234,00
在 TextField 中。这一切都很好。
如果您将此初始值更改为"987"
,则与转换器的绑定将在对象中保存“987”,并€ 987,00
在您键入 TextField 后立即显示在 TextField 中。
在我们的 Vaadin 8 迁移中,我们将其更改为:
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;
}
}
我希望与 Vaadin 7 中的行为相同,但它的行为如下:
当您使用此转换器加载页面时,初始值为
null
,它不会在 TextField 中显示任何内容。如果之前保存的初始值为"1234"
,它将显示€ 1.234,00
在 TextField 中。这一切都很好。
如果您将此初始值更改为"987"
,则与转换器的绑定将在对象中保存“987”,并在您键入TextField 时立即显示€ 987,00
在TextField 中。
它不会更新 TextField 中的值。它仍会显示987
. 似乎 Vaadin 8 转换器仅用于通过绑定从用户输入到域模型,而不是更改已放入的内容。
另一个变化是转换器现在放在 Vaadin 8 中的 Binding 本身,而不是 Vaadin 7 中的 TextField。
这是一个 gif,其中包含 Vaadin 7 的行为/我在顶部输入字段中寻找的内容,以及底部输入字段中的当前 Vaadin 8 行为:
编辑:我们尝试过的一些事情:
我创建了一个只有一个 TextField、一个绑定和一个转换器的测试页:
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);
这将€ 1.234,00
最初显示,当您键入"987"
时,它将写入987
对象(但仍会显示987
而不是€ 987,00
)。
需要注意的一些事项:
- 如果我们
piggyBank.setAmount(456);
在第 1 行添加,它将€ 456,00
最初显示在 UI 的 TextField 中。 - 如果我们
piggyBank.setAmount(456);
在第 2 行而不是第 1 行添加,它仍然会显示€ 1.234,00
在 UI 的 TextField 中!所以转换器的convertToPresentation
方法在binder.setBean(piggyBank)
. - 如果我们
piggyBank.setAmount(456);
在第 1 行添加,并删除binder.setBean(piggyBank);
它仍然显示€ 1.234,00
而不是€ 456,00
!
我们已经能够找到一个丑陋的解决方法,如下所示:
inputField.setValueChangeMode(ValueChangeMode.BLUR);
inputField.addBlurListener(new BlurListener() {
@Override
public void blur(BlurEvent event) {
if(binder.isValid()) {
binder.readBean(binder.getBean());
}
}
});
如您所见,我们只是在每次输入字段值更改时读取 bean。通过此更改,值仍然正确保存到模型中,并且现在也触发了convertToPresentation
转换器的方法,正确显示€ 987,00
(尽管它仍然不正确地显示€ 1.234,00
,而不是€ 456,00
上面编号列表的情况 2 和 3)。
我们还尝试了内联 lambda 转换器而不是对象;尝试一些事情binder.forMemberField
; 尝试一些事情binder.bindInstanceFields
; 试图以某种方式检索设置的活页夹,这是不可能的;等等等等。我们尝试了各种方法,但 Vaadin 8 转换器无法像在 Vaadin 7 中那样工作,我们无法找到替代方案,甚至找不到合适的解决方法。
PiggyBank 代码可在评论中请求:
public class PiggyBank {
private BigDecimal amount;
public BigDecimal getAmount() {
return amount;
}
public void setAmount(BigDecimal amount){
this.amount = amount;
}
}