Adding new line to top of Grid with data does not display prefilled values in non-editable columns (vaadin7) - vaadin

Let's say I load data into a Grid. That works perfectly, everything is displayed. I can see it just fine, even after I call editItem(objectId); to edit data for any given line in the Grid.
Then let's say I have a button that adds a new line with mostly empty elements. In other words, the corresponding bean is mostly empty except for some default values. For some reason this behaves weird when I call editItem(objectId);. Here are the situations and their results:
If column is editable (column.setEditable(true);), my default data displays just fine
If column is not editable (column.setEditable(false);), my default data does NOT display. It is definitely in the bean, just not displayed. I see it once I click "Save" or "Cancel" in the edit form.
If I just show the line, do NOT enter the edit form (don't call editItem(objectId);), it display the default data just fine. I mention this just to point out what happens in the "display only" situation.
I even tried making the editField read only, and even that hid the data. So what is happening?
Example of data being displayed (see circled red):
Figure 1: Non-Empty data, but editable column
Example of data NOT being displayed (see circled red):
Figure 2: Empty data inside edit form
Figure 3: Non-Empty data after clicking save.
Note that it does not matter if the column is a ComboBox or just a regular text element, if I make it non-editable, it will not show the value I put in that column on this new line until after I click Save or Cancel.
Here is how I load the list of beans initially, where gridContainer is defined as BeanItemContainer<T> gridContainer:
public void updateList( List<T> dataList, T defaultData ) {
updateList( dataList, defaultData, new GridLoader<T>() {
#Override
public void loadGrid(List<T> dataList) {
gridContainer.removeAllItems();
if( dataList instanceof List && !dataList.isEmpty() )
gridContainer.addAll(dataList);
}
});
}
This works fine for non-editable columns, all data being displayed as expected. My pictures sort of hide it, but it is displaying just fine, and works in edit mode just fine as well.
And here is how I add a new line:
private void addRouting() {
Routing emptyData = Routing.create();
emptyData.setKey(null);
if( facilityId instanceof String && !facilityId.trim().isEmpty() )
emptyData.setFacilityId(facilityId.trim());
if(wmsItem instanceof String && !wmsItem.trim().isEmpty())
{
emptyData.setWmsItem(wmsItem);
// gridComponent.hideColumn("wmsItem", true);
// gridComponent.hideColumn("wmsItemDisplay", false);
}
else
{
// gridComponent.hideColumn("wmsItem", false);
// gridComponent.hideColumn("wmsItemDisplay", true);
}
if(workCenter instanceof String && !workCenter.trim().isEmpty())
{
emptyData.setWorkCenter(workCenter);
// gridComponent.hideColumn("workCenter", true);
// gridComponent.hideColumn("workCenterDisplay", false);
}
else
{
// gridComponent.hideColumn("workCenter", false);
// gridComponent.hideColumn("workCenterDisplay", true);
}
gridComponent.addItemAt(0, emptyData);
gridComponent.select(emptyData);
if(gridComponent.isEditorEnabled())
gridComponent.editItem(emptyData);
}
And in GridComponent, we have addItemAt defined as follows (BTW, GridComponent just wraps a layout with a toolbar at top and a Grid for the data, and so exposes various methods I need from the toolbar and Grid):
public BeanItem<T> addItemAt(int index, T bean) throws IllegalStateException {
BeanItemContainer<T> gridContainer = getGridContainer();
if(gridContainer instanceof BeanItemContainer)
{
/* Clear filter first because adding an item will break this.
* TODO: Maybe restore prior filter with "saveLastFilter();" and then "reapplyLastFilter();" after "add"
*/
saveLastFilter();
clearFilter();
BeanItem<T> newItem = gridContainer.addItemAt(index, bean);
reapplyLastFilter();
return newItem;
}
else
throw new IllegalStateException("Missing bean grid container");
}

Related

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;
}
}

Vaadin Grid Row Index

In a vaadin table if we do
table.setRowHeaderMode(RowHeaderMode.INDEX);
we get a column with the row index.
Is it possible to to the same with a vaadin grid?
So far I haven't seen such an option, but you should be able to fake it with a generated column. Please see below a naive implementation which should get you started (improvements and suggestions are more than welcome):
// our grid with a bean item container
Grid grid = new Grid();
BeanItemContainer<Person> container = new BeanItemContainer<>(Person.class);
// wrap the bean item container so we can generated a fake header column
GeneratedPropertyContainer wrappingContainer = new GeneratedPropertyContainer(container);
wrappingContainer.addGeneratedProperty("rowHeader", new PropertyValueGenerator<Long>() {
private long index = 0;
#Override
public Long getValue(Item item, Object itemId, Object propertyId) {
return index++;
}
#Override
public Class<Long> getType() {
return Long.class;
}
});
// assign the data source to the grid and set desired column order
grid.setContainerDataSource(wrappingContainer);
grid.setColumnOrder("rowHeader", "name", "surname");
// tweak it a bit - definitely needs more tweaking
grid.getColumn("rowHeader").setHeaderCaption("").setHidable(false).setEditable(false).setResizable(false).setWidth(30);
// freeze the fake header column to prevent it from scrolling horizontally
grid.setFrozenColumnCount(1);
// add dummy data
layout.addComponent(grid);
for (int i = 0; i < 20 ; i++) {
container.addBean(new Person("person " + i, "surname " + i));
}
This will generate something similar to the image below:
There is a Grid Renderer that can be used to do this now. It is in the grid renderers add-on https://vaadin.com/directory/component/grid-renderers-collection-for-vaadin7. It is compatible with Vaadin 8 as well.
Here is how it could be used (there are a few different options for how to render the index).
grid.addColumn(value -> "", new RowIndexRenderer()).setCaption("Row index");
Worth to mention that I use the following with Vaadin 18 flow and works perfectly.
grid.addColumn(TemplateRenderer.of("[[index]]")).setHeader("#");
Ok, it took me more than a while to figure this out. I don't know why you need this, but if your purpose is to find which grid row was clicked, then you can get the index from the datasource of your control via the itemClick event of your listener.
In my case, my datasource is an SQLContainer, and I already had it available (see ds var) so I did it this way:
grid.addListener(new ItemClickEvent.ItemClickListener() {
#Override
public void itemClick(ItemClickEvent event) {
Object itemId = event.getItemId();
int indexOfRow = ds.indexOfId(itemId);
}
});
You usually add a datasource to your control when you initialize it, via constructor or by setting the property. If you got you Grid from somewhere with an already-attached datasource, you can always get it with something like this:
SQLContainer ds = (SQLContainer)gred.getContainerDataSource();
I use this trick:
int i = 0;
grid.addComponentColumn(object -> {
i++;
return new Label("" + i);
}).setCaption("");

How does the Vaadin table work with Navigator?

I'm writing a view which navigates to a table entry's page displayed on the left side when a table entry (on the right) is chosen. This is similar to the addressbook tutorial on Vaadin's site, only I make use of the Navigator and views.
While I got the navigation to work (clicking on entry with id #12 navigates to localhost:8080/test/12) and a test label in the view's enter() gets changed to match the id, testTable.getItem(event.getParameters()) returns null for some reason so I can't access the entry.
The ValueChangeListener and enter() for the view are shown below.
class ValueChangeListener implements Property.ValueChangeListener {
Object testId;
#Override
public void valueChange(ValueChangeEvent event) {
// Navigate to a chosen table entry
this.testId = event.getProperty().getValue();
navigator.navigateTo("test/" + testId);
}
}
...
public void enter(ViewChangeEvent event) {
Object tmp = event.getParameters();
testName.setValue((String) tmp); // is set to the id
System.out.println(testTable.getItem(tmp) == null); // DEBUG: always returns true
}
I think you should change this:
System.out.println(testTable.getItem(tmp) == null);
to this:
String str = (String) tmp;
if (str != null && !str.isEmpty()) {
System.out.println(testTable.getItem(Integer.parseInt(str)) == null);
}
I think there's something wrong in how you manage you Navigator.
Firstly when you change view with Navigator you who should add a proper "URL fragment" with "#".
For example the Vaadin sampler uses:
http://demo.vaadin.com/sampler/#foundation
If in your URL there's no "#" ViewChangeEvent.getParameters() gives you null or isEmpty().

How to sort records (with code) in a grouped ListGrid?

This is the scenario: I'm working with a listgrid that needs to be grouped, and also needs to have its records ordered within each group. I've already used the ListGrid.sort() and the ListGrid.sort(String, SortDirection) methods but none of them works properly.
This problem doesn't show up when the grid isn't grouped (it makes the sort perfectly); and when the sort (with the listgrid is grouped) is made by clicking the column header, works fine but I need to sort it by code (without user interaction) because the header sort option needs to be disabled (and context menu too).
I'm using SmartGWT 4.0
Here is the class I'm using:
public class Access extends ListGrid {
public Access() {
super();
setWidth("30%");
setHeight100();
// setShowHeaderContextMenu(false);
setCanResizeFields(false);
// setCanSort(false);
setAutoFitWidthApproach(AutoFitWidthApproach.BOTH);
setWrapCells(true);
setFixedRecordHeights(false);
setShowRecordComponents(true);
setShowRecordComponentsByCell(true);
ListGridField id = new ListGridField("id", "ID");
ListGridField user = new ListGridField("user", "User");
ListGridField access = new ListGridField("access", "Access");
id.setHidden(true);
user.setWidth("60%");
access.setWidth("40%");
access.setType(ListGridFieldType.BOOLEAN);
access.setCanEdit(true);
setFields(id, user, access);
groupBy("access");
access.setGroupTitleRenderer(new GroupTitleRenderer() {
public String getGroupTitle(Object groupValue, GroupNode groupNode, ListGridField field, String fieldName,
ListGrid grid) {
return (String) groupValue + " - " + groupNode.getGroupMembers().length;
}
});
getField("access").setGroupValueFunction(new GroupValueFunction() {
public Object getGroupValue(Object value, ListGridRecord record, ListGridField field, String fieldName,
ListGrid grid) {
Boolean access = (Boolean) value;
if (access)
return "With access";
else
return "Without access";
}
});
ListGridRecord lgr1 = new ListGridRecord();
lgr1.setAttribute("id", 1);
lgr1.setAttribute("user", "ewgzx");
lgr1.setAttribute("access", true);
ListGridRecord lgr2 = new ListGridRecord();
lgr2.setAttribute("id", 2);
lgr2.setAttribute("user", "Bgfths");
lgr2.setAttribute("access", false);
ListGridRecord lgr3 = new ListGridRecord();
lgr3.setAttribute("id", 3);
lgr3.setAttribute("user", "utcvs");
lgr3.setAttribute("access", true);
ListGridRecord lgr4 = new ListGridRecord();
lgr4.setAttribute("id", 4);
lgr4.setAttribute("user", "gfdjxc");
lgr4.setAttribute("access", false);
ListGridRecord lgr5 = new ListGridRecord();
lgr5.setAttribute("id", 5);
lgr5.setAttribute("user", "763");
lgr5.setAttribute("access", true);
ListGridRecord lgr6 = new ListGridRecord();
lgr6.setAttribute("id", 6);
lgr6.setAttribute("user", "2");
lgr6.setAttribute("access", false);
ListGridRecord lgr7 = new ListGridRecord();
lgr7.setAttribute("id", 7);
lgr7.setAttribute("user", "35");
lgr7.setAttribute("access", false);
ListGridRecord lgr8 = new ListGridRecord();
lgr8.setAttribute("id", 8);
lgr8.setAttribute("user", "123");
lgr8.setAttribute("access", true);
ListGridRecord lgr9 = new ListGridRecord();
lgr9.setAttribute("id", 9);
lgr9.setAttribute("user", "2342");
lgr9.setAttribute("access", true);
ListGridRecord lgr10 = new ListGridRecord();
lgr10.setAttribute("id", 10);
lgr10.setAttribute("user", "aqwc");
lgr10.setAttribute("access", false);
setRecords(new ListGridRecord[] { lgr1, lgr2, lgr3, lgr4, lgr5, lgr6, lgr7, lgr8, lgr9, lgr10 });
sort("user", SortDirection.ASCENDING);
}
}
I have been having a similar issue. Disclaimer: if the "grouping data" message is not appearing when you group then the following solution may not help.
In my case the sorting of a grouped column was screwed because of the "grouping data" pop up.
Let me clarify.
The "grouping data" pop up appears when trying to group a ListGrid that is displaying more than 50 records.
It appears because the ListGrid, internally, is doing the grouping operation asynchronously to avoid the "script running slowly" message from the browser.
What I did was to set the grouping async threshold to a higher value. The risk of doing this is getting the "script running slowly" browser message, even though this is likely to happen only with IE8/9.
In the end , in the grid constructor, just add (I used 500 as a threshold):
setInitialSort(new SortSpecifier[] {new SortSpecifier("user", SortDirection.ASCENDING)}));
setGroupByField("access");
setGroupByAsyncThreshold(500);
Also set the initial sort and the grouped column as shown above.
PROGRAMMATICALLY, FIRST SORT, THEN GROUP.
Hope this helps.
This is due to sort() being called before rendering the grid, and setRecords() complicates things further.
Initial rendering of the grid happens along with its parents when rootCanvas.draw() is called (in onModuleLoad or similar).
As setRecords() can be used to change data set in the grid anytime, it tries to redraw the grid regardless of whether its initial stage or not.
If in the real scenario, sort is triggered after UI initialization, it should work as given in following code sample.
Remove the sort() call at the end of the constructor.
final Access access = new Access();
Button button = new Button("Sort");
button.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
// toggle sort direction, using two different ways to do it
SortSpecifier sortSpecifier = access.getSortSpecifier("user");
if (sortSpecifier == null || SortDirection.DESCENDING.equals(sortSpecifier.getSortDirection())) {
access.sort("user", SortDirection.ASCENDING);
} else {
access.setSort(new SortSpecifier[]{
new SortSpecifier("user", SortDirection.DESCENDING)
});
}
}
});
Check http://www.smartclient.com/smartgwt/showcase/#grid_multilevel_sort to see how to use listGrid.setInitialSort().
Having setRecords() in the constructor could lead to other initialization issues as well.
Update
To have the grid grouped by and sorted on load, set an initial sort and a group by field as indicated below.
// along with other configuration methods, can not use after grid is drawn
SortSpecifier sortSpecifier = new SortSpecifier("user", SortDirection.ASCENDING);
setInitialSort(new SortSpecifier[]{sortSpecifier});
// use following instead of groupBy(), which is used to group the grid programmatically
// groupBy() causes a redraw
setGroupByField("access");
An overloaded ListGrid.setGroupByField(String... field) method is available to group by multiple fields.

Nested bean : a collection inside an object

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;
}
}

Resources