Hello everyone,
I just saw yesterday, that the IModel became/will become a @FunctionalInterface
in Wicket 8 and was very happy about it. I am also very happy about the ongoing
implementations and discussions around lambda usage in Wicket, because it
simplifies a lot of code.
Now, one thing I have been working on, is implementing some of the now known
methods of Stream<T> for a AbstractReadOnlyModel and also for other things -
like columns. I have not seen such discussions on here, please forgive me if I
missed that. If it has indeed not been discussed and you find the idea useful,
you can take a look at an implementation in the following repository:
https://github.com/noobymatze/lambdawicket-model/blob/master/src/main/java/com/github/noobymatze/lambdawicket/model/ReadOnlyModel.java
What might we be able to do with that?
Consider a Person containing an Address, containing a Street with a name. Now I
want to display the name of the street. How would I go about that?new
Label("streetName", () -> person.getAddress().getStreet().getName());
This is one approach, but it completely ignores the possibility of null values
and a default value for such cases. I could wrap the above in an Optional or
create a method in the Person class like 'Optional<String> getStreetName()
{...}'.
With the proposed ReadOnlyModel I could also do it in the following way:
new Label("streetName", ReadOnlyModel.of(person). map(Person::getAddress).
map(Address::getStreet). map(Street::getName). orElse("n/a"));
But that's not all. Imagine we would need to dynamically truncate the
streetName based on user input:
IModel<WicketFunction<String, String>> truncate = () -> str -> str.length() >
10 ? str.substring(0, 10) + "..." : str;
IModel<String> truncatedStreetName = ReadOnlyModel.of(person).
map(Person::getAddress). map(Address::getStreet). map(Street::getName).
apply(truncate). orElse("n/a");
Now we could switch the truncate Model using a
DropDownChoice<WicketFunction<String, String>> or by just setting the contained
function somewhere.truncate.setObject(str -> str.substring(0, 20));
The whole approach is - if I am not mistaken - heavier on memory, because
instead of one IModel<String> we now have 5 or 6 Models, that cannot be garbage
collected, because they are all needed to perform the computation.
This doesn't apply for mapping over an IColumn though, because it doesn't need
to rely on the laziness, like the ReadOnlyModel does (otherwise a change in
truncate wouldn't mean a change in the final value), so:new
LambdaColumn(of("Name"), Person::getAddress). map(Address::getStreet).
map(Street::getName). orElse("n/a"));
would create 4 new instances of the LambdaColumn, but 3 of them could be gc'd
more or less the second they were created - like I said, if I am not mistaken.
If this is at all an approach you think might be useful, I would be happy to
provide the implementation of the LambdaColumn. Otherwise just ignore my
message. :)
Have a nice day,Matthias