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
docs: update data-provider article for item index provider (#3089) (#3117)
* docs: update data-provider article for item index provider
Document how to make callback for an item index with lazy data binding.
Related-to: vaadin/flow#18088
* docs: fix vale findings
* First pass at editing.
* Vale fix
* Second full pass at editing.
---------
Co-authored-by: Russell J.T. Dyer <6652767+russelljtdyer@users.noreply.github.com>
Co-authored-by: Russell JT Dyer <russelljtdyer@users.noreply.github.com>
Co-authored-by: Mikhail Shabarov <61410877+mshabarov@users.noreply.github.com>
Copy file name to clipboardExpand all lines: articles/flow/binding-data/data-provider.adoc
+66-32Lines changed: 66 additions & 32 deletions
Original file line number
Diff line number
Diff line change
@@ -1,15 +1,15 @@
1
1
---
2
2
title: Binding Items to Components
3
-
description: How to bind and display a list of items in components such as Grid and Combo Box.
3
+
description: Binding and displaying a list of items in components, such as Grid and Combo Box.
4
4
order: 50
5
5
---
6
6
7
7
8
8
= Binding Items to Components
9
9
10
-
Selection components allow selecting a field value from a list of options. This article describes how they're bound and displayed in such components.
10
+
Selection components allow selecting a field value from a list of options. This page describes how they're bound and displayed in such components.
11
11
12
-
Applications often display lists of items. You might want users to select one or more of these items. To display such lists, you can use basic components such as HTML elements. Alternatively, you can use components specifically designed for this purpose. For example, there's `Grid`, `ComboBox`, and `ListBox`.
12
+
Applications often display lists of items, of which you might want users to select one or more items. To display such lists, you can use basic components such as HTML elements. Alternatively, you can use components specifically designed for this purpose: such as `Grid`, `ComboBox`, and `ListBox`.
13
13
14
14
[source,java]
15
15
----
@@ -25,9 +25,9 @@ grid.setItems(
25
25
);
26
26
----
27
27
28
-
All listing components in Vaadin have many overloaded [methodname]`setItems()` methods to define the items to display. Items can be basic objects, such as strings or numbers, or they can be plain-old Java objects (POJO), such as Data Transfer Objects (DTO) and JPA entities.
28
+
All listing components in Vaadin have many overloaded [methodname]`setItems()` methods to define the items to display. Items can be basic objects (e.g., strings or numbers), or they can be plain-old Java objects (POJO), such as Data Transfer Objects (DTO) and JPA entities.
29
29
30
-
The easiest way to bind items to a component is to provide a [classname]`List` of objects to be shown in such a component.
30
+
The easiest way to bind items to a component is to provide a [classname]`List` of objects to be shown in the component.
31
31
32
32
If there are many items, requiring plenty of memory, `Grid` and `ComboBox` allow lazy data binding using callbacks to fetch only the required set of items from the backend.
Component-specific APIs allow you to adjust how items are displayed. By default, listing components use the [methodname]`toString()` method to display items. If this isn't suitable, you can change the behavior by configuring the component.
40
40
41
-
Listing components have one or more callbacks that define how to display the items. For example, consider the `ComboBox` component that lists status items. You can configure it to use [methodname]`Status::getLabel()` method to get a label for each status item.
41
+
Listing components have one or more callbacks that define how to display the items. For example, consider the `ComboBox` component that lists status items. You can configure it to use [methodname]`Status::getLabel()` method to get a label for each status item:
42
42
43
43
[source,java]
44
44
----
45
45
ComboBox<Status> comboBox = new ComboBox<>();
46
46
comboBox.setItemLabelGenerator(Status::getLabel);
47
47
----
48
48
49
-
In a `Grid`, you can use [methodname]`addColumn()` to define the columns and configure the getter that returns the content for the column. The [methodname]`setHeader()` method sets the column header.
49
+
In a `Grid`, you can use [methodname]`addColumn()` to define the columns and configure the getter that returns the content for the column. The [methodname]`setHeader()` method sets the column header:
Using callback methods is a more advanced way to bind data to components. By this method, only the required portion of the data is loaded from your backend to the server memory. This approach is more difficult to implement and provides fewer features out-of-the-box, but it can save plenty of resources on the backend and on the UI server.
129
+
Using callback methods is a more advanced way to bind data to components. By this method, only the required portion of the data is loaded from your backend to the server memory. This approach is more difficult to implement and provides fewer features out-of-the-box. However, it can save plenty of resources on the backend and on the UI server.
130
130
131
-
Currently, only `Grid` and `ComboBox` support lazy data binding.
131
+
Currently, only `Grid` and `ComboBox` support lazy data binding. It's important to understand how lazy data binding works. Here's the process it follows:
132
132
133
-
It's important to understand how lazy data binding works. Here's the process it follows:
134
-
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
-
. The component automatically 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 automatically displays it once the data is available.
133
+
- 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.
134
+
- 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.
135
+
- 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 automatically displays it once the data is available.
138
136
139
137
For example, to bind data lazily to a `Grid` you might do this:
<1> To create a lazy binding, use an overloaded version of the [methodname]`setItems()` method that uses a callback instead of passing data directly to the component.
153
-
<2> Typically, you call the service layer from the callback, as is done here.
151
+
<2> Typically, you'd call the service layer from the callback, as is done here.
154
152
<3> The link:https://vaadin.com/api/platform/{moduleMavenVersion:com.vaadin:vaadin}/com/vaadin/flow/data/provider/Query.html#getOffset()[_offset_] refers to the first index of the item to fetch.
155
153
<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
-
<5> In this example, it's assumed that the backend returns a [classname]`List`. Therefore, you need to convert it to a [classname]`Stream`.
154
+
<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
155
158
-
The example above works well with JDBC backends, where you can request a set of rows from a given index. Vaadin 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.
156
+
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.
159
157
160
158
For example, to do lazy data binding from a Spring Data Repository to `Grid` you would do something like this:
161
159
@@ -171,7 +169,7 @@ grid.setItems(query -> {
171
169
172
170
<1> Call a Spring Data repository to get the requested result set.
173
171
<2> The query object contains a shorthand for a zero-based page index.
174
-
<3> The query object also contains page size.
172
+
<3> The query object also contains the page size.
175
173
<4> Return a stream of items from the Spring Data [classname]`Page` object.
176
174
177
175
@@ -211,11 +209,11 @@ public void bindWithSorting() {
211
209
----
212
210
<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
211
<2> Alternatively, define a key to your columns, which is passed to the callback, and define the column to be sortable.
214
-
<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 is mostly converting Vaadin's QuerySortOrder hints to Spring's [classname]`Order` objects and finally passing the sort and paging details to the backend.
212
+
<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
213
216
-
.Helpers for Spring Data based backends
214
+
.Spring Data Based Backend Helpers
217
215
[NOTE]
218
-
The examples above are written against 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.
216
+
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.
<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
243
<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 in `%` characters to match anywhere in the text.
244
+
<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
245
<4> Pass the filter to your backend in the binding.
248
246
249
247
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
248
251
-
The `ComboBox` examples below use the new data API available since Vaadin 18, where the item count query isn't needed to fetch items.
249
+
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.
252
250
253
251
You can handle filterable lazy data binding to a Spring Data repository as follows:
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.
302
300
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:
301
+
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:
When using lazy data binding, the component can't know the index of the item in the data set, if it's not loaded yet. Index is needed, for example, when you want to scroll to an item's position in the component. [methodname]`setItemIndexProvider(ItemIndexProvider)` method in [classname]`LazyDataView` is used to provide a callback to get the index of the item in the data set.
330
+
331
+
The example below sets the item index provider that uses a service which uses the Spring Data repository. It fetches all persons and finds the index of the matching item in the list. This is not an optimal solution with a large data set, but it shows how to implement the callback. Callback should always ensure the data set used to find the item index matches the component's data set with the same sorting and filtering:
The callback gives parameters of the target item, and the [classname]`Query` object to fetch the index. The query is prepared for fetching all items, including filter and sorting. The returned index is the index of the item in the filtered and sorted data set. If the item is not found, null is expected as a return value.
350
+
351
+
The index is inconsistent if the data set for the returned index is different from the component's data set. Changing the data set of either side during this call may cause an inconsistent index.
330
352
331
-
You may need to get a handle to all items shown in a listing component. For example, add-ons or generic helpers might want to do something with the data that's currently listed in the component. For such a purposes, the supertype of data views can be accessed with the [methodname]`getGenericDataView()` method.
353
+
The index of an item is retrieved with [methodname]`getItemIndex(Object)` method in [classname]`DataView`. It works with lazy data binding only when the item index provider is set. Otherwise, it throws [classname]`UnsupportedOperationException`.
354
+
355
+
This is an example of a call that scrolls to the item's position in the component:
356
+
357
+
[source,java]
358
+
----
359
+
grid.scrollToIndex(dataView.getItemIndex(item));
360
+
----
361
+
362
+
363
+
== Accessing Displayed Items
364
+
365
+
You may need to get a handle to all items shown in a listing component. For example, add-ons or generic helpers might want to do something with the data that's currently listed in the component. For such purposes, the supertype of data views can be accessed with the [methodname]`getGenericDataView()` method.
332
366
333
367
[CAUTION]
334
368
Calling certain methods in data views can be an expensive operation. Particularly with lazy data binding, calling [methodname]`grid.getGenericDataView().getItems()` causes the whole data set to be loaded from the backend.
If you've assigned your items as in-memory data, you have more methods available in a list data view object. You can get the reference to that as a return value of the [methodname]`setItems()` method or through the [methodname]`getListDataView()` method. It's then possible to get the next or previous item from a certain item. Of course, this can be done by saving the original data structure,
357
-
but that way you can implement a generic UI logic without dependencies on the assigned data.
390
+
If you've assigned your items as in-memory data, you have more methods available in a list data view object. You can get the reference to that as a return value of the [methodname]`setItems()` method or through the [methodname]`getListDataView()` method. It's then possible to get the next or previous item from a certain item. Of course, this can be done by saving the original data structure, but that way you can implement a generic UI logic without dependencies on the assigned data.
358
391
359
392
For example, you can programmatically select the next item in a `Grid`, if a current value is selected and there is a next item after it.
360
393
@@ -375,7 +408,7 @@ Button selectNext = new Button("Next", e -> {
375
408
376
409
== Updating the Displayed Data
377
410
378
-
A typical scenario in Vaadin applications is that data displayed in, for example, a `Grid` component, is edited elsewhere in the application. Editing the item elsewhere doesn't automatically update the UI in a listing component.
411
+
A typical scenario in Vaadin Flow applications is that data displayed, for example, in a `Grid` component, is edited elsewhere in the application. Editing an item elsewhere doesn't automatically update the UI in a listing component.
379
412
380
413
An easy way to refresh the component's content is to call [methodname]`setItems()` again with the fresh data. Alternatively, you can use finer-grained APIs in the `DataView` to update a portion of the dataset.
You can create a separate data provider class. The following example uses only the [classname]`FetchCallBack`, but you can also implement a full data provider by, for example, extending [classname]`AbstractbackendDataProvider`.
558
+
You can create a separate data provider class. The following example uses only the [classname]`FetchCallBack`, but you can also implement a full data provider by extending [classname]`AbstractbackendDataProvider`.
526
559
527
560
[source,java]
528
561
----
@@ -543,8 +576,9 @@ public class PersonDataProvider implements CallbackDataProvider.FetchCallback<Pe
543
576
personGrid.setItems(dataProvider);
544
577
----
545
578
579
+
546
580
[[data-binding.data-provider.item-identifiers]]
547
-
== Ensuring Item Identities are Stable and Unique
581
+
== Stable & Unique Item Identities
548
582
549
583
When you bind items to a component, the identities of those items are essential for the component to work. For example, if you bind a list of `Person` objects to a `Grid`, the `Grid` relies on the identities of the `Person` objects for various operations, such as for highlighting the selected rows and updating the data in an edited row.
0 commit comments