Nested bean : a collection inside an object - vaadin

I got a simple POJO class that i wish to display / update in a form
Using the BeanItem class and the binding of component data, i was able to quickly display the first attributes of may data class. However i've hit a wall for tow related attributes :
my class posses a set of available status, as a list of object 'AppStatus'. it also possess a current status, that is one of the status in the 'available' list.
I would like to display the list in the form as a combobox, with the current status selected.
I'we managed to associate the 'available' attribute with a combobox, but i can't seem to be able to fill this combobox when setting the data source (method setItemDataSource). How do i get the avalaible status list and the current status from my Item ?
I could always use a workaround and add a parameter to the method to get the source objet in addition to the BeanItem, but i would prefer to avoid this if the Item properties can give me my attribute.
Regards
Edit : shortened exemple, with code from Eric R.
class Status {
String id;
Sting label
+ setter /getter
}
class App {
String AppId;
String AppLabel
ArrayList<Status> availablestatus;
Status currentStatus
+setter/getter
}
in the form extension, in the createField of the fieldfactory i added the following lines
if ("status".equals(propertyId)) {
// create the combobox
ComboBox status = new ComboBox(
texts.getString("application.label.status"));
status.setItemCaptionMode(AbstractSelect.ITEM_CAPTION_MODE_PROPERTY);
status.setItemCaptionPropertyId("label");
status.setImmediate(true);
status.setNullSelectionAllowed(false);
IndexedContainer container = new IndexedContainer(
(Collection<ApplicationStatus>) item.getItemProperty(
"availableStatus").getValue());
status.setContainerDataSource(container);
status.setPropertyDataSource(item.getItemProperty("currentStatus"));
return status;
} else...
this didn't work, i do get a combobox, with the correct number of lines, but all empties.
i tried to use a beanContainer instead of a IndexedContainer
BeanContainer<String, ApplicationStatus> container =
new BeanContainer<String, ApplicationStatus>(ApplicationStatus.class);
container.addAll((Collection<ApplicationStatus>) item
.getItemProperty("availableStatus").
container.setBeanIdProperty("id");
the result is slightly better, since i do have the available values in the combobox.
only the currentValue is not selected...
I also tried to use a nestedbean property to get the id of the currentstatus, but the result is still not valid... i get a combobox, with the correct value selected, but i can not see others values anymore, since the combobox is readonly ?(even with setReadOnly(false);)

I suggest my way to resolve this. I don't think this is the nicest way, but it's works.
The beanItem class contains all you need.
I did the following in a simple project and it's work verry well :
ComboBox status = new ComboBox("ComboBox");
status.setImmediate(true);
status.setNullSelectionAllowed(false);
for(Status st : (Collection<Status>)item.getItemProperty("availableStatus").getValue()) {
status.addItem(st);
status.setItemCaption(st, st.getLabel());
}
status.setPropertyDataSource(item.getItemProperty("currentStatus"));
Hope it's works.
Regards Éric

From the vaadin demo site you can get this sample that show how to fill a combobox with countries. You could do the same i would guess (not sure I understand your problem 100%):
myForm.setFormFieldFactory(new MyFormFieldFactory ());
private class MyFormFieldFactory extends DefaultFieldFactory {
final ComboBox countries = new ComboBox("Country");
public MyFormFieldFactory () {
countries.setWidth(COMMON_FIELD_WIDTH);
countries.setContainerDataSource(ExampleUtil.getISO3166Container());
countries
.setItemCaptionPropertyId(ExampleUtil.iso3166_PROPERTY_NAME);
countries.setItemIconPropertyId(ExampleUtil.iso3166_PROPERTY_FLAG);
countries.setFilteringMode(ComboBox.FILTERINGMODE_STARTSWITH);
}
#Override
public Field createField(Item item, Object propertyId,
Component uiContext) {
Field f = (Field)item;
if ("countryCode".equals(propertyId)) {
// filtering ComboBox w/ country names
return countries;
}
return f;
}
}

Related

Vaadin data Binder - ComboBox issues

Later Edit: I noticed that by returning one of the options in ValueProvider's apply method leads to having the check mark present, but appears to show the previous select too. I.e. if the current and previous values are distinct, two check marks are shown.
I am having troubles with ComboBox binding. I cannot get the com.vaadin.flow.data.binder.Binder properly select an option inside the combobox - i.e. tick the check mark in the dropdown.
My binder is a "generic", i.e. I am using it along with a Map, and I provide dynamic getters/setters for various map keys. So, consider Binder<Map>, while one of the properites inside the Map should be holding a Person's id.
ComboBox<Person> combobox = new ComboBox<>("Person");
List<Person> options = fetchPersons();
combobox.setItems(options);
combobox.setItemLabelGenerator(new ItemLabelGenerator<Person>() {
#Override
public String apply(final Person p) {
return p.getName();
}
});
binder.bind(combobox, new ValueProvider<Map, Person>() {
#Override
public Person apply(final Map p) {
return new Person((Long)p.get("id"), (String)p.get("name"));
}
}, new Setter<Map, Person>() {
#Override
public void accept(final Map bean, final Person p) {
bean.put("name", p.getName());
}
});
Wondering what could I possibly do wrong...
Later edit: Adding a screenshot for the Status ComboBox which has a String for caption and Integer for value.
Your problem is that you are creating a new instance in your binding, which is not working. You probably have some other bean, (I say here Bean) where Person is a property. So you want to use Binder of type Bean, to bind ComboBox to the property, which is a Person. And then populate your form with the Bean by using e.g. binder.readBean(bean). Btw. using Java 8 syntax makes your code much less verbose.
Bean bean = fetchBean();
Binder<Bean> binder = new Binder();
ComboBox<Person> combobox = new ComboBox<>("Person");
List<Person> options = fetchPersons();
combobox.setItems(options);
combobox.setItemLabelGenerator(Person::getName);
binder.forField(combobox).bind(Bean::getPerson, Bean::setPerson);
binder.readBean(bean);

Unkown Key in Vaadin 14 Grid during selection

I'm using a Grid in Vaadin 14. The grid is in multi-selection mode.
The selection handler takes a couple of seconds to complete and I'm calling setItems(...) at the end to update the items in the grid.
When the user selects another row while the previous selection handler is still running, I get an "Unknown key" error similar to the one described in https://github.com/vaadin/vaadin-grid-flow/issues/322, even though the new set of items still contains the selected item (another object instance but same according to equals()). This seems to be because the keys in the KeyMapper have already been changed due to setItems(), so the key coming from the client is not present anymore.
Is there a way to work around this, for example by disabling selection while the previous request is in progress?
UPDATE
To work around this Vaadin bug, I'm also calling setPageSize() with the exact number of items as argument. But it seems the same problem occurs even if I don't call setPageSize(), so it's probably due to setItems().
Do not change the grids items inside a SelectionListener.
You can still do all the things you wanted, but setting the items anew is not actually needed. In fact it will only create problems as you are experiencing now.
While working at this answer, I realized you will need to do your own Checkbox Column in order to be able to do actions for the one item that was just "selected", instead of removing all then add all selected ones (because much better performance). Here is how that could look.
// in my code samples, a `Foo` item can have many `Bar` items. The grid is of type Bar.
Grid.Column customSelectionColumn = grid.addComponentColumn(item -> {
Checkbox isSelected = new Checkbox();
isSelected.setValue(someParentFoo.getBars().contains(item));
isSelected.addValueChangeListener(event -> {
boolean newSelectedValue = event.getValue();
if(newSelectedValue){
someParentFoo.getBars().add(item)
} else {
someParentFoo.getBars().remove(item);
}
fooRepository.save(someParentFoo);
});
});
// make a Checkbox that selects all in the header
Checkbox toggleSelectAll = new Checkbox();
toggleSelectAll.addValueChangeListener(event -> {
if(event.getValue()){
someParentFoo.getBars().addAll(allGridItems);
} else {
someParentFoo.getBars().removeAll(allGridItems);
}
fooRepository.save(someParentFoo);
grid.getDataProvider().refreshAll(); // updates custom checkbox value of each item
});
gridHeaderRow.getCell(customSelectionColumn).setComponent(toggleSelectAll);
I solved this problem. Vaadin use data as key in HashMap. You need calc hashCode use immutable data fields. For example
public class TestData {
private int id;
private String name;
public TestData(int id) {
this.id = id;
}
#Override
public int hashCode() {
return Objects.hash(id);
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

How to fix object set in grid?

In my application i have a class like:
public class Team {
private Country teamId;
private Set<Player> playerSet;
private Set<Player> substitutes;
private Set<Coach> coachSet;
}
When i instantiate a grid like:
Grid<Team> grid = new Grid<>(Team.class);
and set allTeam() from database it shows object for playerSet and coachSet.
My question is i just want to show players name and coach name concate by ,or \n.
Any idea how can i do that?As a beginner it is complicated for me
I see three options.
The first option is the one you already found yourself: concatenate their names in a single String. This can be done like this:
grid.addColumn(team -> {
Set<String> coachNames = new HashSet<>();
for (Coach coach : team.getCoaches()){
coachNames.add(coach.getName());
}
return String.join(", ", coachNames);
});
The second one would be to make use of the Grid item Detail - you could show a coaches grid in the item details. Since you want to display both coaches and players, this option is probably not the best but I wanted to mention the possibility. (Placing two grids inside the item details is possible, but quite strange. Not optimal user experience.)
grid.setItemDetailsRenderer(new ComponentRenderer<>(team -> {
Grid<Coach> coachGrid = new Grid<>(Coach.class);
coachGrid.setItems(team.getCoaches());
return coachGrid;
}));
A third option would be to have the team grid on one side of the view, and on the other you show some relevant stuff of the selected item of the team grid. You can have a separate Grid for the coaches, one for the players, one for the substitutes. You could implement this team detail layout also as a separate view if you wish. If your Team object will get more complicated with more sets, collections and other relative properties, the more will this option become appealing, as this is quite scalable/expandable.
grid.addSelectionListener(event -> {
if(event.getFirstSelectedItem().isPresent()){
buildTeamDetails(event.getFirstSelectedItem().get())
}
})
private void buildTeamDetails(Team team){
// build your team detail layouts here
}
You can configure which columns are shown in the grid by using grid.removeAllColumns() and then adding all columns you want to have in the grid with grid.addColumn(). Within addColumn() you can create a renderer that defines how the fields (coachName and playerSet) are displayed in the grid.
Let's have a class Team like
public class Team {
private String coachName;
private Set<Player> playerSet;
private Set<Object> objects;
//getters and setters
}
and a class Player like
public class Player {
private String firstName;
private String lastName;
// getters and setters
}
Now you want to only have coach and player names in the grid. So (in my example) for coachName we can just use the field's getter and we can create a comma separated String for the playerSet with java streams easily.
Configure the grid like:
grid.setItems(team);
grid.removeAllColumns();
grid.addColumn(new TextRenderer<>((ItemLabelGenerator<Team>) Team::getCoachName))
.setHeader("Coach");
grid.addColumn(new TextRenderer<>((ItemLabelGenerator<Team>) team1 -> team1.getPlayerSet().stream()
.map(player1 -> player1.getFirstName() + " " + player1.getLastName())
.collect(Collectors.joining(", "))))
.setHeader("Players")
.setFlexGrow(1);
Then the result looks like:

Vaadin Bean validation for table

When I enter edit mode of my Table, I want to have data validation on all fields in that Table.
First, a couple of notes:
I'm using Vaadin 7, so the Bean Validation addon sadly won't work.
I know the implementation of JSR-303 works, because I tried adding a BeanValidator to a TextField without issues.
Now, I have a perfectly working table for which I am using a BeanItemContainer to keep my Person beans inside.
The Person bean looks as follows:
public class Person {
#Size(min = 5, max = 50)
private String firstName;
#Size(min = 5, max = 50)
private String lastName;
#Min(0)
#Max(2000)
private int description;
... getters + setters...
}
Person beans are added to the BeanItemContainer, which in turn is set to the container data source with setContainerDataSource()
The BeanValidator was added to the table like so:
table.addValidator(new BeanValidator(Person.class, "firstName"));
When I run the application, I have two problems:
When I run the application, the table shows up as intended. However, when I edit the fields and set one of the firstName fields to, say, "abc" - no validation error is shown and the value is accepted
How am I supposed to get BeanValidator to work on all of my tables fields?
When I use table.setSelectable(true) or table.setMultiSelect(true), I get this error:
com.vaadin.server.ServiceException:
java.lang.IllegalArgumentException: [] is not a valid value for
property firstName of type class
com.some.path.vaadinpoc.sampleapp.web.Person
How am I supposed to get BeanValidator to work with Selectable/MultiSelect?
Please advice
Thanks!
You'll need to add the validators to the editable fields themselves, not to the table. (Table itself is a field => the Validator from table.addValidator validates the value of the Table => the value of the table is the selected itemId(s) => the BeanValidator fails)
You can add the validators to the fields that by using a custom TableFieldFactory on the table. Here's a very simple one-off example for this scenario - clearly, if you need to do this with a lot of different beans/tables, it'll be worth creating a more generic/customizable factory
table.setTableFieldFactory(new DefaultFieldFactory() {
#Override
public Field<?> createField(Item item, Object propertyId, Component uiContext) {
Field<?> field = super.createField(item, propertyId, uiContext);
if (propertyId.equals("firstName")) {
field.addValidator(new BeanValidator(Person.class, "firstName"));
}
if (propertyId.equals("lastName")) {
field.addValidator(new BeanValidator(Person.class, "lastName"));
}
if (propertyId.equals("description")) {
field.addValidator(new BeanValidator(Person.class, "description"));
}
return field;
}

vaadin : multiple links in one field on the table

I want to ask about the UI of the vaadin, which is Table.
If I used this component, then I have to create a field using this command:
userTable.addContainerProperty("Status", String.class, "Active");
If I want to create link into this field, then I have to do like this:
userTable.addContainerProperty("Action", Link.class, new Link("Remove", new ExternalResource("#")));
My question is, the example above, only display single link in one field which is REMOVE Link. I want to create two links in one field of that table. For example link for EDIT and DELETE below the "Action" field, how can I do that?
Use a generated column to add the components to each row. Create an Horizontal Layout and two Buttons as the content.
class ValueColumnGenerator implements Table.ColumnGenerator {
String format; /* Format string for the Double values. */
/**
* Creates double value column formatter with the given
* format string.
*/
public ValueColumnGenerator(String format) {
this.format = format;
}
/**
* Generates the cell containing the Double value.
* The column is irrelevant in this use case.
*/
public Component generateCell(Table source, Object itemId,
Object columnId) {
// Get the object stored in the cell as a property
Property prop =
source.getItem(itemId).getItemProperty(columnId);
if (prop.getType().equals(Double.class)) {
HorizontalLayout hbox = new HorizontalLayout()
hbox.addComponent(new Button("Status"))
hbox.addComponent(new Button("Remove"))
return hbox;
}
return null;
}
}
See Section 5.14.5 of the Book of Vaadin for more info:
https://vaadin.com/book/-/page/components.table.html
You can add this buttons to HorizontalLayout or any other container component. Then add this layout to the container property in your table.

Resources