0

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 to

Parameters:

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 type

Returns:

a new binding with the appropriate type

Throws:

IllegalStateException - if bind has already been called

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
  • 1
    Are you asking how to make your own custom Converter? – Jay Jul 12 '18 at 08:08
  • @Jay Nope. I am *not* asking how to write a class implementing [`Converter`](https://vaadin.com/download/release/8.4/8.4.5/docs/api/com/vaadin/data/Converter.html). I am asking how to write the pair of `SerializableFunction` arguments to pass to the [`Binder.BindingBuilder::withConverter`](https://vaadin.com/download/release/8.4/8.4.5/docs/api/com/vaadin/data/Binder.BindingBuilder.html#withConverter-com.vaadin.server.SerializableFunction-com.vaadin.server.SerializableFunction-) methods listed as bullet items in my Question. Examine the JavaDoc I linked in previous sentence. – Basil Bourque Jul 12 '18 at 18:38

2 Answers2

4

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(/* ... */);
Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
ollitietavainen
  • 3,900
  • 13
  • 30
  • How does a regular Lambda qualify as a [`SerializableFunction`](https://vaadin.com/download/release/8.4/8.4.5/docs/api/com/vaadin/server/SerializableFunction.html) required by the `.withConverter` method? – Basil Bourque Jul 12 '18 at 18:42
  • 1
    It's because of Java 8 magic - and the `@FunctionalInterface` annotation on the SerializableFunction interface. Check the javadocs for some more information: https://docs.oracle.com/javase/8/docs/api/java/lang/FunctionalInterface.html – ollitietavainen Jul 13 '18 at 10:33
  • This does indeed work; verified in my project. But I am still confused. What is the purpose of the Vaadin team defining the `SerializableFunction` interface if we can just slip in a regular Lambda? – Basil Bourque Jul 14 '18 at 22:46
  • I realized the question asked in my previous comment is an interesting topic of its own. So I posted a separate Question, [*What purpose is served by the `SerializableFunction` interface defined in Vaadin 8 if we can simply pass a regular Lambda expression in its place?*](https://stackoverflow.com/q/51344045/642706) – Basil Bourque Jul 14 '18 at 23:05
1

If you need your converter multiple times, I recommend creating a separate class implementing interface com.vaadin.data.Converter. However, using lambdas is possible, too, as you already know (see answer of @ollitietavainen). But this is not Vaadin specific, it's a Java 8+ feature you can read about e.g. here. Basically, you can use lambdas whereever an object implementing an interface with only one method is required.

Steffen Harbich
  • 2,639
  • 2
  • 37
  • 71