Vaadin Grid always selects last row - select

I need help debugging the follow issue:
When I click on a Vaadin Grid with 3 rows, it ignores the click on any but the last row and always selects the 3rd row.
Code is as follows:
// declare Grid objects
public final Grid<SoapKPIContainer> soapKPIOverviewGridDisplay;
..
public SoapKPIOverviewView(SoapKPIRepository soapKPIRepository, Navigator navigator,
BpspSoapCheckCommunications bpspSoapCheckCommunications,
UIMessageByLocaleService messageByLocaleService) {
..
this.soapKPIOverviewGridDisplay = new Grid<>(SoapKPIContainer.class);
this.soapKPIOverviewGridDisplay.setColumns();
Grid.Column<SoapKPIContainer, ?> lastAlarmStatusIconColumn = this.soapKPIOverviewGridDisplay.addColumn("lastAlarmStatusIcon", new ImageRenderer<>());
lastAlarmStatusIconColumn.setSortable(false);
lastAlarmStatusIconColumn.setResizable(false);
lastAlarmStatusIconColumn.setWidth(80.0f);
lastAlarmStatusIconColumn.setCaption(messageByLocaleService.getMessage("label.lastalarmstatus"));
Grid.Column<SoapKPIContainer, ?> activationIconColumn = this.soapKPIOverviewGridDisplay.addColumn("activationIcon", new ImageRenderer<>());
activationIconColumn.setSortable(false);
activationIconColumn.setResizable(false);
this.soapKPIOverviewGridDisplay.getDefaultHeaderRow().getCell("lastAlarmStatusIcon").setHtml(messageByLocaleService.getMessage("label.lastalarmstatus"));
this.soapKPIOverviewGridDisplay.getDefaultHeaderRow().getCell("activationIcon").setHtml(messageByLocaleService.getMessage("label.activationicon"));
this.soapKPIOverviewGridDisplay.addColumn(SoapKPIContainer::getKpiName).
setCaption(messageByLocaleService.getMessage("header.kpiName"));
..
this.setSizeFull();
this.soapKPIOverviewGridDisplay.setSizeFull();
this.soapKPIOverviewGridDisplay.setHeight(400, Unit.PIXELS);
this.soapKPIService = new SoapKPIService(soapKPIRepository);
this.soapKPIOverviewGridDisplay.setItems( soapKPIService.toContainer( soapKPIService.findAllSoapKPI() ) );
..
// adding Listener for the Grid to enable for the user to select single KPIs
soapKPIOverviewGridDisplay.setSelectionMode(SelectionMode.SINGLE);
soapKPIOverviewGridDisplay.asSingleSelect().addValueChangeListener(e -> {
log.debug("asSingleSelect().addValueChangeListener " + e.getValue());
if ( e.getValue() != null) {
log.debug("asSingleSelect().addValueChangeListener findSoapKPIById #" + e.getValue().getKpiId());
testKPIDefView.setEnabled(true);
soapKPI = soapKPIService.findSoapKPIById( e.getValue().getKpiId() );
changeEnabled.setVisible(true);
if( soapKPI.getEnabled() == 1)
changeEnabled.setCaption(messageByLocaleService.getMessage("button.disable"));
else
changeEnabled.setCaption(messageByLocaleService.getMessage("button.enable"));
}
else {
testKPIDefView.setEnabled(false);
changeEnabled.setVisible(false);
soapKPI = null;
}
});
..
soapKPIOverviewLayout.addComponent(soapKPIOverviewGridDisplay);

I'm still not sure what line in the code cause it to break, but I found that overriding the equals method of the row object makes the problem go away:
#Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof SoapKPIContainer)) {
return false;
}
SoapKPIContainer container = (SoapKPIContainer) o;
return this.getKpiId() == container.getKpiId();
}

Related

Eclipse scout cache form page

I want to cache state in form page when switching from page to page.
So : I have 3 page with forms and I want to data stays in forms when I switch from one to another.
I found this : https://wiki.eclipse.org/Scout/Concepts/Page_Detail_Form
#Override
protected void execPageActivated() throws ProcessingException {
if (getDetailForm() == null) {
PersonDetailForm form = new PersonDetailForm();
form.setPersonNr(getPersonNr());
setDetailForm(form);
form.startView();
}
}
and says that setDetailForm() caches datas
As already said, attaching a detail form to a page means the detail form will automatically be
hidden when the page gets deactivated and shown when the page gets activated (see
PageDetailFormChanged on Desktop). So the detail form actually gets cached and does not need to
be started more than once per page. This requires that the form does not get closed.
But this don't work for me.
My code is
#Override
protected void execPageActivated() throws ProcessingException {
// / Create and open form
if (getDetailForm() == null) {
MarginCalculationForm form = new MarginCalculationForm();
form.startModify();
setDetailForm(form);
}
super.execPageActivated();
}
but it stays on last page.
For example :
If I have page A,B,C and I open page A it create it self and set it to detailForm(). If I then open page B it is OK too. But if I then click on page A again it check if the detailForm() is not null (and it is not) so it stays on page B (insted of going on page A)
EDIT :
I figure that getDetailForm() is returning the right form but apparently super.execPageActivated() don't work.
I found out what was wrong.
Problem is in DefaultPageChangeStrategy class in Scout. Method pageChanged() is like this :
#Override
public void pageChanged(IOutline outline, IPage deselectedPage, IPage selectedPage) {
if (outline == null) {
return;
}
outline.clearContextPage();
IForm detailForm = null;
ITable detailTable = null;
ISearchForm searchForm = null;
// new active page
outline.makeActivePageToContextPage();
IPage activePage = outline.getActivePage();
if (activePage != null) {
try {
activePage.ensureChildrenLoaded();
}
catch (ProcessingException e1) {
SERVICES.getService(IExceptionHandlerService.class).handleException(e1);
}
if (activePage instanceof IPageWithTable) {
IPageWithTable tablePage = (IPageWithTable) activePage;
detailForm = activePage.getDetailForm();
if (activePage.isTableVisible()) {
detailTable = tablePage.getTable();
}
if (tablePage.isSearchActive()) {
searchForm = tablePage.getSearchFormInternal();
}
}
else if (activePage instanceof IPageWithNodes) {
IPageWithNodes nodePage = (IPageWithNodes) activePage;
detailForm = activePage.getDetailForm();
if (activePage.isTableVisible()) {
detailTable = nodePage.getInternalTable();
}
}
}
// remove first
if (detailForm == null) {
outline.setDetailForm(null);
}
if (detailTable == null) {
outline.setDetailTable(null);
}
if (searchForm == null) {
outline.setSearchForm(null);
}
// add new
if (detailForm != null) {
outline.setDetailForm(detailForm);
}
if (detailTable != null) {
outline.setDetailTable(detailTable);
}
if (searchForm != null) {
outline.setSearchForm(searchForm);
}
}
}
And if it is activePage a AbstractPage (and not AbstractPageWithTable AbstractPageWithNode), detailForm is always null and this break behavior.
So solution is to change AbstractPage with AbstractPageWithNode and add line
setTableVisible(false);
This line is needed because if it's not the firs time launch page will not be presented. (nodePage.getInternalTable() is not null but it is empty so :
if (detailTable != null) {
outline.setDetailTable(detailTable);
}
will present empty page.)

Header of Sortable CellTable Column not Responsive

I am using GWT 2.5 to create a CellTable with a sortable date column.
My code is as follows:
CellTable<Activity> table = new CellTable<Activity>();
table.setRowStyles(new RowStyles<Activity>() {
#Override
public String getStyleNames(Activity row, int rowIndex) {
return TABLE_ROW_STYLE_NAME;
}
});
// create date column
TextColumn<Activity> dateColumn = new TextColumn<Activity>() {
#Override
public String getValue(Activity a) {
return dateFormat.format(a.getDate());
}
};
dateColumn.setSortable(true);
dateColumn.setDefaultSortAscending(false);
// add column to table
table.addColumn(dateColumn, myConstants.dateColumnHeader());
// attach provider to table
activityProvider.addDataDisplay(table);
// create sort handler
ListHandler<Activity> sortHandler = new ListHandler<Activity>(activityProvider.getList());
sortHandler.setComparator(dateColumn, new Comparator<Activity>() {
#Override
public int compare(Activity a1, Activity a2) {
if (a1 == a2) {
return 0;
}
// compare the date columns
if (a1 != null) {
if (a2 != null) {
long a1Val = a1.getDate().getTime();
long a2Val = a2.getDate().getTime();
if (a1Val == a2Val) {
return 0;
}
else if (a1Val > a2Val) {
return 1;
}
else {
return -1;
}
}
else {
return 1;
}
}
return -1;
}
});
// add sort handler to table
table.addColumnSortHandler(sortHandler);
// add date column to table's sort list
table.getColumnSortList().push(dateColumn);
table.setWidth("100%");
getView().getActivityPanel().add(table);
With this code, the data is displayed in the table and the sorting arrow on the column appears. However, nothing happens when I click on the sortable column's header. The sorting order doesn't change, rows are not rearranged.
Can anyone spot the problem here? This code is almost the same as what they have in Google's own example.
This is what I use:
dateColumn.setSortable(true);
sortHandler.setComparator(dateColumn, new Comparator<ObjectPobject>() {
public int compare(ObjectPobject o1, ObjectPobject o2) {
return o1.getDate().compareTo(o2.getDate());
}
});
It should be
a1.getDate().getTime().compareTo(a2.getDate().getTime())
or
a1.getDate().after(a2.getDate())
This happens because GWT uses JavaScript comparisons, and compareTo does not work for dates.

GWT SelectionModel is returning old selection

I have a cell table with an async data provider. If I update the data via the data provider the table renders the new data correctly but the selection model still holds onto and returns old objects.
Any ideas how to refresh the selection model?
I think you should make your SelectionModel work with different instance of the same "logical" object using the appropriate ProvidesKey. For instance, you could use ProvidesKey that calls getId on the object, so that two objects with the same such ID would be considered equal; so even if the SelectionModel holds onto the old object, it can still answer "yes, it's selected" when you give it the new object.
FYI, this is exactly what the EntityProxyKeyProvider does (using the stableId of the proxy). And the SimpleKeyProvider, used by default when you don't specify one, uses the object itself as its key.
I came across the same issue. Currently I have this as single selection model.
SelectedRow = store it when you select it.
Then when data is reloaded you can clear it by
celltable.getSelectionModel().setSelected(SelectedRow, false);
I guess it is too late for you but hope it helps someone else.
Here is my manual method for refreshing the SelectionModel. This allows you to use the selectedSet() when needed and it will actually contain the current data, rather than the old data - including the removal of deleted rows and updated fields!
I have included bits & pieces of a class extending DataGrid. This should have all the logic at least to solve your problems.
When a row is selected, call saveSelectionKeys().
When the grid data is altered call refeshSelectedSet().
If you know the key type, you can replace the isSameKey() method with something easier to deal with. This class uses generics, so this method attempts to figure out the object conversion itself.
.
public abstract class AsyncDataGrid<T> extends DataGrid<T> {
...
private MultiSelectionModel<T> selectionModel_;
private ListDataProvider<T> dataProvider_;
private List<T> dataList_;
private Set<Object> priorSelectionKeySet_;
private boolean canCompareKeys_;
...
public AsyncDataGrid( final ProvidesKey<T> keyProvider ){
super( keyProvider );
...
dataProvider_ = new ListDataProvider<T>();
dataList_ = dataProvider_.getList();
canCompareKeys_ = true;
...
}
private void saveSelectionKeys(){
priorSelectionKeySet_ = new HashSet<Object>();
Set<T> selectedSet = selectionModel_.getSelectedSet();
for( Iterator<T> it = selectedSet.iterator(); it.hasNext(); ) {
priorSelectionKeySet_.add( super.getValueKey( it.next() ) );
}
}
private void refeshSelectedSet(){
selectionModel_.clear();
if( priorSelectionKeySet_ != null ){
if( !canCompareKeys_ ) return;
for( Iterator<Object> keyIt = priorSelectionKeySet_.iterator(); keyIt.hasNext(); ) {
Object priorKey = keyIt.next();
for( Iterator<T> it = dataList_.iterator(); it.hasNext(); ) {
T row = it.next();
Object rowKey = super.getValueKey( row );
if( isSameKey( rowKey, priorKey ) ) selectionModel_.setSelected( row, true );
}
}
}
}
private boolean isSameRowKey( final T row1, final T row2 ) {
if( (row1 == null) || (row2 == null) ) return false;
Object key1 = super.getValueKey( row1 );
Object key2 = super.getValueKey( row2 );
return isSameKey( key1, key2 );
}
private boolean isSameKey( final Object key1, final Object key2 ){
if( (key1 == null) || (key1 == null) ) return false;
if( key1 instanceof Integer ){
return ( ((Integer) key1) - ((Integer) key2) == 0 );
}
else if( key1 instanceof Long ){
return ( ((Long) key1) - ((Long) key2) == 0 );
}
else if( key1 instanceof String ){
return ( ((String) key1).equals( ((String) key2) ) );
}
canCompareKeys_ = false;
return false;
}
}
I fixed my particular issue by using the following code to return the visible selection. It uses the selection model to determine what is selected and combines this with what is visible. The objects themselves are returned from the CellTable data which is always upto date if the data has ever been changed via an async provider (the selection model data maybe stale but the keys will be correct)
public Set<T> getVisibleSelection() {
/*
* 1) the selection model contains selection that can span multiple pages -
* we want to return just the visible selection
* 2) return the object from the cellTable and NOT the selection - the
* selection may have old, stale, objects if the data has been updated
* since the selection was made
*/
Set<Object> selectedSet = getKeys(selectionModel.getSelectedSet());
List<T> visibleSet = cellTable.getVisibleItems();
Set<T> visibleSelectionSet = new HashSet<T>();
for (T visible : visibleSet) {
if (selectedSet.contains(KEY_PROVIDER.getKey(visible))) {
visibleSelectionSet.add(visible);
}
}
return visibleSelectionSet;
}
public static Set<Object> getKeys(Collection<T> objects) {
Set<Object> ids = new HashSet<Object>();
for (T object : objects) {
ids.add(KEY_PROVIDER.getKey(object));
}
return ids;
}

How to apply like search on GWT cell table?

I am using GWT 2.3.I which I am using GWT cell table.
Here below is the code for my cell table:
public class FormGrid extends SuperGrid {
List<Form> formList;
#Override
public void setColumns(CellTable table) {
TextColumn<Form> nameColumn = new TextColumn<Form>() {
#Override
public String getValue(Form object) {
return object.getName();
}
};
table.addColumn(nameColumn, "Name");
}
#Override
public void setData() {
if (formList != null && formList.size() > 0) {
AsyncDataProvider<Form> provider = new AsyncDataProvider<Form>() {
#Override
protected void onRangeChanged(HasData<Form> display) {
int start = display.getVisibleRange().getStart();
int end = start + display.getVisibleRange().getLength();
end = end >= formList.size() ? formList.size() : end;
List<Form> sub = formList.subList(start, end);
updateRowData(start, sub);
}
};
provider.addDataDisplay(getTable());
provider.updateRowCount(formList.size(), true);
}
}
public List<Form> getFormList() {
return formList;
}
public void setFormList(List<Form> formList) {
this.formList = formList;
}
}
In this my set column and set data will be called fro super class flow.This cell table is working fine.
Now I want to put a filter type facility (like search) in this cell table.It should be like, there is a texbox above the cell table and what ever written in that text box, it should fire a like query to all form name for that text box value.
for example I have 1000 form in the grid.Now if user writes 'app' in some filter textbox above the cell table the all the form which have 'app' in there name will be filtered and grid has only those forms only.
This is the first case:
Another case is I am only render one column in grid name.I have two more properties in form (description,tag).But I am not rendering them.now for filter if user writes 'app' in filter box then it should make a query to all three (name, description, and tag) and should return if 'app' matched to any of three.
I am not getting how to apply filter in cell table.
Please help me out.Thanks in advance.
You can find an implementation in the expenses sample.
Here is a short summary of the steps
1.) Create a Textbox and a SearchButton.
2.) add a clickHandler to the SearchButton (You can also add KeyUpHandler to the Textbox alternatively)
searchButton.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
search();
}
});
3.) In the search function retrieve the searchString and store it.
private void search() {
searchString = searchBox.getText();
setData();
}
4.) modify your setdata() function to take searchString into account
#Override
public void setData() {
if (formList != null && formList.size() > 0) {
AsyncDataProvider<Form> provider = new AsyncDataProvider<Form>() {
#Override
protected void onRangeChanged(HasData<Form> display) {
int start = display.getVisibleRange().getStart();
int end = start + display.getVisibleRange().getLength();
//new function if searchString is specified take into account
List<Form> sub = getSubList(start,end);
end = end >= sub.size() ? sub.size() : end;
updateRowData(sub.subList(start, end);, sub);
}
};
provider.addDataDisplay(getTable());
provider.updateRowCount(formList.size(), true);
}
}
private List<Form> getSubList(int start, int end) {
List<Form> filtered_list = null;
if (searchString != null) {
filtered_list= new ArrayList<Form>();
for (Form form : formList) {
if (form.getName().equals(searchString) || form.getTag().equals(searchString) || form.getDescription().equals(searchString))
filtered_list.add(form);
}
}
else
filtered_list = formList;
return filtered_list;
}
can propose another solution what can be used quite easy multiple times.
Idea is to create custom provider for your celltable.
GWT celltable filtering
Video in this post shows it in action.
Here is the part of code of custom list data provider which u have to implement.
#Override
protected void updateRowData(HasData display, int start, List values) {
if (!hasFilter() || filter == null) { // we don't need to filter, so call base class
super.updateRowData(display, start, values);
} else {
int end = start + values.size();
Range range = display.getVisibleRange();
int curStart = range.getStart();
int curLength = range.getLength();
int curEnd = curStart + curLength;
if (start == curStart || (curStart < end && curEnd > start)) {
int realStart = curStart < start ? start : curStart;
int realEnd = curEnd > end ? end : curEnd;
int realLength = realEnd - realStart;
List<t> resulted = new ArrayList<t>(realLength);
for (int i = realStart - start; i < realStart - start + realLength; i++) {
if (filter.isValid((T) values.get(i), getFilter())) {
resulted.add((T) values.get(i));
}
}
display.setRowData(realStart, resulted);
display.setRowCount(resulted.size());
}
}
}

GWT: Select a TreeItem with right click

I'm capturing a right click event to show a context menu. What I haven't been able to figure out, is how to make the right click actually select the TreeItem, prior to showing of context menu.
All help is appreciated.
private Tree tree = new Tree() {
#Override
public void onBrowserEvent(Event event) {
if (event.getTypeInt() == Event.ONCONTEXTMENU) {
DOM.eventPreventDefault(event);
showContextMenu(event);
}
super.onBrowserEvent(event);
}
#Override
protected void setElement(Element elem) {
super.setElement(elem);
sinkEvents(Event.ONCONTEXTMENU);
}
};
ONMOUSEDOWN event gets fired before ONCONTEXTMENU. Have you tried to listen for onMouseDown events, and set the selected item? Something along these lines:
#Override
public void onBrowserEvent(Event event) {
switch (DOM.eventGetType(event)) {
case Event.ONMOUSEDOWN:
if (DOM.eventGetButton(event) == Event.BUTTON_RIGHT) {
TreeItem selectedItem = findSelectedItem(event);
if (selectedItem != null) {
selectedItem.setSelected(true);
}
} else {
super.onBrowserEvent(event);
}
break;
case Event.ONCONTEXTMENU:
showContextMenu(event);
break;
default:
super.onBrowserEvent(event);
break;
}
and findSelectedItem traverses the tree looking for the selected item:
TreeItem findSelectedItem(Event e) {
return findSelectedItemRecursive(event.getClientX(), event.getClientY());
}
TreeItem findSelectedTreeItemRecursive(TreeItem root, int x, int y) {
if (null == root) {
int count = getItemCount();
for (int i = 0; i < count; i++) {
TreeItem selected = findSelectedTreeItemRecursive(getItem(i), x, y);
if (selected != null) {
return selected;
}
}
return null;
}
int count = item.getChildCount();
for (int i = 0; i < count; i++) {
TreeItem selected = findSelectedTreeItem(item.getChild(i), x, y);
if (selected != null) {
return selected;
}
}
if (x >= item.getAbsoluteLeft()
&& x <= item.getAbsoluteLeft() + item.getOffsetWidth()
&& y >= item.getAbsoluteTop()
&& y <= item.getAbsoluteTop() + item.getOffsetHeight()) {
return item;
}
return null;
}
You can use dedicated overloaded TreeItem :
public class MyTreeItem extends TreeItem implements ContextMenuHandler {
public SBTreeItem(SBItemTree tree, String name) {
super();
Label w = new Label(name);
w.addDomHandler(this, ContextMenuEvent.getType());
setWidget(w);
}
public void onContextMenu(ContextMenuEvent event) {
Window.alert(getSBItem().getName());
event.getNativeEvent().stopPropagation();
}
}
I'd just like to add a couple of links leading to issues about this:
http://code.google.com/p/google-web-toolkit/issues/detail?id=4529&q=right%20click%20selection
http://code.google.com/p/google-web-toolkit/issues/detail?id=4604&q=right%20click%20selection
I know this is an old question, but hopefully here's an answer that will save time for the masses hitting this page from a Google search. IMO, the best way is to use Google's own internal tree searching code -- it's a solution that scales very well with the number of elements in the tree. I
am using GWT 2.5.1.
private void initTree() {
tree = new Tree() {
#Override
public void onBrowserEvent(Event event) {
/*
* If the event is a context menu event, we want the tree item
* to also be selected.
*
* This logic must occur before the call to the superclass
* method so the selection is updated before the context menu
* logic executes. This is useful when we want to make items in
* the context menu invisible/disabled based on the selection.
*/
if (DOM.eventGetType(event) == Event.ONCONTEXTMENU) {
if (getItemCount() > 0) {
// In my use case there is only 1 top-level tree item
TreeItem root = getItem(0);
// Taken from com.google.gwt.user.client.ui.Tree.elementClicked(Element):
ArrayList<Element> chain = new ArrayList<Element>();
collectElementChain(chain, getElement(), DOM.eventGetTarget(event));
TreeItem selection = findItemByChain(chain, 0, root);
/*
* For some reason SelectionEvent will only fire if
* selection is non-null; I am firing the selection
* event manually because I want to know when there has
* been a deselection of an item in the tree.
*/
if (selection != null) {
this.setSelectedItem(selection);
} else {
SelectionEvent.fire(this, null);
}
}
}
super.onBrowserEvent(event);
}
};
tree.setAnimationEnabled(true);
}
//// BEGIN code copied from com.google.gwt.user.client.ui.Tree:
/**
* Collects parents going up the element tree, terminated at the tree root.
*/
private void collectElementChain(ArrayList<Element> chain, Element hRoot,
Element hElem) {
if ((hElem == null) || (hElem == hRoot)) {
return;
}
collectElementChain(chain, hRoot, DOM.getParent(hElem));
chain.add(hElem);
}
private TreeItem findItemByChain(ArrayList<Element> chain, int idx,
TreeItem root) {
if (idx == chain.size()) {
return root;
}
Element hCurElem = chain.get(idx);
for (int i = 0, n = root.getChildCount(); i < n; ++i) {
TreeItem child = root.getChild(i);
if (child.getElement() == hCurElem) {
TreeItem retItem = findItemByChain(chain, idx + 1,
root.getChild(i));
if (retItem == null) {
return child;
}
return retItem;
}
}
return findItemByChain(chain, idx + 1, root);
}
//// END