GWT CheckboxCell hinders selection in CellTable - gwt

I discovered that if you have a GWT CellTable and add a column that contains a CheckboxCell, the selection via a SingleSelectionModel does not work anymore. This cell type does hinder the row selection.
Following a code sample that demonstrates this behaviour in 2.5.0.rc1.
final CellTable<LicenseDto> licenseTable = new CellTable<LicenseDto>();
final SingleSelectionModel<LicenseDto> selectionModel = new SingleSelectionModel<LicenseDto>();
licenseTable.setSelectionModel(selectionModel);
//--- If I add this column, the selection does work.
Column<LicenseDto, String> workingColumn = new Column<LicenseDto, String>(new TextCell()) {
#Override
public String getValue(LicenseDto object) {
return "Works";
}
};
workingColumn.setFieldUpdater(new FieldUpdater<LicenseDto, String>() {
#Override
public void update(int index, LicenseDto object, String value) {
;
}
});
licenseTable.addColumn(workingColumn);
//--- If I add this column, the selection does NOT work anymore.
Column<LicenseDto, Boolean> notWorkingColumn = new Column<LicenseDto, Boolean>(new CheckboxCell(true, true)) {
#Override
public Boolean getValue(LicenseDto object) {
return object.getEnabled();
}
};
notWorkingColumn.setFieldUpdater(new FieldUpdater<LicenseDto, Boolean>() {
#Override
public void update(int index, LicenseDto object, Boolean value) {
presenter.enableLicense(object, value);
}
});
licenseTable.addColumn(notWorkingColumn);
initWidget(licenseTable);
You can combine multiple cells and add them to the table (e.g. LinkActionCell etc). As long as there is no CheckboxCell, the blue selection with the SingleSelectionModel works like a charm. Does anyone see what I do wrong with this CheckboxCell or is there a bug?

Thank you Thomas! The problem was that I set handlesSelection = true even thought I don't handle anything. Setting it to false solves the problem.
By the way, I add a fieldUpdater to the column to handle a tick or untick of the checkbox:
Column<LicenceDto, Boolean> enableLicenseColumn = new Column<LicenceDto, Boolean>(new CheckboxCell(false, false)) {
#Override
public Boolean getValue(LicenceDto object) {
return object.getEnabled();
}
};
enableLicenseColumn.setFieldUpdater(new FieldUpdater<LicenceDto, Boolean>() {
#Override
public void update(int index, LicenceDto object, Boolean value) {
presenter.enableLicense(object, value);
}
});
The question is answered.

Related

Unable to get GWT ListDataProvider to work with DataGrid

I had my data in a FlexTable, but am migrating to a DataGrid so I can easily add pagination. I get the data via a REST call. I can't seem to get the data to actually display. Here are the relevant snippets:
private DataGrid<SearchResult> resultsGrid = new DataGrid<SearchResult>();
resultsGrid.setAutoHeaderRefreshDisabled(true);
TextColumn<SearchResult> titleColumn = new TextColumn<SearchResult>() {
#Override
public String getValue(SearchResult object) {
return object.getTitle();
}
};
resultsGrid.addColumn(titleColumn, "Document Title");
ButtonCell buttonCell = new ButtonCell();
Column<SearchResult, String> buttonColumn = new Column<SearchResult, String>(buttonCell){
#Override
public String getValue(SearchResult object) {
return "Show";
}
};
resultsGrid.addColumn(buttonColumn, "");
buttonColumn.setFieldUpdater(new FieldUpdater<SearchResult, String>() {
public void update(int index, SearchResult object, String value) {
doPreview(object.title);
}
});
TextColumn<SearchResult> roleColumn = new TextColumn<SearchResult>() {
#Override
public String getValue(SearchResult object) {
return object.getRoles();
}
#Override
public String getCellStyleNames(Context context, SearchResult object) {
if (object.containsCurrentRole)
return "highlight";
else
return null;
}
};
resultsGrid.addColumn(roleColumn, "Associated Roles");
final SingleSelectionModel<SearchResult> selectionModel = new SingleSelectionModel<SearchResult>();
resultsGrid.setSelectionModel(selectionModel);
selectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() {
public void onSelectionChange(SelectionChangeEvent event) {
SearchResult selected = selectionModel.getSelectedObject();
if (selected != null) {
clearWordCloud();
getWordCloud(selected.getTitle());
}
}
});
dataProvider.addDataDisplay(resultsGrid);
// Create a Pager to control the table.
SimplePager.Resources pagerResources = GWT.create(SimplePager.Resources.class);
pager = new SimplePager(TextLocation.CENTER, pagerResources, false, 0, true);
pager.setDisplay(resultsGrid);
resultsGrid.setVisible(true);
resultsGrid.addStyleName("results");
mainPanel.add(resultsGrid);
...
The function that gets called after a search:
private void updateTable(List<SearchResult> results) {
dataProvider.getList().addAll(results);
dataProvider.refresh();
dataProvider.flush();
resultsGrid.setVisible(true);
resultsFlexTable.setVisible(true);
}
At first I was missing the flush and refresh, but adding them had no effect. I'm kind of stumped.
The most likely problem is that your DataGrid has a height of zero. DataGrid implements RequiresResize, which means that its height either has to be set explicitly, or it will acquire its height from a parent widget if this parent widget implements ProvidesResize. FlexTable does not implement ProvidesResize interface.
NB: You don't need flush and refresh - adding data to the DataProvider refreshes the grid.

Create GWT Datagrid with a checkbox column

I have successfully created a datagrid table with two columns. A checkbox col and a string col. When I press a button I want to get the selected strings. Currently when I press the button i get an empty hash set.
Selection Model:
private MultiSelectionModel<String> selectionModel = new MultiSelectionModel<String>(KEY_PROVIDER);
Here is how I create the column
Column<String, Boolean> checkColumn =
new Column<String, Boolean>(new CheckboxCell(true, false)) {
#Override
public Boolean getValue(String object) {
// Get the value from the selection model.
return selectionModel.isSelected(object);
}
};
Here is the method that is called from the button
public Set<String> getSelectedItems(){
Set<String> s = selectionModel.getSelectedSet();
return s;
}
Two pieces are missing. You need to add a FieldUpdater to your checkColumn, and you need to link it to a checkbox manager. Replace T with your Object:
checkColumn.setFieldUpdater(new FieldUpdater<T, Boolean>() {
#Override
public void update(int index, T object, Boolean value) {
getSelectionModel().setSelected(object, value);
dataProvider.refresh();
}
});
setSelectionModel(selectionModel, DefaultSelectionEventManager.<T> createCheckboxManager(0));

Events of multiple cells in single column

I have two buttons(edit + delete) in one column.
ButtonCell functionButtonCell = new ButtonCell() {
#Override
public void render(final Context context, final SafeHtml data, final SafeHtmlBuilder sb) {
sb.appendHtmlConstant("<button type='button' class='gwt-Button' style = 'width:60px;margin:1px;'>Edit</button>");
sb.appendHtmlConstant("<br/>");
sb.appendHtmlConstant("<button type='button' class='gwt-Button' style = 'width:60px;margin:1px;'>Delete</button>");
}
};
functionColumn = new Column<AdminModel, String>(functionButtonCell) {
public String getValue(final AdminModel object) {
return object.getSeq().toString();
}
};
Bind event for this column in Presenter as
.........
view.getFunctionColumn().setFieldUpdater(new FieldUpdater<AdminModel, String>() {
public void update(final int index, final AdminModel object, final String value) {
Window.alert(index + "-" + value);
}
});
After clicked on edit button , alert-box has appeared , but not on delete button. When I clicked on delete button , nothing has appeared. What would be the problem ?
Addition: How can I decide which button was clicked by user (edit or delete) from my presenter ?
I would really appreciate any of your suggestions because I am troubled on it for a long times. Thanks!
ButtonCell filters events on the first child element only: https://gwt.googlesource.com/gwt/+/2.6.1/user/src/com/google/gwt/cell/client/ButtonCell.java This is why you don't get an event when clicking the second button (note: the goal of that code is to make sure you clicked on the button, and not on blank space around the button; see https://gwt.googlesource.com/gwt/+/a0dc88c8be7408be9554f746eb1ec93798183a28)
The easiest way to implement a two-button cell is to use a CompositeCell; it requires that child cells are rendered into sibling elements though (uses <span>s by default, example below overrides the rendering to use <div>s so your buttons stack each on its own line).
new CompositeCell<AdminModel>(Arrays.asList(
// First button
new HasCell<AdminModel, String>() {
#Override public Cell<String> getCell() { return new ButtonCell(); }
#Override public FieldUpdated<AdminModel, String> getFieldUpdater() {
return new FieldUpdater<AdminModel, String>() {
#Override public void update(int index, AdminModel object, String value) {
Window.alert("Edit " + object.getId());
}
};
}
#Override public String getValue(AdminModel o) {
return "Edit";
}
},
// Second button
new HasCell<AdminModel, String>() {
#Override public Cell<String> getCell() { return new ButtonCell(); }
#Override public FieldUpdated<AdminModel, String> getFieldUpdater() {
return new FieldUpdater<AdminModel, String>() {
#Override public void update(int index, AdminModel object, String value) {
Window.alert("Delete " + object.getId());
}
};
}
#Override public String getValue(AdminModel o) {
return "Delete";
}
}) {
#Override protected <X> void render(Cell.Context context, AdminModel value, SafeHtmlBuilder sb, HasCell<String,X> hasCell) {
// use a <div> instead of the default <span>
Cell<X> cell = hasCell.getCell();
sb.appendHtmlConstant("<div>");
cell.render(context, hasCell.getValue(value), sb);
sb.appendHtmlConstant("</div>");
}
};
(note: in your case, because the button's text doesn't depend on the row object, maybe you should rather use an ActionCell; it would better fit "semantically" with what you're doing, but otherwise it's almost the same; with an ActionCell, you'd use HasCell<AdminModel, AdminModel>, ActionCell<AdminModel>, getFieldUpdater would return null, and thegetValueof theHasCellwould just return theAdminModel` argument as-is).
Otherwise, implement your Cell (or AbstractCell) entirely by yourself.
Ideally, a column should have only one type of cell be it ImageCell, ButtonCell etc. Because all this ImageCell and ButtonCell does not provide any in-built events. The events are handled by FieldUpdater itself which does not have differentiators to identify that which ButtonCell is clicked. Ideally on click of that column, the field-updater will be called.
You should rather create your own composite widget which extends HasCell. This composite widget will have two different buttons and those in built methods are called on click of respective button.
public void onModuleLoad() {
CellTable<Person> table = new CellTable<Person>();
List<HasCell<Person, ?>> cells = new LinkedList<HasCell<Person, ?>>();
cells.add(new ActionHasCell("Edit", new Delegate<Person>() {
#Override
public void execute(Person object) {
// EDIT CODE
}
}));
cells.add(new ActionHasCell("Delete", new Delegate<Person>() {
#Override
public void execute(Person object) {
// DELETE CODE
}
}));
CompositeCell<Person> cell = new CompositeCell<Person>(cells);
table.addColumn(new TextColumn<Person>() {
#Override
public String getValue(Person object) {
return object.getName()
}
}, "Name");
// ADD Cells for Age and Address
table.addColumn(new Column<Person, Person>(cell) {
#Override
public Person getValue(Person object) {
return object;
}
}, "Actions");
}
private class ActionHasCell implements HasCell<Person, Person> {
private ActionCell<Person> cell;
public ActionHasCell(String text, Delegate<Person> delegate) {
cell = new ActionCell<Person>(text, delegate);
}
#Override
public Cell<Person> getCell() {
return cell;
}
#Override
public FieldUpdater<Person, Person> getFieldUpdater() {
return null;
}
#Override
public Person getValue(Person object) {
return object;
}
}
Also, see the link below.
[GWT CellTable-Need to have two buttons in last single cell of each row

Gwt Simple pager issues with a column sort handler

I have set up an AsyncDataProvider for my CellTable and added it to a SimplePager. I have hooked up a ListHandler to take care of sorting based on a column.
When I click the header of that column, the data doesn't change but on going to the next/previous page within the pager the data is then sorted. Also before the column is clicked there is no visual indicator on the column that would indicate that it is meant to be sortable.
How can I get the data to update when I click the header of the Column?
Here's my code snippet
service.getHosts(environment, new AsyncCallback<Set<String>>() {
#Override
public void onSuccess(final Set<String> hosts) {
final List<String> hostList = new ArrayList<String>(hosts);
//Populate the table
CellTable<String> hostTable = new CellTable<String>();
TextColumn<String> hostNameColumn = new TextColumn<String>(){
#Override
public String getValue(String string){
return string;
}
};
NumberCell numberCell = new NumberCell();
Column<String, Number> lengthColumn = new Column<String, Number>(numberCell){
#Override
public Number getValue(String string) {
return new Integer(string.length());
}
};
AsyncDataProvider<String> dataProvider = new AsyncDataProvider<String>() {
#Override
protected void onRangeChanged(HasData<String> data) {
int start = data.getVisibleRange().getStart();
int end = start + data.getVisibleRange().getLength();
List<String> subList = hostList.subList(start, end);
updateRowData(start, subList);
}
};
// Hooking up sorting
ListHandler<String> columnSortHandler = new ListHandler<String>(hostList);
columnSortHandler.setComparator(lengthColumn, new Comparator<String>(){
#Override
public int compare(String arg0, String arg1) {
return new Integer(arg0.length()).compareTo(arg1.length());
}
});
hostTable.setPageSize(10);
hostTable.addColumnSortHandler(columnSortHandler);
hostTable.addColumn(hostNameColumn,"Host Name");
lengthColumn.setSortable(true);
hostTable.addColumn(lengthColumn, "Length");
VerticalPanel verticalPanel = new VerticalPanel();
SimplePager pager = new SimplePager();
pager.setDisplay(hostTable);
dataProvider.addDataDisplay(hostTable);
dataProvider.updateRowCount(hosts.size(), true);
verticalPanel.add(hostTable);
verticalPanel.add(pager);
RootPanel.get().add(verticalPanel);
}
#Override
public void onFailure(Throwable throwable) {
Window.alert(throwable.getMessage());
}
});
I'm not sure how to make sure that the list is shared by both the table and the Pager. Before adding the pager I was using
ListDataProvider<String> dataProvider = new ListDataProvider<String>();
ListHandler<String> columnSortHandler = new ListHandler<String>(dataProvider.getList());
The AsyncDataProvider doesn't have the method getList.
To summarize I want the data to be sorted as soon as the column is clicked and not after I move forward/backward with the pager controls.
As per the suggestion I have changed the code for the AsyncDataProvider to
AsyncDataProvider<String> dataProvider = new AsyncDataProvider<String>() {
#Override
protected void onRangeChanged(HasData<String> data) {
int start = data.getVisibleRange().getStart();
int end = start + data.getVisibleRange().getLength();
List<String> subList = hostList.subList(start, end);
// Hooking up sorting
ListHandler<String> columnSortHandler = new ListHandler<String>(hostList);
hostTable.addColumnSortHandler(columnSortHandler);
columnSortHandler.setComparator(lengthColumn, new Comparator<String>(){
#Override
public int compare(String v0, String v1) {
return new Integer(v0.length).compareTo(v1.length);
}
});
updateRowData(start, subList);
}
};
But there is no change in the behavior even after that. Can someone please explain the process. The GWT showcase app seems to have this functionality but how they've done it isn't all that clear.
When using an AsyncDataProvider both pagination and sorting are meant to be done on the server side. You will need an AsyncHandler to go with your AsyncDataProvider:
AsyncHandler columnSortHandler = new AsyncHandler(dataGrid) {
#Override
public void onColumnSort(ColumnSortEvent event) {
#SuppressWarnings("unchecked")
int sortIndex = dataGrid.getColumnIndex((Column<Entry, ?>) event.getColumn());
boolean isAscending = event.isSortAscending();
service.getPage(0, sortIndex, isAscending, new AsyncCallback<List<Entry>>() {
public void onFailure(Throwable caught) {
}
public void onSuccess(List<Entry> result) {
pager.setPage(0);
provider.updateRowData(0, result);
}
});
}
};
dataGrid.addColumnSortHandler(columnSortHandler);
Clicking on a column header will then fire a columnSortEvent. Then you have to get the column clicked. I am overloading my servlet to provide both sorting and pagination, so I pass a -1 for the column index when only pagination is desired.
provider = new AsyncDataProvider<Entry>() {
#Override
protected void onRangeChanged(HasData<Entry> display) {
final int start = display.getVisibleRange().getStart();
service.getPage(start, -1, true, new AsyncCallback<List<Entry>>() {
#Override
public void onFailure(Throwable caught) {
}
#Override
public void onSuccess(List<Entry> result) {
provider.updateRowData(start, result);
}
});
}
};
provider.addDataDisplay(dataGrid);
provider.updateRowCount(0, true);
Then your servlet implementation of getPage performs the sorting and pagination. The whole thing is much easier to follow with separate event handlers.
I think the problem is with the ListHandler initialization. You are passing hostList as a parameter to List Handler and in onRangeChange method you are calling updateRowData with a different list (sublist).
Make sure you use the same list in both the places.
or
Move your ListHander initialization and cellTable.addColumnSortHandler method call to onRangeChange method after updateRowData call.

Handling onClick for a checkbox in a CellTable Header

I am trying to create a CellTable that has a column with some text and a checkbox, which will be used as a select all checkbox (see the drawing below, "cb" is checkbox). Currently I am using an class derived from Header and overriding it's render method to output the text and a checkbox. I am overriding onBrowserEvent() however it is only giving me onChange events, which would work fine except that the checkbox doesn't function correctly. Does anyone have any ideas on this?
+-------+------------+
| col 1 | Select All |
| | cb |
+-------+------------+
| row 1 | cb |
+-------+------------+
The issues I'm having with the checkbox is that when it's not checked, you have to click it twice for the checkmark to appear (at least on Chrome), even though it's "checked" property is true the first time. One click unchecks it correctly.
Here is some code:
Setup the CellTable columns:
/** Setup the table's columns. */
private void setupTableColumns() {
// Add the first column:
TextColumn<MyObject> column1 = new TextColumn<MyObject>() {
#Override
public String getValue(final MyObject object) {
return object.getColumn1Text();
}
};
table.addColumn(macColumn, SafeHtmlUtils.fromSafeConstant("Column1"));
// the checkbox column for selecting the lease
Column<MyObject, Boolean> checkColumn = new Column<MyObject, Boolean>(
new CheckboxCell(true, false)) {
#Override
public Boolean getValue(final MyObject object) {
return selectionModel.isSelected(object);
}
};
SelectAllHeader selectAll = new SelectAllHeader();
selectAll.setSelectAllHandler(new SelectHandler());
table.addColumn(checkColumn, selectAll);
}
My Select All Header:
public static class SelectAllHeader extends Header<Boolean> {
private final String checkboxID = "selectAllCheckbox";
private ISelectAllHandler handler = null;
#Override
public void render(final Context context, final SafeHtmlBuilder sb) {
String html = "<div>Select All<div><input type=\"checkbox\" id=\"" + checkboxID + "\"/>";
sb.appendHtmlConstant(html);
}
private final Boolean allSelected;
public SelectAllHeader() {
super(new CheckboxCell());
allSelected = false;
}
#Override
public Boolean getValue() {
Element checkboxElem = DOM.getElementById(checkboxID);
return checkboxElem.getPropertyBoolean("checked");
}
#Override
public void onBrowserEvent(final Context context, final Element element, final NativeEvent event) {
Event evt = Event.as(event);
int eventType = evt.getTypeInt();
super.onBrowserEvent(context, element, event);
switch (eventType) {
case Event.ONCHANGE:
handler.onSelectAllClicked(getValue());
event.preventDefault();
break;
default:
break;
}
}
public void setSelectAllHandler(final ISelectAllHandler handler) {
this.handler = handler;
}
}
It looks like you're rendering a non-checked checkbox whenever you render the header, which could be wiping out the selection state whenever the celltable re-renders.
Try storing the checked state and rendering the checkbox with the state. It looks like you're half way there with allSelected, you're just not using it.
EDIT Here is a working implementation I've just written for Zanata (see SearchResultsView.java). The HasValue interface is implemented so that value change events can be handled in a standard way. I have not overridden the render method, if you want to do so make sure you use getValue() to determine whether you render a checked or an unchecked checkbox. The selection/de-selection logic is handled in the associated presenter class (see SearchResultsPresenter.java).
private class CheckboxHeader extends Header<Boolean> implements HasValue<Boolean> {
private boolean checked;
private HandlerManager handlerManager;
public CheckboxHeader()
{
//TODO consider custom cell with text
super(new CheckboxCell());
checked = false;
}
// This method is invoked to pass the value to the CheckboxCell's render method
#Override
public Boolean getValue()
{
return checked;
}
#Override
public void onBrowserEvent(Context context, Element elem, NativeEvent nativeEvent)
{
int eventType = Event.as(nativeEvent).getTypeInt();
if (eventType == Event.ONCHANGE)
{
nativeEvent.preventDefault();
//use value setter to easily fire change event to handlers
setValue(!checked, true);
}
}
#Override
public HandlerRegistration addValueChangeHandler(ValueChangeHandler<Boolean> handler)
{
return ensureHandlerManager().addHandler(ValueChangeEvent.getType(), handler);
}
#Override
public void fireEvent(GwtEvent<?> event)
{
ensureHandlerManager().fireEvent(event);
}
#Override
public void setValue(Boolean value)
{
checked = value;
}
#Override
public void setValue(Boolean value, boolean fireEvents)
{
checked = value;
if (fireEvents)
{
ValueChangeEvent.fire(this, value);
}
}
private HandlerManager ensureHandlerManager()
{
if (handlerManager == null)
{
handlerManager = new HandlerManager(this);
}
return handlerManager;
}
}