In Vaadin 8 Framework, and Vaadin 10 Flow, the data-binding capability lets us provide a Converter
to mediate between the widget’s expected data type (such as String
for a TextField
) and the data type of the backing bean property (such as Integer
number).
In this example, the built-in Converter
implementation StringToIntegerConverter
is used.
binder
.forField( this.phaseField )
.withConverter(
new StringToIntegerConverter( "Must enter an integer number" )
)
.bind( Panel::getPhase , Panel::setPhase ) ;
But what about defining a Converter
for other types? How can I easily define a short-and-sweet Converter
? For example, a String-to-UUID converter. I want to show the canonical 36-character hex string in a TextField
, and going the other direction, parse that string back into a UUID.
// String to UUID
UUID uuid = UUID.fromString( myString ) ;
// UUID to String
String myString = uuid.toString() ;
I see that Binder.BindingBuilder
offers the pair of methods withConverter
that both take a pair of SerializableFunction
objects.
Binder.BindingBuilder::
withConverter
(SerializableFunction<TARGET,NEWTARGET> toModel, SerializableFunction<NEWTARGET,TARGET> toPresentation)
Binder.BindingBuilder::
withConverter
(SerializableFunction<TARGET,NEWTARGET> toModel, SerializableFunction<NEWTARGET,TARGET> toPresentation, String errorMessage)
➥ So how do I define the pair of SerializableFunction
objects/classes?
I noticed that this interface lists a known subinterface ValueProvider
<SOURCE,TARGET>
. That looks familiar, and I have a hunch it is the key to easily defining a short simple converter. But I do not quite comprehend the syntax with lambdas and all that is going on here.
I am not asking how to write a class implementing Converter
. I am asking how to write the pair of SerializableFunction
arguments to pass to the Binder.BindingBuilder::withConverter
methods listed above as bullet items.
Quoting that JavaDoc:
Interface Binder.BindingBuilder<BEAN,TARGET>
…
withConverter
default <NEWTARGET> Binder.BindingBuilder<BEAN,NEWTARGET> withConverter(SerializableFunction<TARGET,NEWTARGET> toModel, SerializableFunction<NEWTARGET,TARGET> toPresentation)
Maps the binding to another data type using the mapping functions and a possible exception as the error message.
The mapping functions are used to convert between a presentation type, which must match the current target data type of the binding, and a model type, which can be any data type and becomes the new target type of the binding. When invoking
bind(ValueProvider, Setter)
, the target type of the binding must match the getter/setter types.For instance, a TextField can be bound to an integer-typed property using appropriate functions such as:
withConverter(Integer::valueOf, String::valueOf);
Type Parameters:
NEWTARGET
- the type to convert toParameters:
toModel
- the function which can convert from the old target type to the new target type
toPresentation
- the function which can convert from the new target type to the old target typeReturns:
a new binding with the appropriate type
Throws:
IllegalStateException
- if bind has already been called
You can do it by passing two lambda expressions to withConverter
, so something like this:
binder.forField(textField)
.withConverter(text -> UUID.fromString(text), uuid -> uuid.toString())
.bind(/* ... */);
If you need a more complicated conversion, then the right-hand side of the lambda can be surrounded with brackets, e.g.
binder.forField(textField).withConverter( text -> {
if ( text == null ) {
return something;
} else {
return somethingElse;
}
}, uuid -> { return uuid.toString(); } )
.bind(/* ... */);