Вы находитесь на странице: 1из 17

VAADIN

8
1

21 IMPROVEMENTS
in Vaadin 8

by Alejandro Duarte

Vaadin 8 was a major overhaul, deprecating some old features to introduce new support for JDK 8, modern Java APIs,
improved type safety, and easier backend lazy loading.

1
Vaadin Framework 8 renewed essentially the whole data binding API and
added dozens of enhancements. Here is a list of the most important changes
that will give you a boost in your next Vaadin Framework projects.
New Modern Java APIs
The main feature in Vaadin 8 is core API modernization. We now fully take
advantage of parameterization, lambdas, etc. Upgrading to Vaadin 8 is still
easy as legacy APIs are still available in a compatibility package.
Pass Lists of Entities Directly to Grid and Selects
The most common way to populateGrid and various select components has
become much simpler and more typesafe. Just pass the list or array of your
model objects directly to yourGrid or select component:
List<Person> persons = Backend.getPersons();

Grid<Person> grid = new Grid<>(Person.class);

grid.setItems(persons);

ItemCaptionGenerator
ItemCaptionGeneratoris a handy way to define a customized caption for options
in selects:
List<Person> persons = Backend.getPersons();

ComboBox<Person> comboBox = new ComboBox<>();

comboBox.setItemCaptionGenerator(p -> p.getFirstName() + " " + p.getLastName());

comboBox.setItems(persons);

Typesafe Value Change Listeners


ValueChangeListenersare now parameterized with the field value type, which
makes wiring the business logic easier:
comboBox.addValueChangeListener(evt -> assert(evt.getValue().getId() == 42));

Grid Column Definition With Typesafe Lambda Expres-


sions
A typesafe mechanism to define columns that is faster to use in-IDE and
much easier to understand:
Grid<Person> grid = new Grid<>();

grid.setItems(persons);

2
grid.addColumn(Person::getFirstName).setCaption("First Name");

grid.addColumn(Person::getLastName).setCaption("Last Name");

Easier Lazy Loading of Data From the Backend


Previously, for a memory efficient lazy loading solution you needed to imple-
ment a complexContainer interface. Now, in most cases, you can do a lazy
binding to your backend using two lambda expressions:
grid.setDataProvider(

(sortorder, offset, limit) -> service.findAll(offset, limit),

() -> service.count()

);

Inline Converter With Lambda Expressions


You can define a converter inline using two lambda expressions:
new Binder<Person>().forField(textField)

.withConverter(

textToInteger -> Integer.parseInt(textToInteger),

integerToText -> integerToText.toString()

// or with method references:

// withConverter(Integer::parseInt, Object::toString)

.bind("age");

Validators Can Be Defined Both Before and After Con-


version
You can define field validators both based on the value type of the field or the
converted value that will be used in your business objects:
new Binder<Person>().forField(tf)

.withValidator(str -> str.length() == 4, "Must be 4 chars")

.withConverter(new StringToIntegerConverter("Must be Integer"))

.withValidator(integer -> integer.equals(2017), "Wrong date")

3
.bind(Person::getBirthYear, Person::setBirthYear);

Legacy APIs Still Supported


For components with dramatic changes, we have preserved the legacy compo-
nents in a compatibility package. With the migration tool you can upgrade to
Vaadin 8 in a snap and start using new improved APIs gradually. Update the
version of the Vaadin Maven Plugin to the latest available version and run:
mvn vaadin:upgrade8

Support for HTML5 History API


History API makes it possible for single-page web apps to change the location
of the browser and this way support proper deep linking, back button and
search engines, without hashbang ("#!" in URL) haxies:
// push state:

Button button = new Button("Go to page 1");

button.addClickListener(e -> {

// URL will change to .../page1

Page.getCurrent().pushState("page1");

});

// pop state:

Page.getCurrent().addPopStateListener(event -> {

String uri = event.getUri();

// ... update the UI accordingly

});

HTML Imports
HTML imports are a technology needed for utilizing web components. Since
Vaadin 8, HTML imports are supported by the Framework, without theneed
for special add-ons or low-level JavaScript extensions:
bower install -save game-card

4
// implement the server-side component:

@JavaScript("GameCard.js")

@HtmlImport("vaadin://bower_components/game-card/game-card.html")

public class GameCard extends AbstractJavaScriptComponent {

public GameCard(String symbol, String rank) {

callFunction("setCard", symbol, rank);

// implement the client-side component (GameCard.js):

com_example_GameCard = function () {

var element = this.getElement();

this.setCard = function (symbol, rank) {

element.set("symbol", symbol);

element.set("rank", rank);

};

};

com_example_GameCard.tag = "game-card";

Add Components and Automatically Expand Them


The newaddComponentsAndExpand method removes boilerplate code when adding
components that need to be expanded:
HorizontalLayout header = new HorizontalLayout(title, logout);

VerticalLayout root = new VerticalLayout(header);

root.addComponentsAndExpand(grid);

Helpful Guidance in Exception Messages


Exceptions thrown by the framework often contain a verbose explanation in
the error message about how to fix the code:

5
Improved Defaults
Because Vaadin 8 is a major version, the team had a chance to modify the de-
fault settings of some commonly used components. Less to configure, more
time to think about your actual UI.
Null Values Are Displayed as Empty Strings by Default
In earlier Vaadin versions,TextField rendered null values as "null", which
may have been a good choice for developers, but useless for end users. Now
the default is an empty string as it should be:

6
Ordered Layouts Now Have Better Defaults
Vaadin UIs now look better than ever by default. Margin and spacing defaults
have been adjusted in most commonly used layouts and you don't need to
callsetMargin andsetSpacing that often:
With Vaadin 7 With Vaadin 8

Eager Field Validation


Vaadin 8 supports using proper validation while the user is typing in fields.
This improves the UX to desktop level:
User user = new User();

TextField textField = new TextField("Vaadin 8");

Binder<User> binder = new Binder<>();

binder.setBean(user);

binder.forField(textField)

.withValidator(new StringLengthValidator("Too short", 8, 256))

.bind(User::getUsername, User::setUsername);

Improved Performance
The new data binding code is much easier and productive for developers, but
it also saves quite a bit of memory and CPU.
Up to 10 Times Less Overhead in In-Memory Listings
The new data access mechanism consumes multitudes less memory when list-
ing in-memory data. Previously, the most commonly usedContainer,BeanItem-
Container, eagerly created a wrapper for each and every domain object you
wanted to list. Now extra memory is not wasted for non-visible objects at all.
A Fraction of thePrevious CPU Consumed When List-
ing Large Datasets
When listing in-memory data, bean introspection is no longer done at all or
done on demand. This saves a lot of CPU cycleswhen listing a large number
of rows in your data grids. In a simple test with 100,000 entities listed in
aGrid, the server CPU time needed to serve the view dropped from 233ms to
9ms.
Built for the Future
7
There are some changes that might actually remove "features," but which al-
low us to add exciting new features in upcoming minor releases.
Modern Web Browsers Supported
By dropping legacy browser support, we can start using more modern web
technologies, like CSS3 flexbox layout, and add client-side optimizations,
which were not possible before.
Java 8 or Newer Required
Using JDK 8 features in the API allows us to build features faster and in a
more elegant way. Many of the API improvements presented above wouldn't
have been possible while supporting JDK 7.
Modern Java Server Required
Vaadin 8 requires a Servlet 3 container, which also rules out some legacy serv-
ers. Less hacks needed for old servers, which results in amore maintainable
code base.
New Hook for Add-ons to Configure Themselves
Vaadin add-ons can now hook to application initialization usingVaadinServ-
iceInitListener interfaces, built on Java's standard ServiceLoader mecha-
nism. This can make the usage of certain Vaadin add-ons much easier:
// file META-INF/services/com.vaadin.server.VaadinServiceInitListener:

com.example.MyListener

// file: MyListener.java:

import com.vaadin.server.ServiceInitEvent;

import com.vaadin.server.VaadinServiceInitListener;

public class MyListener implements VaadinServiceInitListener {

@Override

public void serviceInit(ServiceInitEvent event) {

// Configure service, add session init listeners,

// request handlers etc...

// ...

8
9
2

CHAPTER

VIRITIN
Commodo hendrerit frigilla lucus mauris

10
Lazy loading in Viritin
Clone in Desktop
Viritin contains sophisticated solutions for "lazy loading" data from your backend services.
You can/should use this technique when you connect a Table/ComboBox/Grid to a large
set of rows/options. Using the lazy loading features in Viritin have been build as simple as
possible, so it is easy to connect it to variety of "backends", but it is still better performing
than e.g. JPAContainer.
Lazy loading is build using ListContainer and LazyList classes, and their sub-classes, but
most often you don't need to use those classes directly. Instead you can, and it is sug-
gested, that just bind your data source directly to the UI components.
Generally you'll have to implement two interfaces, most commonly with lambda expres-
sions. CountProvider needs to return the amount of items available and PagingProvider
will return a java.util.List of items, starting from a certain index.

MTable and MGrid


Of these two components, using MTable is still the preferred: more mature and stable. The
suggested way to do lazy loading with MTable and MGrid is to use lazyloadFrom(...) meth-
ods and a constructor with the explicit type parameter. Though, it is possible to iron out a
line or two by using the constructors.
Note: If you don't provide the type parameter, and your listing is empty, properties are not
show in header at all.
Next listing uses the extended version with sorting, against a Spring Data JPA repository.
The non-sortable version contains only one parameter for the PagingProvider (first parame-
ter), making the binding naturally even simpler.
list = new MTable<>(Person.class);

list.lazyLoadFrom(
// entity fetching strategy
(firstRow, asc, sortProperty) -> repo.findAllBy(
new PageRequest(
firstRow / PAGESIZE,
PAGESIZE,
asc ? Sort.Direction.ASC : Sort.Direction.DESC,
// fall back to id as "natural order"
sortProperty == null ? "id" : sortProperty
)
),
// count fetching strategy
() -> (int) repo.count(),
PAGESIZE
);
If you use the variations without page size, the default page size is 45. Note, that by de-
fault, that is too small for full sized Grid, causing Grid to do random access to the back-
end and causes issues with the lazy loading strategy. If you see too many queries to your
backend, try increasing the page size.
Note: If you use the lazy listing in many places in your application, create a custom class
to abstract the lazy loading code. E.g. PersonListing extends MTable in this case.

11
Filtering listing
If your UI has a filtering for the listing, you can simply use the filtering data directly in your
binding code. To notify the components of changed data, also call refreshRows() method,
that will clear the cache in components.

LazyComboBox
Lazy loading in the LazyComboBox works with similar principles as with MTable. The dier-
ence is, that both methods in the functional interfaces that you need to implement, gets an
additional filter (String) parameter. Below you can see an example usage with classes (but
naturally your can and should use lambdas if you can). As you are responsible for doing
the filtering, you can do pretty much what ever you want with the filtering, simple starts
with filtering is only one option. In this example we also use a CaptionGenerator to com-
bine firstName and lastName properties to be shown on the UI.
final LazyComboBox.FilterablePagingProvider filterablePagingProvider = new
LazyComboBox.FilterablePagingProvider() {

@Override
public List findEntities(int firstRow, String filter) {
return service.findPersons(filter, firstRow,
LazyList.DEFAULT_PAGE_SIZE);
}
};
final LazyComboBox.FilterableCountProvider filterableCountProvider = new
LazyComboBox.FilterableCountProvider() {

@Override
public int size(String filter) {
return service.countPersons(filter);
}

};

final LazyComboBox<Person> cb = new LazyComboBox(Person.class, filterablePaging-


Provider, filterableCountProvider)
.setCaptionGenerator(new CaptionGenerator<Person>() {

@Override
public String getCaption(Person option) {
return option.getFirstName() + " " + option.
getLastName();
}
});
And the lambda version of the above:
final LazyComboBox<Person> cb = new LazyComboBox(Person.class,
(firstRow, filter) -> return service.findPersons(filter, firstRow,
LazyList.DEFAULT_PAGE_SIZE),
filter -> return service.countPersons(filter)
).setCaptionGenerator(p -> return p.getFirstName() + " " + p.getLastName());

12
Non-lowercase filter string
LazyComboBox by default (like core Vaadin always) lowercases the filter string. This can
be avoided by calling setUseRawFilter(true).

Eager Validation
Clone in Desktop
By eager validation we mean that user input is validated while the date is being inputted
and NOT postponed until the user presses Submit or Save. In eager validation you also
typically control your save button's enabled state, based on the validity of your input
fields. This highly improves UX and puts your web app's forms in par with typical desktop
UI's. This slightly increases the chattiness of the application, but in most cases this is irrele-
vant if you really start to investigate your apps performance characteristics.
Eager validation is used by default by AbstractForm and is also supported the MBeanField-
Group class. Also MTextField has a special support for eager validation, while user is typ-
ing into the text field, so it is highly suggested to use that instead of raw TextField in your
form.

AbstractForm
Clone in Desktop
AbstractForm tries to make editing simple Java beans as easy as possible. It also tries to
guide you towards good UX for the forms you build to edit your beans. It for example by
default uses eager validation, so that users cannot click save until they have filled in a
proper input. This also makes your job a bit easier.
Like the core Vaadin, AbstractForm supports JSR-303 style validation (as well as other pro-
grammatic validation). In the example in this page, we use a following bean:
public class Person {
private int id;

@NotNull
@Size(min = 3, max = 15)
private String firstName;
private String lastName;

@NotNull
private Integer age;

private List<Address> addresses = new ArrayList<Address>();

private List<Group> groups = new ArrayList<Group>();

// getters/setters omitted
}
To create a re-usable form for that bean you could use following class:
public static class PersonForm extends AbstractForm<Person> {

13
private MTextField firstName = new MTextField("Name");
private IntegerField age = new IntegerField("Age");

@Override
protected Component createContent() {
return new MVerticalLayout(firstName, age
getToolbar());
}

}
The above class only allows user to edit firstName and age properties. The getToolbar()
method, which you can override (see implementation), contains basic Save, Cancel and
Delete buttons, which appear if related handlers have been set.

Handlers
You can define handlers inside your form class or separately, which ever suits better for
your architecture. If you are using the default getToolbar(), setting handlers also make a re-
lated button appear in the UI. Save button is only active (by default, see protected void ad-
justSaveButtonState() method), if the form has unsaved changes and, if the form uses ea-
ger validation, the changes pass all validation.
All handlers are functional interfaces with only one parameter, the currently edited bean.
NOTE: You are by no means tied to using these handlers, you can also just add your own
save/cancel features or tie it to automatic saving.
TIP: If you want to use the built in handlers and buttons, but don't like how buttons are
enabled/disabled by default, override methods adjustSave(Reset/Deleted)ButtonState.

SaveHandler
An example usage for SaveHandler (as lambda expression):
form.setSavedHandler(entity -> service.save(entity));

ResetHandler
An example usage for ResetHandler (as method reference):
form.setResetHandler(this::closeEditor);

DeleteHandler
An example usage for DeleteHandler (as lambda expression):
form.setDeleteHandler(entity -> service.remove(entity));

Assigning bean for editing


Assigning bean for editing is done with setEntity(T entity) method, for example:
form.setEntity(person);
For good UX, it is suggested that you also focus a field from your form. You can use e.g.
the helper method focusFirst().

14
Helper for "popup" usage
In apps that target desktop users, it is often handy to show the form in a (modal) popup.
The form has a built in helper method openInModalPopup() for this use case. It also auto-
matically focuses the first field inside the popup for better default UX.
Opening with the helper method:
form.openInModalPopup();
There is also helper to return the popup (if currenly open) and to close it:
Window popup = form.getPopup();
// or you can use shorthand to close it, e.g. in reset handler
form.closePopup();

Editing "nested properties"


AbstractForm supports nested properties. Fields that edit nested properties must be anno-
tated with @PropertyId annotation and nested properties must be explicitly defined. An ex-
ample of such form below:
public static class PersonForm extends AbstractForm<Person>
@PropertyId("address.type")
EnumSelect<Address.AddressType> type = new EnumSelect<>();
@PropertyId("address.street")
MTextField street = new MTextField().withInputPrompt("street");
@PropertyId("address.city")
MTextField city = new MTextField().withInputPrompt("city");
@PropertyId("address.zipCode")
MTextField zipCode = new MTextField().withInputPrompt("zip");

public PersonForm() {
setNestedProperties("address.street","address.city", "address.zipCode",
"address.type");
}

// ....

Cross-field validation
AbstractForm supports Cross-Field-Validation. See the referred page for more details.

Cross Field Validation


Clone in Desktop
AbstractForm and the low level MBeanFieldGroup support cross-field validation. Both JSR
303 style bean level validators and a programmatic validators are supported. See more de-
tails from Cross Field Validation.
NOTE: Bean level validators are only executed if all property level validators pass. If they
do, then all bean level validators are executed. So, unlike with property level validations
with Vaadin, the validation doesn't stop for the first invalid validator.

15
If you use bean level validators, you should also decide how to show possible validation
errors for them. Abstract field contains a method called Component getConstraintViola-
tionsDisplay() that returns a built in display for bean level validation errors. Easiest way is
to use that and show that somewhere in your UI. If you want to handle them manually,
MBeanFieldGroup contains a method public Collection getBeanLevelValidationErrors() that
you can use to retrieve current bean level validation constraint messages.
Alternatively you can target bean level validation errors to be displayed on certain fields.
See sub topics for more details.

MValidator interface
MValidator is a well typed sister for the Validator in the Vaadin core. The parameter the vali-
date method gets is of the type of bean that is edited, so it can be used for bean level (or
"cross-field") validation as well. Those can be added to AbstractForm or MBeanField-
Group using addValidator method.
An example usage for form editing Reservation beans:
addValidator(reservation -> {
if (reservation.getStart().getTime() > reservation.getEnd().
getTime()) {
throw new Validator.InvalidValueException(
"End time cannot be before start time!");
}
});
Optionally you can specify which Fields are related to this validator. If you specify the
fields, the error message is shown on those fields.

JSR 303 style validators


With JSR 303 style bean level validators, it is enough to just present the validator in the ed-
ited class.
You can use public void setValidationGroups(Class<?>... validationGroups) to specify
which validators should be taken into account, but note, that groups are not taken into ac-
count for property level validators.
If you want to direct error messages of bean level validation to certain field(s), use setVali-
dationErrorTarget method, e.g. :
setValidationErrorTarget(FieldMatch.class, verifyEmail);

Full usage example


Refer to CrossFieldValidation in the add-ons integration tests for full usage examples.

16

Вам также может понравиться