Skip to content

Commit ffa5e2d

Browse files
tltvrusselljtdyermshabarov
authored
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>
1 parent 19be062 commit ffa5e2d

File tree

1 file changed

+66
-32
lines changed

1 file changed

+66
-32
lines changed

articles/flow/binding-data/data-provider.asciidoc renamed to articles/flow/binding-data/data-provider.adoc

Lines changed: 66 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
---
22
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.
44
order: 50
55
---
66

77

88
= Binding Items to Components
99

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.
1111

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`.
1313

1414
[source,java]
1515
----
@@ -25,9 +25,9 @@ grid.setItems(
2525
);
2626
----
2727

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.
2929

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.
3131

3232
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.
3333

@@ -38,15 +38,15 @@ include::{articles}/flow/binding-data/_items-identities.adoc[]
3838

3939
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.
4040

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:
4242

4343
[source,java]
4444
----
4545
ComboBox<Status> comboBox = new ComboBox<>();
4646
comboBox.setItemLabelGenerator(Status::getLabel);
4747
----
4848

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:
5050

5151
[source,java]
5252
----
@@ -85,7 +85,7 @@ grid.addColumn(Person::getYearOfBirth)
8585
.setHeader("Year of birth");
8686
----
8787

88-
It's also possible to set the `Grid` columns to display by property name. For this, you need to get the column objects to configure the headers.
88+
It's also possible to set the `Grid` columns to display by property name. For this, you'll need to get the column objects to configure the headers:
8989

9090
[source,java]
9191
----
@@ -126,15 +126,13 @@ grid.setItems(personRepository.findAll());
126126

127127
== Lazy Data Binding Using Callbacks
128128

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, 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.
130130

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:
132132

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.
138136

139137
For example, to bind data lazily to a `Grid` you might do this:
140138

@@ -150,12 +148,12 @@ grid.setItems(query -> // <1>
150148
);
151149
----
152150
<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.
154152
<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.
155153
<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`.
157155

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.
159157

160158
For example, to do lazy data binding from a Spring Data Repository to `Grid` you would do something like this:
161159

@@ -171,7 +169,7 @@ grid.setItems(query -> {
171169

172170
<1> Call a Spring Data repository to get the requested result set.
173171
<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.
175173
<4> Return a stream of items from the Spring Data [classname]`Page` object.
176174

177175

@@ -211,11 +209,11 @@ public void bindWithSorting() {
211209
----
212210
<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.
213211
<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.
215213

216-
.Helpers for Spring Data based backends
214+
.Spring Data Based Backend Helpers
217215
[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.
219217

220218

221219
=== Filtering with Lazy Data Binding
@@ -243,12 +241,12 @@ private void listPersonsFilteredByName(String filterString) {
243241

244242
<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.
245243
<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.
247245
<4> Pass the filter to your backend in the binding.
248246

249247
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.
250248

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.
252250

253251
You can handle filterable lazy data binding to a Spring Data repository as follows:
254252

@@ -300,7 +298,7 @@ cb.setItemsWithFilterConverter(
300298

301299
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.
302300

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:
304302

305303
[source,java]
306304
----
@@ -326,9 +324,45 @@ dataView.setItemCountCallback(q -> getPersonService().getPersonCount());
326324
----
327325

328326

329-
== Accessing Currently Shown Items
327+
=== Callback for an Item Index
328+
329+
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:
332+
333+
[source,java]
334+
----
335+
dataView.setItemIndexProvider((item, query) -> {
336+
if(item == null) {
337+
return null;
338+
}
339+
AtomicInteger index = new AtomicInteger();
340+
return getPersonService().list(
341+
PageRequest.of(query.getPage(), query.getPageSize(), VaadinSpringDataHelpers.toSpringDataSort(query)))
342+
.stream()
343+
.peek(v -> index.incrementAndGet())
344+
.anyMatch(item::equals) ?
345+
index.get() - 1 : null;
346+
});
347+
----
348+
349+
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.
330352

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.
332366

333367
[CAUTION]
334368
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.
@@ -353,8 +387,7 @@ private void exportToCsvFile(Grid<Person> grid)
353387
}
354388
----
355389

356-
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.
358391

359392
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.
360393

@@ -375,7 +408,7 @@ Button selectNext = new Button("Next", e -> {
375408

376409
== Updating the Displayed Data
377410

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.
379412

380413
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.
381414

@@ -522,7 +555,7 @@ public static void listItems(Grid<Person> grid, PersonRepository repository) {
522555
}
523556
----
524557

525-
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`.
526559

527560
[source,java]
528561
----
@@ -543,8 +576,9 @@ public class PersonDataProvider implements CallbackDataProvider.FetchCallback<Pe
543576
personGrid.setItems(dataProvider);
544577
----
545578

579+
546580
[[data-binding.data-provider.item-identifiers]]
547-
== Ensuring Item Identities are Stable and Unique
581+
== Stable & Unique Item Identities
548582

549583
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.
550584

0 commit comments

Comments
 (0)