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.
Related
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.
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
My MainPresenter has a CellTable with a button column. When u hit a button the presenter calls "addToPopupSlot(editPopup, true)". A editPopup appears with several settings u can make there. After pressing the save button on the popup view it sends data to the database which the CellTable in the MainPresenter wants to get.
My problem is: When I click on the save button, the table doesnt get updated. I have to either refresh the page or navigate from another Presenter back to the MainPresenter.
EditPopupPresenter
#Override
protected void onBind() {
super.onBind();
this.username = Cookies.getCookie("domusr");
// hours and minutes displayed in listboxes
for (int i = 0; i < TimeSettings.HOURS_RANGE; i++) {
getView().getBeginHoursLBX().addItem(String.valueOf(i));
getView().getEndHoursLBX().addItem(String.valueOf(i));
getView().getPauseHoursLBX().addItem(String.valueOf(i));
}
for (int i = 0; i < 60; i += TimeSettings.MINUTES_RANGE) {
getView().getBeginMinutesLBX().addItem(String.valueOf(i));
getView().getEndMinutesLBX().addItem(String.valueOf(i));
getView().getPauseMinutesLBX().addItem(String.valueOf(i));
}
getView().getSaveBTN().addClickHandler(new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
DateTimeFormat dtf = DateTimeFormat.getFormat("yyyy-MM-dd");
final String startHours = getView()
.getBeginHoursLBX()
.getValue(
getView().getBeginHoursLBX().getSelectedIndex());
final String startMinutes = getView().getBeginMinutesLBX()
.getValue(
getView().getBeginMinutesLBX()
.getSelectedIndex());
final String endHours = getView().getEndHoursLBX().getValue(
getView().getEndHoursLBX().getSelectedIndex());
final String endMinutes = getView()
.getEndMinutesLBX()
.getValue(
getView().getEndMinutesLBX().getSelectedIndex());
final String pauseHours = getView()
.getPauseHoursLBX()
.getValue(
getView().getPauseHoursLBX().getSelectedIndex());
final String pauseMinutes = getView().getPauseMinutesLBX()
.getValue(
getView().getPauseMinutesLBX()
.getSelectedIndex());
final String projectId = getView().getProjectIdLBL().getText();
final java.sql.Date date = new java.sql.Date(dtf.parse(
getView().getDateLBL().getText()).getTime());
dispatcher.execute(
new InsertTimesIntoDB(Integer.parseInt(startHours),
Integer.parseInt(startMinutes), Integer
.parseInt(endHours), Integer
.parseInt(endMinutes), Integer
.parseInt(pauseHours), Integer
.parseInt(pauseMinutes), Integer
.parseInt(projectId), date, username),
new AsyncCallback<InsertTimesIntoDBResult>() {
#Override
public void onFailure(Throwable caught) {
}
#Override
public void onSuccess(InsertTimesIntoDBResult result) {
}
});
getView().hide();
}
});
}
editColumn in MainPresenter (onBind())
// edit column
Column<Booking, String> editColumn = new Column<Booking, String>(
new ButtonCell()) {
#Override
public String getValue(Booking booking) {
return "edit";
}
};
editColumn.setFieldUpdater(new FieldUpdater<Booking, String>() {
#Override
public void update(int index, Booking object, String value) {
// pop up widget addToSlot call
editPopup.getView().getDateLBL()
.setText(String.valueOf(object.getFullDate()));
editPopup.getView().getProjectIdLBL()
.setText(String.valueOf(1234567));
editPopup.getView().getBeginHoursLBX()
.setItemSelected(object.getStartHours(), true);
editPopup
.getView()
.getBeginMinutesLBX()
.setItemText(
minutesRange.getIndex(object
.getEndMinutes()),
String.valueOf(object.getStartMinutes()));
editPopup.getView().getEndHoursLBX()
.setItemSelected(object.getEndHours(), true);
editPopup
.getView()
.getEndMinutesLBX()
.setItemText(
minutesRange.getIndex(object
.getEndMinutes()),
String.valueOf(object.getEndMinutes()));
editPopup.getView().getPauseHoursLBX()
.setItemSelected(object.getPauseHours(), true);
editPopup
.getView()
.getPauseMinutesLBX()
.setItemText(
minutesRange.getIndex(object
.getEndMinutes()),
String.valueOf(object.getPauseMinutes()));
addToPopupSlot(editPopup, true);
}
});
getView().getTimeTable().addColumn(editColumn);
I think you have some solutions here. If I were you I would do next steps:
Create a listener of events in the MainPresenter.
When you finished
update your DB (after pressing save in your popup); I´d fire an
event.
When the MainPresenter receives the event, you go to the DB
and fetch the data (filtering it using getVisibleRange()).
Refresh the CellTable using setRowData(...) method (passing correctly the arguments)
Other option is create a ListDataProvider associate with the CellTable, and call refresh on it.
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.
I have this button cell in my CellTable
ButtonCell reListCell = new ButtonCell();
reListColumn = new Column<EmployerJobs, String>(reListCell) {
#Override
public String getValue(EmployerJobs object) {
return "ReList";
}
};
ctJobs.addColumn(reListColumn,
EmployerDashBoardConstants.EMPTYHEADERCOLUMN);
but i only want this cell to be appear if the below condition pass
public void getDateDiff(final EmployerJobs object) {
rpcService.getDateDiff(object.getJobValidDate(), new AsyncCallback<Boolean>() {
public void onFailure(Throwable caught) {
}
public void onSuccess(Boolean jobExpired) {
if(jobExpired) {
// HERE I WANT TO SHOW MY RELISTCELL, means if the job is expired only then
// there will be a button showing relist would be appear in that row ,for
// the jobs which are not expired NO button should appear..
}
}
});
}
how can i achieve this?
thanks
I agree with DTing.
Quering the backend for each cell/row is not really efficient.
I would rather put the info (jobExpired) into your EmployerJobs class and transfer the info when you request the list of your EmployerJobs to be displayed in your CellTable.
You can update the list periodically to account for changes (see the expenses sample on how to do that).
But to your initial question (hiding the cell). There are two solutions:
Use an ActionCell and override the render method.
ActionCell:
ActionCell<EmployerJobs> reListCell = new ActionCell<EmployerJobs>("ReList",
new ActionCell.Delegate<EmployerJobs>() {
#Override
public void execute(EmployerJobs object) {
// code to be executed
}
})
{
#Override
public void render(Cell.Context context,EmployerJobs value,SafeHtmlBuilder sb) {
if (value.isJobExpired()) // isJobExpired returns the field jobExpired.
super.render(context,value,sb);
}
};
reListColumn = new Column<EmployerJobs, EmployerJobs>(reListCell) {
#Override
public String getValue(EmployerJobs object) {
return object;
}
};
ctJobs.addColumn(reListColumn,
EmployerDashBoardConstants.EMPTYHEADERCOLUMN);
Use a ButtonCell and override the render method of your Column.
ButtonCell:
ButtonCell reListCell = new ButtonCell();
reListColumn = new Column<EmployerJobs, String>(reListCell) {
#Override
public String getValue(EmployerJobs object) {
return "ReList";
}
#Override
public void render(Cell.Context context,EmployerJobs object,SafeHtmlBuilder sb) {
if (value.isJobExpired()) // isJobExpired returns the field jobExpired.
super.render(context,value,sb);
}
};
ctJobs.addColumn(reListColumn,
EmployerDashBoardConstants.EMPTYHEADERCOLUMN);
Just tried Umit solution #2 ButtonCell. It works!
To link an specific action to the button, reListColumn.setFieldUpdater(new FieldUpdater....
would be needed
I tried ButtonCell solution too. But if you click in a cell who as no button then an error on client side occur:
com.google.gwt.core.client.JavaScriptException: (TypeError) #com.google.gwt.core.client.impl.Impl::apply(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)([JavaScript object(445), JavaScript object(240), JavaScript object(637)]): parent is null
So I added this to override the Event and avoid Event if I want:
#Override
public void onBrowserEvent(com.google.gwt.cell.client.Cell.Context context,
Element parent, YourObject object, NativeEvent event) {
if (object.isCompleted())
super.onBrowserEvent( context, parent, object, event);
}
I don't know if it's the better way to do it but it works.