I have an applications that hosts a TableView. Whenever the user sorts the rows by clicking the header of a particular column, I need to
Save the current order of items,
Do the actual sorting,
Save the new order of items.
I was able to spot this:
this.tableView.sortPolicyProperty().set(t -> {
System.out.println("saving source order");
... // Saving
FXCollections.sort(tableView.getItems(), t.getComparator());
System.out.println("saving target order");
... // Saving
return true;
});
However, this throws ClassCastException pretty often. Is there a better way of saving the item permutations before and after sorting?
You could listen to it using the ListChangeListener the better way :)
tv.getItems().addListener(new ListChangeListener<T>(){
#Override
public void onChanged(javafx.collections.ListChangeListener.Change<
? extends T> c) {
while(c.next()){
if(c.wasPermutated()){
System.out.println("is permuated");
}
}
}
});
Hope it helps.
Related
I am using rxjava2 for the first time on an Android project, and am doing SQL queries on a background thread.
However I am having trouble figuring out the best way to do a simple SQL query, and being able to handle the case where the record may or may not exist. Here is the code I am using:
public Observable<Record> createRecordObservable(int id) {
Callable<Record> callback = new Callable<Record>() {
#Override
public Record call() throws Exception {
// do the actual sql stuff, e.g.
// select * from Record where id = ?
return record;
}
};
return Observable.fromCallable(callback).subscribeOn(Schedulers.computation());
}
This works well when there is a record present. But in the case of a non-existent record matching the id, it treats it like an error. Apparently this is because rxjava2 doesn't allow the Callable to return a null.
Obviously I don't really want this. An error should be only if the database failed or something, whereas a empty result is perfectly valid. I read somewhere that one possible solution is wrapping Record in a Java 8 Optional, but my project is not Java 8, and anyway that solution seems a bit ugly.
This is surely such a common, everyday task that I'm sure there must be a simple and easy solution, but I couldn't find one so far. What is the recommended pattern to use here?
Your use case seems appropriate for the RxJava2 new Observable type Maybe, which emit 1 or 0 items.
Maybe.fromCallable will treat returned null as no items emitted.
You can see this discussion regarding nulls with RxJava2, I guess that there is no many choices but using Optional alike in other cases where you need nulls/empty values.
Thanks to #yosriz, I have it working with Maybe. Since I can't put code in comments, I'll post a complete answer here:
Instead of Observable, use Maybe like this:
public Maybe<Record> lookupRecord(int id) {
Callable<Record> callback = new Callable<Record>() {
#Override
public Record call() throws Exception {
// do the actual sql stuff, e.g.
// select * from Record where id = ?
return record;
}
};
return Maybe.fromCallable(callback).subscribeOn(Schedulers.computation());
}
The good thing is the returned record is allowed to be null. To detect which situation occurred in the subscriber, the code is like this:
lookupRecord(id)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<Record>() {
#Override
public void accept(Record r) {
// record was loaded OK
}
}, new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) {
// there was an error
}
}, new Action() {
#Override
public void run() {
// there was an empty result
}
});
I red a lot about sorting a CellTable. I also went trough the ColumnSorting with AsyncDataProvider. But my CellTable does not sort.
Here is my code:
public class EventTable extends CellTable<Event> {
public EventTable() {
EventsDataProvider dataProvider = new EventsDataProvider(this);
dataProvider.addDataDisplay(this);
SimplePager.Resources pagerResources = GWT.create(SimplePager.Resources.class);
SimplePager pager = new SimplePager(TextLocation.CENTER, pagerResources, false, 5, true);
pager.setDisplay(this);
[...]
TextColumn<Event> nameCol = new TextColumn<Event>() {
#Override
public String getValue(Event event) {
return event.getName();
}
};
nameCol.setSortable(true);
AsyncHandler columnSortHandler = new AsyncHandler(this);
addColumnSortHandler(columnSortHandler);
addColumn(nameCol, "Name");
getColumnSortList().push(endCol);
}
}
public class EventsDataProvider extends AsyncDataProvider<Event> {
private final EventTable eventTable;
public EventsDataProvider(EventTable eventTable) {
this.eventTable = eventTable;
}
#Override
protected void onRangeChanged(HasData<Event> display) {
int start = display.getVisibleRange().getStart();
int length = display.getVisibleRange().getLength();
// check false values
if (start < 0 || length < 0) return;
// check Cache before making a rpc
if (pageCached(start, length)) return;
// get Events async
getEvents(start, length);
}
}
I do now know, if all the methods are need here. If so, I will add them. But in short:
pageCached calls a method in my PageCache Class which holds a map and a list. Before making a rpc call, the cache is checked if the events where already taken and then displayed.
getEvents just makes an rpc call via asynccallback which updates the rowdata via updateRowData() on success.
My Table is displayed fast with currently around 500 entries (could be more, depends on the customer). No missing data and the paging works fine.
I just cannot get the sorting work. As far as I know, AsyncHandler will fire a setVisibleRangeAndClearData() and then an onRangeChanged(). onRangeChanged is never fired. As for the setVisibleRangeAndClearData() I do not know. But the sortindicator (arrow next to the columnname) does change on every click.
I do not want to let the server sort the list. I have my own Comparators. It is enough, if the current visible page of the table is sorted. I do now want to sort the whole list.
Edit:
I changed following code in the EventTable constructor:
public EventTable() {
[...]
addColumnSortHandler(new ColumnSortEvent.AsyncHandler(this) {
public void onColumnSort(ColumnSortEvent event) {
super.onColumnSort(event);
MyTextColumn<Event> myTextColumn;
if (event.getColumn() instanceof MyTextColumn) {
// Compiler Warning here: Safetytype unchecked cast
myTextColumn = (MyTextColumn<Event>) event.getColumn();
MyLogger.log(this.getClass().getName(), "asc " + event.isSortAscending() + " " + myTextColumn.getName(), Level.INFO);
}
List<Event> list = dataProvider.getCurrentEventList();
if (list == null) return;
if (event.isSortAscending()) Collections.sort(list, EventsComparator.getComparator(EventsComparator.NAME_SORT));
else Collections.sort(list, EventsComparator.descending(EventsComparator.getComparator(EventsComparator.NAME_SORT)));
}
});
addColumn(nameCol, "Name");
getColumnSortList().push(endCol);
}
I had to write my own TextColumn to determine the Name of the column. Otherwise how should I know, which column was clicked? The page gets sorted now but I have to click twice on the column. After then, the sorting is done with every click but in the wrong order.
This solution does need polishing and it seems kinda hacky to me. Any better ideas?
The tutorial, that you linked to, states:
This sorting code is here so the example works. In practice, you would
sort on the server.
Async provider is used to display data that is too big to be loaded in a single call. When a user clicks on any column to sort it, there is simply not enough objects on the client side to display "first 20 evens by name" or whatever sorting was applied. You have to go back to your server and request these first 20 events sorted by name in ascending order. And when a user reverses sorting, you have to go to the server again to get first 20 events sorted by name in a descending order, etc.
If you can load all data in a single call, then you can use regular DataProvider, and all sorting can happen on the client side.
EDIT:
The problem in the posted code was in the constructor of EventsDataProvider. Now it calls onRangeChanged, and the app can load a new sorted list of events from the server.
I use a CellTable with EditTextCell
When the EditTextCell fire the FieldUpdater, I want to do a validation and set the EditTextCell to the old value if validation fail. But I cant find how to update the CellTable or the specified row.
Here a piece of code:
titleColumn.setFieldUpdater(new FieldUpdater<QuestionDto, String>() {
#Override
public void update(int index, QuestionDto object, String value) {
if (!isValid(value))
// Here I need to set the EditTextCell to the value in my object
else
// It's valid I do the work
}
});
I was looking for something like : ((EditTextCell)titleColumn.getCell(index)).setValue(object.getTitle());
The other solution is to reset all the CellTable like that:
table.setRowData(dataProvider.getList());
But it's don't work too.
I'm not very knowledgeable of EditTextCell but for other widgets I would catch the ChangeEvent (is it possible to catch it the cell you're using ?) then call event.stopPropagation() if I don't want the user action to have any effect.
Trying to figure out on how can i disable the reordering of table columns in javafx 2?
Here's the solution:
tblView.getColumns().addListener(new ListChangeListener() {
#Override
public void onChanged(Change change) {
change.next();
if(change.wasReplaced()) {
tblView.getColumns().clear();
tblView.getColumns().addAll(column1,column2...);
}
}
});
After much waste of time, I've found the following, very simple, solution:
TableHeaderRow header = (TableHeaderRow) myTableView.lookup("TableHeaderRow");
header.setMouseTransparent(true);
I would like to know how to update a panel when we select a drop down chioce values, that is in onUpdate() method.
My custom panel has AjaxFallbackDefaultDataTable.
Below is Panel and drop down components code. When user selects date, I want to replace my entire Panel. Currently I have commened that target.addComponent code, but I want to have implementation here. Any suggestions?
List<DealHistory> dealHistoryList = ServicesCaller
.getAllDealHistoryRecords();
DealHistoryTablePanel dealHistoryTablePanel = new DealHistoryTablePanel(
"deal_history_table_panel", dealHistoryList);
dealHistoryTablePanel.setOutputMarkupId(true);
add(dealHistoryTablePanel);
IModel<List<? extends String>> dateChoices = new AbstractReadOnlyModel<List<? extends String>>() {
#Override
public List<String> getObject() {
List<String> list = new ArrayList<String>();
list.add("Last 3 months");
list.add("Last 6 months");
return list;
}
};
final DropDownChoice<String> datesDropDown = new DropDownChoice<String>(
"dates", new PropertyModel<String>(this, "selectedDate"),
dateChoices);
datesDropDown.add(new AjaxFormComponentUpdatingBehavior("onchange") {
#Override
protected void onUpdate(AjaxRequestTarget target) {
//target.addComponent(dealHistoryTablePanel);
}
});
add(datesDropDown);
You're definitely on the right track. The basic thing that will make it happen is having the
target.addComponent(dealHistoryTablePanel);
exactly where you have it, in an AjaxFormComponentUpdatingBehavior.
You'll also likely want to change the model in your DealHistoryTablePanel, probably by replacing the list it contains with one gotten by a different call to your service. To say anything more explicit, I'd have to see the code for DealHistoryTablePanel.
An example showing the updating of one DropDownChoice after the selction of one is instructive, though of course the component it updates is different.