Reputation: 113
Following the book "Learning Vaadin 7, Second Edition", I am now trying to display simple beans in a table. However, the book only covers the usage of the old java.util.Date class. I am trying to display a LocalDate property with the use of a Converter.
The bean (Person) I am trying to display:
public class Person {
private long id;
private String firstName;
private String lastName;
private LocalDate birthdate;
private Gender gender;
// .. GETTERS & SETTERS
I have written a LocalDateToStringConverter, implementing the com.vaadin.data.util.converter.Converter.
package be.kapture.converters;
import com.vaadin.data.util.converter.Converter;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Locale;
public class LocalDateToStringConverter implements Converter<String, LocalDate> {
@Override
public LocalDate convertToModel(String value, Class<? extends LocalDate> targetType, Locale locale) throws ConversionException {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy");
try {
return LocalDate.parse(value, formatter);
} catch (DateTimeParseException ex) {
return null;
}
}
@Override
public String convertToPresentation(LocalDate value, Class<? extends String> targetType, Locale locale) throws ConversionException {
return value.toString();
}
@Override
public Class<LocalDate> getModelType() {
return LocalDate.class;
}
@Override
public Class<String> getPresentationType() {
return String.class;
}
}
In the UI, here's the code snippet where I set the converter:
...
Table table = new Table("", container);
table.setConverter("birthdate", new LocalDateToStringConverter());
verticalLayout.addComponent(table);
'container' being a BeanItemContainer, in which I put some example Person objects. When visiting the Vaadin application in my browser I get the following Exception:
jun 27, 2016 1:56:45 PM org.apache.catalina.core.StandardWrapperValve invoke
SEVERE: Servlet.service() for servlet [HelloVaadinServlet] in context with path [] threw exception [com.vaadin.server.ServiceException: com.vaadin.ui.Table$CacheUpdateException: Error during Table cache update. Additional causes not shown.] with root cause
com.vaadin.data.util.converter.Converter$ConversionException: Unable to convert value of type java.time.LocalDate to presentation type class java.lang.String. No converter is set and the types are not compatible.
at com.vaadin.data.util.converter.ConverterUtil.convertFromModel(ConverterUtil.java:116)
at com.vaadin.ui.AbstractField.convertFromModel(AbstractField.java:736)
at com.vaadin.ui.AbstractField.convertFromModel(AbstractField.java:721)
at com.vaadin.ui.AbstractField.setPropertyDataSource(AbstractField.java:657)
at com.vaadin.ui.Table.bindPropertyToField(Table.java:4140)
at com.vaadin.ui.Table.getPropertyValue(Table.java:4109)
at com.vaadin.ui.Table.parseItemIdToCells(Table.java:2386)
at com.vaadin.ui.Table.getVisibleCellsNoCache(Table.java:2225)
at com.vaadin.ui.Table.refreshRenderedCells(Table.java:1745)
at com.vaadin.ui.Table.refreshRowCache(Table.java:2691)
at com.vaadin.ui.Table.containerItemSetChange(Table.java:4587)
at com.vaadin.data.util.AbstractContainer.fireItemSetChange(AbstractContainer.java:242)
at com.vaadin.data.util.AbstractInMemoryContainer.fireItemsAdded(AbstractInMemoryContainer.java:1012)
at com.vaadin.data.util.AbstractInMemoryContainer.fireItemAdded(AbstractInMemoryContainer.java:994)
at com.vaadin.data.util.AbstractInMemoryContainer.internalAddItemAtEnd(AbstractInMemoryContainer.java:884)
at com.vaadin.data.util.AbstractBeanContainer.addItem(AbstractBeanContainer.java:533)
at com.vaadin.data.util.AbstractBeanContainer.addBean(AbstractBeanContainer.java:598)
at com.vaadin.data.util.BeanItemContainer.addItem(BeanItemContainer.java:227)
at be.kapture.MyUI.init(MyUI.java:88)
at com.vaadin.ui.UI.doInit(UI.java:682)
at com.vaadin.server.communication.UIInitHandler.getBrowserDetailsUI(UIInitHandler.java:214)
at com.vaadin.server.communication.UIInitHandler.synchronizedHandleRequest(UIInitHandler.java:74)
at com.vaadin.server.SynchronizedRequestHandler.handleRequest(SynchronizedRequestHandler.java:41)
at com.vaadin.server.VaadinService.handleRequest(VaadinService.java:1409)
at com.vaadin.server.VaadinServlet.service(VaadinServlet.java:364)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:292)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:212)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:141)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:616)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:528)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1099)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:672)
at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.doRun(AprEndpoint.java:2508)
at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.run(AprEndpoint.java:2497)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
Why is it not registering the Converter to do the String to LocalDate conversions (and vice-versa if needed at some point)? I have tried using an anonymous inner class as a parameter in the Table.setConverter method, with the same result. What am I doing wrong here?
UPDATE (on request):
Here is the full UI code where the Table, Container and Converter are being used. Note: it is a "HelloVaadin" sandbox project with no actual goal. It is set-up especially for purposes like this issue, trying to integrate Java 8's LocalDate into Vaadin projects.
package be.kapture;
import be.kapture.converters.LocalDateToDateConverter;
import be.kapture.converters.LocalDateToStringConverter;
import be.kapture.entities.Person;
import be.kapture.util.CustomFieldGroupFieldFactory;
import com.vaadin.annotations.*;
import com.vaadin.data.fieldgroup.FieldGroup;
import com.vaadin.data.util.BeanItem;
import com.vaadin.data.util.BeanItemContainer;
import com.vaadin.server.VaadinRequest;
import com.vaadin.server.VaadinServlet;
import com.vaadin.ui.*;
import javax.servlet.annotation.WebServlet;
import java.time.LocalDate;
import java.util.Arrays;
import static com.vaadin.data.Property.ValueChangeListener;
@Theme("mytheme")
@Widgetset("be.kapture.MyAppWidgetset")
@PreserveOnRefresh
@Title("Hello Vaadin!")
public class MyUI extends UI implements Window.CloseListener {
private static final Person person1 = new Person(1L, "John", "DOE", LocalDate.of(70, 1, 1));
private static final Person person2 = new Person(2L, "Jane", "doe", LocalDate.of(70, 1, 1));
private static final Person person3 = new Person(3L, "jules", "winnf", LocalDate.of(48, 11, 21));
private static final Person person4 = new Person(4L, "vincent", "Vega", LocalDate.of(54, 2, 17));
private static final BeanItemContainer<Person> container = new BeanItemContainer<>(Person.class);
static {
container.addAll(Arrays.asList(person1, person2, person3, person4));
}
private final VerticalLayout verticalLayout = new VerticalLayout();
@Override
protected void init(VaadinRequest vaadinRequest) {
Person person = new Person(1L);
person.setFirstName("John");
person.setLastName("Doe");
person.setBirthdate(LocalDate.now());
BeanItem<Person> beanItem = new BeanItem<>(person);
FieldGroup group = new FieldGroup(beanItem);
group.setFieldFactory(new CustomFieldGroupFieldFactory());
Field<?> id = group.buildAndBind("id");
Field<?> firstName = group.buildAndBind("firstName");
Field<?> lastName = group.buildAndBind("lastName");
Field<?> birthdate = group.buildAndBind("birthdate");
Field<?> gender = group.buildAndBind("gender");
// birthdate.setConverter(new LocalDateToDateConverter());
// birthdate.setPropertyDataSource(item.getItemProperty("birthdate"));
// FormLayout layout = new FormLayout(id, firstName, lastName,
// birthdate);
// layout.setMargin(true);
// setContent(layout);
verticalLayout.setMargin(true);
verticalLayout.setSpacing(true);
verticalLayout.addComponents(id, firstName, lastName, birthdate, gender);
// Define a person which cannot exist
Person nullPerson = new Person(-1L);
nullPerson.setFirstName("Test");
container.addItem(nullPerson);
final ListSelect select = new ListSelect("", container);
// Send events on directly when clicked
select.setImmediate(true);
// Handle the value of the person as null
select.setNullSelectionItemId(nullPerson);
select.setItemCaptionPropertyId("firstName");
select.addValueChangeListener((ValueChangeListener) event -> System.out.println(select.getValue()));
verticalLayout.addComponent(select);
Table table = new Table("");
table.setEditable(true);
table.setConverter(LocalDateToDateConverter.class);
table.setContainerDataSource(container);
verticalLayout.addComponent(table);
setContent( verticalLayout);
}
@Override
public void windowClose(Window.CloseEvent e) {
Notification.show("Window closed.");
}
@WebServlet(urlPatterns = "/*", name = "MyUIServlet", asyncSupported = true)
@VaadinServletConfiguration(ui = MyUI.class, productionMode = false)
public static class MyUIServlet extends VaadinServlet {
}
}
Upvotes: 1
Views: 2673
Reputation: 2749
I was curious and figured out your problem. The method setConverter(Object, Converter)
just allows to change how values in that column are displayed to the user (textual representation). That's why the second argument's type is Converter<java.lang.String,?>
.
In your example you enabled editing in table. This requires Vaadin to know how it will provide a table cell editor for your LocalDate
column. By default, the table doesn't know about LocalDate
. I am aware of 2 options you have:
Converter
for LocalDate
to String
. The table component is then able to show a text field where you can enter a date according to the format in your converter. I did not try what happened when user enters an invalid string.PopupDateField
or something similar. This way you are type-safe and can use built-in date fields.IMO the latter would be a better user experience but of course it is more effort in development.
Upvotes: 1