You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardexpand all lines: articles/flow/binding-data/data-provider.adoc
+109-89
Original file line number
Diff line number
Diff line change
@@ -134,9 +134,36 @@ Currently, only `Grid` and `ComboBox` support lazy data binding. To use it, thou
134
134
135
135
- The user performs an action that requires the component to display more data. For example, the user might scroll down a list of items in a `Grid` component.
136
136
- The component detects that more data is needed, and it passes a link:https://vaadin.com/api/platform/{moduleMavenVersion:com.vaadin:vaadin}/com/vaadin/flow/data/provider/Query.html[`Query`] object as a parameter to the callback methods. This object contains the necessary information about the data that should be displayed next to the user.
137
-
- The callback methods use this [classname]`Query` object to fetch only the required data -- usually from the backend -- and return it to the component, which displays it once the data is available.
137
+
- The callback methods use this [classname]`Query` object (for generic callbacks) or a [classname]`Pageable` object (for Spring services) to fetch only the required data and return it to the component, which displays it once the data is available.
138
138
139
-
For example, to bind data lazily to a `Grid` you might do this:
139
+
140
+
=== Binding a Grid to a Spring Service for Lazy Loading
141
+
142
+
For example, to bind a `Grid` to load its data on demand from a Spring service, you do:
143
+
144
+
[source,java]
145
+
----
146
+
grid.setItemsPageable(productService::list);
147
+
148
+
// Assuming a service like
149
+
class ProductService {
150
+
public List<Product> list(Pageable pageable) { ... }
151
+
}
152
+
----
153
+
154
+
The `setItemsPageable` method takes care of making the Grid request available in a Spring friendly format, as a `Pageable` instance. This `Pageable` instance contains information about which page (offset/limit) to load and also about how to sort the data (based on what is selected in the Grid). Many spring data sources use `Pageable` so you can directly pass it into e.g. a JPA repository class.
155
+
156
+
If your Spring service requires additional parameters, you can use the longer version
For the generic case, you use the `setItems` method which uses a more generic `Query` type parameter. The `Query` parameter contains the same information as `Pageable` for Spring, i.e. which rows to fetch (offset/limit) and which sort order to use.
<4> The link:https://vaadin.com/api/platform/{moduleMavenVersion:com.vaadin:vaadin}/com/vaadin/flow/data/provider/Query.html#getLimit()[_limit_] refers to the number of items to fetch. When fetching more data, you should utilize [classname]`Query` properties to limit the amount of data to fetch.
156
183
<5> In this example, it's assumed that the backend returns a [classname]`List`. Therefore, you'll need to convert it to a [classname]`Stream`.
157
184
158
-
The example above works well with JDBC backends, where you can request a set of rows from a given index. Vaadin Flow executes your data binding call in a paged manner, so it's also possible to bind to "paging backends", such as Spring Data-based solutions.
185
+
The example above works well with JDBC backends, where you can request a set of rows from a given index. Vaadin Flow executes your data binding call in a paged manner, so it's also possible to bind to "paging backends".
186
+
187
+
[[data-binding.data-provider.lazy-sorting]]
188
+
189
+
For efficient lazy data binding, sorting needs to happen in the backend. By default, `Grid` makes all columns appear sortable in the UI if you pass the class as a constructor parameter. You can declare which columns should be sortable. Otherwise, the UI may show that some columns are sortable, but nothing happens if you try to sort them.
159
190
160
-
For example, to do lazy data binding from a Spring Data Repository to `Grid` you would do something like this:
191
+
To make sorting work in a lazy data binding, you need to pass the hints that `Grid` provides in the [classname]`Query` object to your backend logic. For example, to enable sortable lazy data binding to a custom service like
161
192
162
193
[source,java]
163
194
----
164
-
grid.setItems(query -> {
165
-
return repository.findAll( // <1>
166
-
PageRequest.of(query.getPage(), // <2>
167
-
query.getPageSize()) // <3>
168
-
).stream(); // <4>
169
-
});
195
+
public List<Person> listPersons(int page, int offset, String sortProperty, boolean ascending) { ... }
170
196
----
171
197
172
-
<1> Call a Spring Data repository to get the requested result set.
173
-
<2> The query object contains a shorthand for a zero-based page index.
174
-
<3> The query object also contains the page size.
175
-
<4> Return a stream of items from the Spring Data [classname]`Page` object.
176
-
177
-
178
-
[[data-binding.data-provider.lazy-sorting]]
179
-
=== Sorting with Lazy Data Binding
180
-
181
-
For efficient lazy data binding, sorting needs to happen in the backend. By default, `Grid` makes all columns appear sortable in the UI if you pass the class as a constructor parameter. You can declare which columns should be sortable. Otherwise, the UI may show that some columns are sortable, but nothing happens if you try to sort them.
182
-
183
-
To make sorting work in a lazy data binding, you need to pass the hints that `Grid` provides in the [classname]`Query` object to your backend logic. For example, to enable sortable lazy data binding to a Spring Data repository, do this:
198
+
you could do
184
199
185
200
[source,java]
186
201
----
187
-
public void bindWithSorting() {
188
-
Grid<Person> grid = new Grid<>(Person.class);
189
-
grid.setSortableColumns("name", "email"); // <1>
190
-
grid.addColumn(person -> person.getTitle())
191
-
.setHeader("Title")
192
-
.setKey("title").setSortable(true); // <2>
193
-
grid.setItems(query -> { // <3>
194
-
var vaadinSortOrders = query.getSortOrders();
195
-
var springSortOrders = new ArrayList<Sort.Order>();
<1> If you're using property-name-based column definition, `Grid` columns can be made sortable by their property names. The [methodname]`setSortableColumns()` method makes columns with given identifiers sortable and all others non-sortable.
213
219
<2> Alternatively, define a key to your columns, which is passed to the callback, and define the column to be sortable.
214
220
<3> In the callback, you need to convert the Vaadin-specific sort information to whatever your backend understands. This example uses Spring Data based backend, so it's mostly converting Vaadin's QuerySortOrder hints to Spring's [classname]`Order` objects and finally passing the sort and paging details to the backend.
215
-
216
-
.Spring Data Based Backend Helpers
217
-
[NOTE]
218
-
The examples above are written for Spring Data based examples, but in a verbose way to keep them relevant for any kind of Java backend service. If you're using Spring Data based backends, the above code examples can be written with one-liners using the helper methods in [classname]`VaadinSpringDataHelpers` class. It contains [methodname]`toSpringPageRequest()` and [methodname]`toSpringDataSort()` methods to convert automatically Vaadin specific query hints to their Spring Data relatives. Using the [methodname]`fromPagingRepository()` method, you can create a lazy sortable data binding directly to your repository.
221
+
<4> For a real implementation, you should take into account that there can be many sort orders, i.e. "first sort by birth year and then by last name"
219
222
220
223
221
224
=== Filtering with Lazy Data Binding
222
225
223
226
For the lazy data to be efficient, filtering needs to be done at the backend. For instance, if you provide a text field to limit the results shown in a `Grid`, you need to make your callbacks handle the filter.
224
227
225
-
As an example, to handle filterable lazy data binding to a Spring Data repository in `Grid`, you might do this:
228
+
For example, suppose you have a Spring service that supports filtering like this:
<1> The lazy data value change mode is optimal for filtering purposes. Queries to the backend are only done when a user makes a small pause while typing.
248
+
<2> Passes the current filter value to the service.
249
+
<3> When a value-change event occurs, asks the data provider to load new values.
243
250
244
-
<1> The lazy data binding mode is optimal for filtering purposes. Queries to the backend are only done when a user makes a small pause while typing.
245
-
<2> When a value-change event occurs, you should reset the data binding to use the new filter.
246
-
<3> The example backend uses SQL behind the scenes, so the filter string is wrapped with the `%` wildcard character to match anywhere in the text.
247
-
<4> Pass the filter to your backend in the binding.
251
+
If you are not using Spring, you can pass a filter value in the same way. You can also pass more complex filtering values like JPA specification instances or whatever is needed.
248
252
249
-
You can combine both filtering and sorting in your data binding callbacks. Consider a `ComboBox` as an another example of lazy-loaded data filtering. The lazy-loaded binding in `ComboBox` is always filtered by the string typed in by the user. Initially, when there is no filter input yet, the filter is an empty string.
250
253
251
-
The `ComboBox` examples below use the new data API available since Vaadin Flow 18, where the item count query isn't needed to fetch items.
254
+
=== Binding a `ComboBox` to a Spring Service for Lazy Loading
252
255
253
-
You can handle filterable lazy data binding to a Spring Data repository as follows:
256
+
A Combo Box differs in its data binding for Grid in two ways: it doesn't have UI controls for defining the sort order; and it has a UI control for defining a filter string.
257
+
258
+
The API for connecting a Combo Box to a Spring Service is quite similar to the Grid API:
254
259
255
260
[source,java]
256
261
----
257
-
ComboBox<Person> cb = new ComboBox<>();
258
-
cb.setItems(
259
-
query -> repo.findByNameLikeIgnoreCase(
260
-
// Add `%` marks to filter for an SQL "LIKE" query
public List<Product> list(Pageable pageable, String filterString) { ... }
267
+
}
265
268
----
266
269
267
-
The above example uses a fetch callback to lazy-load items, and the `ComboBox` fetches more items as the user scrolls the drop-down, until there are no more items returned. If you want the scrollbar in the drop-down to reflect the exact number of items matching the filter, an optional item count callback can be used, as shown in the following example:
270
+
A service used for a Combo Box always has at least two parameters: a `Pageable` instance to define which page to load; and a `String` that provides the input the user has typed into the `combobox` field -- which is empty by default.
271
+
272
+
Similarly, when not using Spring, you would have the following:
<1> The [classname]`Query` object contains the filter
279
282
280
283
If you want to filter items with a type other than a string, you can provide a filter converter with the fetch callback to get the right type of filter for the fetch query like so:
<1> [classname]`Query` object contains the filter of type returned by given converter.
298
+
<1> The [classname]`Query` object contains the filter of type returned by given converter.
296
299
<2> The second callback is used to convert the filter from the combo box text on the client side into an appropriate value for the backend.
297
300
298
301
299
302
=== Improving Scrolling Behavior
300
303
301
-
With lazy data binding, the component doesn't know how many items are actually available. When a user scrolls to the end of the scrollable area, `Grid` polls your callbacks for more items. If new items are found, these are added to the component. This causes the relative scrollbar to behave in a strange way as new items are added on the fly.
304
+
With the lazy data binding described above, the component doesn't know how many items are actually available. When a user scrolls to the end of the scrollable area, the component (e.g. `Grid` or `ComboBox`) polls your callbacks for more items. If new items are found, these are added to the component. This pattern, often called infinite scrolling, causes the scrollbar to be updated when new items are added on the fly and does not allow the user to immediately scroll to the end.
305
+
306
+
The usability can be improved by either providing the exact number of items available or providing an estimate of the number of items.
307
+
308
+
If your service is able to provide the exact number of items available, you can add an additional "count" callback to `setItems` or `setItemsPageable` like this:
public List<Product> list(Pageable pageable) { ... }
317
+
public long count(Pageable pageable) { ... } <1>
318
+
}
319
+
----
320
+
<1> The count method should return the total number of items available. The _pageable_ instance is also passed to the count method but in most cases you do not need to take it into account.
302
321
303
-
The usability can be improved by providing an estimate of the actual number of items in the binding code. The adjustment happens through a [classname]`DataView` instance, which is returned by the [methodname]`setItems()` method. For example, to configure the estimate of rows and how the "virtual row count" is adjusted when the user scrolls down, you could do this:
322
+
If your service does not provide an exact count or requesting it is too costly (i.e. takes too long), you can provide an estimate instead.
323
+
This you can do through a [classname]`DataView` instance, which is returned by the [methodname]`setItems()` method. For example, to configure the estimate of rows and how the "virtual row count" is adjusted when the user scrolls down, you could do this:
0 commit comments