I'm looking for right way to create a table that could upload new rows dynamically. As a DataSource I'm using SQLContainer with TableQuery. There could be much of data which should be uploaded quickly.
Anyway, my current realization is following:
Table messagesList = new Table();
...............................
messagesList.setCacheRate(0.1d);
messagesList.setContainerDataSource(messagesContainer);
messagesList.setSelectable(true);
messagesList.setImmediate(true);
messagesList.setSizeFull();
new InitializerThread().start();
...............................
Data is uploading using refreshRowCache method and Vaadin Push tecknology in another thread:
class InitializerThread extends Thread {
#Override
public void run() {
while (!Thread.interrupted()) {
try {
Thread.sleep(refreshMessagesPeriod);
} catch (InterruptedException e) {
}
access(new Runnable() {
#Override
public void run() {
if (messagesList != null && !messagesList.getItemIds().isEmpty()) {
messagesList.refreshRowCache();
messagesList.focus();
}
}
});
}
}
}
This approach has many disadvantages:
1. If there are many rows in the table, it is very inefficient way to refresh all row's cache in the table everytime.
2. Scroll bar jumps to the top of page in the table when the row's cache is refreshing. I didn't find the way to save the scroll's position and set the previous scroll position after refresh.
3. If I select some text in a cell of the table, the selection dissapears after row's cache refresh.
I hope that there is a lightweight and more nice technique to fill new data into Table dynamically.
I use Vaadin 7.1.15 and it is allowed to change version of Vaadin type of table (instead com.vaadin.ui.Table) if necessary.
I found a better solution - control container content manually. Using IndexedContainer as a data source instead TableQuery and periodically checking a new data using sql queries. Vaadin Push helps me to visualise new rows. To prevent a scroll bar jump (which is the result of calling refreshRowCache) I call the private method Table.setCurrentPageFirstItemId(int, boolean) with following parameters: a new row ID, false (do not call refreshRowCache).
Related
Use Case 1 is answered below, Use Case 2 has been moved to a separate question (Vaadin Flow: Returning to a view, the view should not reload data from the backend)
I'd like to use a Vaadin Flow (v14 LTS/v19) grid component backed by a lazy DataProvider which does not automatically fetch data from the backend when the grid is shown.
There are at least two use cases:
showing grid data does not make sense unless the user provided filter parameters
returning to a #PreserveOnRefresh tagged view should not replace the shown data with current data. (further elaborated in update)
Being pretty new to Vaadin 14+, I could not figure out how to achieve this. Every time my GridView is displayed, the count and fetch callbacks of DataProvider are queried. The call originates from the DataCommunicator of the grid.
So for Use Case 1: How to stop the DataProvider from fetching data as long as it does not make sense?
And for Use Case 2: How to prevent overwriting the grid state when adding a grid to the UI for the second time?
Thanks a lot!
StackTrace to my fetch callback (Vaadin Flow 14):
at org.vaadin.example.GridView.fetch(GridView.java:46)
at org.vaadin.example.GridView.lambda$new$c4b2c115$1(GridView.java:23)
at com.vaadin.flow.data.provider.CallbackDataProvider.fetchFromBackEnd(CallbackDataProvider.java:137)
at com.vaadin.flow.data.provider.AbstractBackEndDataProvider.fetch(AbstractBackEndDataProvider.java:61)
at com.vaadin.flow.data.provider.DataCommunicator.fetchFromProvider(DataCommunicator.java:362)
at com.vaadin.flow.data.provider.DataCommunicator.activate(DataCommunicator.java:647)
at com.vaadin.flow.data.provider.DataCommunicator.collectKeysToFlush(DataCommunicator.java:589)
at com.vaadin.flow.data.provider.DataCommunicator.flush(DataCommunicator.java:461)
at com.vaadin.flow.data.provider.DataCommunicator.lambda$requestFlush$2f364bb9$1(DataCommunicator.java:425)
at com.vaadin.flow.internal.StateTree.lambda$runExecutionsBeforeClientResponse$2(StateTree.java:390)
at [java.util.stream] omitted
at com.vaadin.flow.internal.StateTree.runExecutionsBeforeClientResponse(StateTree.java:387)
at com.vaadin.flow.server.communication.UidlWriter.encodeChanges(UidlWriter.java:411)
at com.vaadin.flow.server.communication.UidlWriter.createUidl(UidlWriter.java:187)
at com.vaadin.flow.server.communication.UidlRequestHandler.writeUidl(UidlRequestHandler.java:122)
at com.vaadin.flow.server.communication.UidlRequestHandler.synchronizedHandleRequest(UidlRequestHandler.java:91)
at com.vaadin.flow.server.SynchronizedRequestHandler.handleRequest(SynchronizedRequestHandler.java:40)
at com.vaadin.flow.server.VaadinService.handleRequest(VaadinService.java:1547)
at com.vaadin.flow.server.VaadinServlet.service(VaadinServlet.java:247)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
update 20210430
Here's the code of my GridView which also fakes the backend DataProvider:
#Route(value = "grid", layout = MainView.class)
public class GridView extends VerticalLayout {
public GridView() {
final Grid<Person> g = new Grid(Person.class);
g.setColumns("name");
g.setDataProvider(DataProvider.fromCallbacks(q -> fetch(q), q -> count(q)));
add(g);
// filter omitted
final Button refresh = new Button("refresh");
refresh.addClickListener(e -> {
System.out.println("refresh clicked");
g.getDataProvider().refreshAll();
});
add(refresh);
add(new TextField("State check"));
}
// fake DataProvider
private int count(Query<Person, Void> q) { return 3; }
private Stream<Person> fetch(Query<Person, Void> q) {
q.getLimit(); //vaadin checks these have been called
q.getOffset(); //vaadin checks these have been called
System.out.println("fetching again");
new Exception().printStackTrace(); //figure out who called
return Arrays.asList(new Person("1"), new Person("2"), new Person("3")).stream();
}
}
My MainView is used to switch between GridView and EmptyView
#PreserveOnRefresh
public class MainView extends AppLayout {
private Component emptyBView;
private Component gridBView;
public MainView() {
final Button emptyB = new Button("Btn empty");
emptyB.addClickListener(e -> {
if (emptyBView == null) { emptyBView = new EmptyView();}
setContent(emptyBView);
});
addToNavbar(emptyB);
final Button gridB = new Button("Btn grid");
gridB.addClickListener(e -> {
if (gridBView == null) gridBView = new GridView();
setContent(gridBView);
});
addToNavbar(gridB);
}
}
MainView is an AppLayout used to switch the contents of the AppLayout from GridView to EmptyView and back.
Use Case 2 is: When returning to GridView, the GridView should be exactly same state as before (which works fine with the TextField).
open GridView -> grid should not be filled with data
enter filter params (not shown in code)
click "refresh" to populate the grid
enter "Spiderman" in TextField "stateCheck"
switch to EmptyView
in the real app: do something in EmptyView and potentially other views
return to GridView -> the grid should not reload the data, it should just stay as it was - just like the TextField still displays "Spiderman", the grid should display the same data as before without reloading it.
For Case 1: In the callback check if you have filter parameters, return an empty set if not. Using the new V17+ API it would look like this:
grid.setItems(query -> {
if(filterParameters.isEmpty()) {
// Return an empty stream
} else {
// Fetch from backend
}
});
You can read more in the docs here: https://vaadin.com/docs/latest/flow/binding-data/data-provider (V19) or https://vaadin.com/docs/v14/flow/binding-data/tutorial-flow-data-provider (V14)
I would need more info on what you're currently doing to help out with Case 2. How are you constructing the view, what does your code look like? A full stack trace with the "Caused by" would also help.
I would recommend only setting the DataProvider to the Grid once the first filter parameter is set. The client-side Grid expects to receive the number of items it requires from the fetch query; it might work in some corner case if you don't provide the requested numbers of items from fetch, but it's not designed to behave like that.
Note that this applies specifically to using DataProviders with filters in Vaadin 14 series - Vaadin 17 introduced a new optional simplified way of fetching items, which changes this equation a bit. It's not backported to Vaadin 14 yet (currently planned for 14.7).
Split out from Vaadin Dataprovider: how to avoid "auto-fetch"?.
Given a Vaadin Flow 19 app with a MainView extends AppLayout, a GridView and an EmptyView And #PreserveOnRefresh annotation is used on MainView.
When returning to GridView, the GridView should be exactly in the same state as before:
open GridView using button in MainView for the first time -> Grid uses DataProvider to fetch data from backend
enter "Spiderman" in TextField with caption "stateCheck"
switch to EmptyView using button in MainView
in the real app: do something in EmptyView and potentially other views
return to GridView using button in MainView for the 2nd time
Then (1) the TextField with caption "stateCheck" should display the value "Spiderman"
And (2) the grid should still show the same data as before; it should not reload the data from the backend
Observed behaviour:
(1) is ok, but (2) not: the grid always calls fetch method to get data from the backend.
How do I achieve the desired behavior?
Here's the code of my GridView which also fakes the backend DataProvider:
#Route(value = "grid", layout = MainView.class)
public class GridView extends VerticalLayout {
public GridView() {
final Grid<Person> g = new Grid(Person.class);
g.setColumns("name");
g.setDataProvider(DataProvider.fromCallbacks(q -> fetch(q), q -> count(q)));
add(g);
add(new TextField("State check"));
}
// fake DataProvider
private int count(Query<Person, Void> q) { return 3; }
private Stream<Person> fetch(Query<Person, Void> q) {
q.getLimit(); //vaadin checks these have been called
q.getOffset(); //vaadin checks these have been called
System.out.println("fetching again");
return Arrays.asList(new Person("1"), new Person("2"), new Person("3")).stream();
}
}
MainView is used to switch between GridView and EmptyView
#PreserveOnRefresh
public class MainView extends AppLayout {
private Component emptyBView;
private Component gridBView;
public MainView() {
final Button emptyB = new Button("Btn empty");
emptyB.addClickListener(e -> {
if (emptyBView == null) { emptyBView = new EmptyView();}
setContent(emptyBView);
});
addToNavbar(emptyB);
final Button gridB = new Button("Btn grid");
gridB.addClickListener(e -> {
if (gridBView == null) gridBView = new GridView();
setContent(gridBView);
});
addToNavbar(gridB);
}
}
This is actually intentional behavior. The server side dataprovider listener needs to be removed when component is detached and rewired on attaching. The reason is that otherwise there would be listeners accumulating and producing a memory leakage. If you think your users would be using refresh page often, you should consider adding a cache to your application to optimize performance.
Now one could entertain with the idea of having this kind of caching of previous loaded data behavior via API in Grid also in Vaadin framework, as it may or may not be desirable. It is application specific.
If the use case of refreshing is really to get the fresh data of live and active database, it is actually desired that data is loaded when page is refreshed.
If the desire is to avoid extra bombarding of DB as data is known to be static, you want to have caching.
Can Any one help Me on How to Implement on Handling Pagination on Scrolling of listview in xamarin.android .any link or any sample wil be helpful
Well, Android pagination is quite easy in comparison to iOS and can be done as follows:
public class EndlessScrollListener : Java.Lang.Object, Android.Widget.AbsListView.IOnScrollListener
{
private int visibleThreshold = 5;
private int currentPage = 0;
private int previousTotal = 0;
private bool loading = true;
public EndlessScrollListener()
{
}
public EndlessScrollListener(int visibleThreshold)
{
this.visibleThreshold = visibleThreshold;
}
public void OnScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)
{
if (loading)
{
if (totalItemCount > previousTotal)
{
loading = false;
previousTotal = totalItemCount;
currentPage++;
}
}
if (!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold))
{
// I load the next page of gigs using a background task,
// but you can call any function here.
//new LoadGigsTask().execute(currentPage + 1);
loading = true;
}
}
public void OnScrollStateChanged(AbsListView view, [GeneratedEnum] ScrollState scrollState)
{
// throw new NotImplementedException();
}
}
Then set it to the listview as follows:
List.SetOnScrollListener(new EndlessScrollListener());
Working of code:
visibleThreshold – The minimum amount of items to have below your current scroll position, before loading more.
currentPage – The current page of data you have loaded.
previousTotal – The total number of items in the dataset after the last load.
loading – True if we are still waiting for the last set of data to load.
Next, we have a couple of constructors that allow us to set the visibleThreshold inline if we want.
The first overridden method we have is called every time the list is scrolled. This happens many times a second during a scroll, so be wary of the code you place here. We are given a few useful parameters to help us work out if we need to load some more data, but first, we check if we are waiting for the previous load to finish.
If it’s still loading, we check to see if the dataset count has changed, if so we conclude it has finished loading and update the current page number and total item count.
If it isn’t currently loading, we check to see if we have breached the visibleThreshold and need to reload more data. If we do need to reload some more data, we execute a background task and set the loading flag to true. Thus solving the problem forever!
The last method in the class we do not need, however, if you’re interested, it is primarily used for tracking changes in the scroll action itself via the scrollState parameter.
Finally, the code to call the class creates a new instance of EndlessScrollListener and bind’s it to a ListView of mine. Of course, put your own ListView in place of List.
I have a ListGrid defined like this:
ListGrid lgrid = new ListGrid();
ListGridField first = new ListGridField("first",first");
ListGridField second = new ListGridField("second ",second ");
lgrid.setFields(first, second);
lgrid.setShowFilterEditor(true);
¿How can i put the keyboard focus in the first filter editor field after i call show() in the layout?
Thxs in advance.
Depending on what your use case is (which would be useful to provide a more focused answer), the solution you posted might not be what you really need, because if you scroll on your ListGrid, it could trigger a new data fetch (if there are more records to show), and move the cursor to the filter editor as a result (if your user is editing some records at that point, the cursor moving to the filter row is not what she would want to happen!!).
In such a case, you probably just want to call grid.focusInFilterEditor("fieldToFocus") after the listGrid.show() statement or in the ClickHandler of some button you use to fetch the data, etc.
Anyway, you don't need the Timer either. This works:
listGrid.addDataArrivedHandler(new DataArrivedHandler() {
#Override
public void onDataArrived(DataArrivedEvent event) {
grid.focusInFilterEditor("fieldToFocus");
}
});
I got the solution, its focusInFilterEditor, this is an example to set the focus after the data arrived to the grid:
// Put the focus on the first listGrid field when is loaded
listGrid.addDataArrivedHandler(new DataArrivedHandler() {
#Override
public void onDataArrived(DataArrivedEvent event) {
Timer t = new Timer() {
public void run() {
if(listGrid.getFilterEditorCriteria() == null){
listGrid.focusInFilterEditor("fieldToFocus");
}
}
};
t.schedule(600);
}
});
Following a user clicking in a row in a Vaadin 7 table, how can I tell
if the row has been selected or deselected - clicking in a selected row deselects the row - (since there is also a column of
checkboxes which needs to kept in synch)?
When I tried table.getValue() in an ItemClickListener, this returned null if the
row is selected and the item id if the row is deselected - i.e. the opposite of
what I would have expected?
table.addItemClickListener(new ItemClickEvent.ItemClickListener() {
#Override
public void itemClick(ItemClickEvent event) {
// how tell if row has been selected or deselected?
Object idx = table.getValue();
}
});
Thank you,
Steve
In the Table api you can use the getValue() method, which results the selected rows.
Depending if in multiselct mode or not, you can then deduce what happens.
Use Vaadin Grid instead of table. It has the selectionListener property which will help you achieve what you need.
Grid API
(since there is also a column of checkboxes which needs to kept in
synch)?
Use Grid ans set grid.setSelectionMode(SelectionMode.MULTI); you will have one extra column with checkboxes by default.
Now, If you don't want to use Grid.
For Table you should use vaadinTable.addValueChangeListener(...) instead of ItemClickListener.
For Example :
table.addValueChangeListener(new ValueChangeListener() {
#Override
public void valueChange(ValueChangeEvent event) {
//Set checkbox object as an itemId
CheckBox itemId = (CheckBox)event.getProperty().getValue();
//Manage Collection to add selected items
if(table.isSelected(itemId)) {
table.select(itemId);
itemId.setValue(true);//Add this item to collection
} else {
table.unselect(itemId); //Remove this item to collection
itemId.setValue(false);
}
}
});
One another option is,
table.addItemClickListener(new ItemClickEvent.ItemClickListener() {
#Override
public void itemClick(ItemClickEvent event) {
//Manage collection and manually fetch property of table
Object value = event.getItem().getItemProperty("property").getValue();
}
});