Skip to content

Commit 0dac578

Browse files
sissbrueckertaefi
andauthored
docs: combo box lazy loading (#4199)
* add react example * add lit example * add flow example * Update articles/components/combo-box/index.adoc Co-authored-by: Soroosh Taefi <taefi.soroosh@gmail.com> --------- Co-authored-by: Soroosh Taefi <taefi.soroosh@gmail.com>
1 parent bc27c17 commit 0dac578

File tree

8 files changed

+262
-1
lines changed

8 files changed

+262
-1
lines changed

articles/components/combo-box/index.adoc

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,59 @@ endif::[]
333333
--
334334

335335

336+
== Lazy Loading
337+
338+
Combo Box supports lazy loading, which is useful when dealing with large datasets. This avoids loading the whole dataset into memory and only fetches data for the current filter and viewport.
339+
340+
[.example]
341+
--
342+
ifdef::lit[]
343+
[source,typescript]
344+
----
345+
include::{root}/frontend/demo/component/combobox/combo-box-lazy-loading.ts[render,tags=snippet,indent=0,group=Lit]
346+
----
347+
[source,java]
348+
----
349+
include::{root}/src/main/java/com/vaadin/demo/component/combobox/ComboBoxCountryService.java[render,tags=snippet,indent=0,group=Lit]
350+
----
351+
[source,java]
352+
----
353+
include::{root}/src/main/java/com/vaadin/demo/component/combobox/ComboBoxCountryRepository.java[render,tags=snippet,indent=0,group=Lit]
354+
----
355+
endif::[]
356+
357+
ifdef::flow[]
358+
[source,java]
359+
----
360+
include::{root}/src/main/java/com/vaadin/demo/component/combobox/ComboBoxLazyLoading.java[render,tags=snippet,indent=0,group=Flow]
361+
----
362+
[source,java]
363+
----
364+
include::{root}/src/main/java/com/vaadin/demo/component/combobox/ComboBoxCountryService.java[render,tags=snippet,indent=0,group=Flow]
365+
----
366+
[source,java]
367+
----
368+
include::{root}/src/main/java/com/vaadin/demo/component/combobox/ComboBoxCountryRepository.java[render,tags=snippet,indent=0,group=Flow]
369+
----
370+
endif::[]
371+
372+
ifdef::react[]
373+
[source,tsx]
374+
----
375+
include::{root}/frontend/demo/component/combobox/react/combo-box-lazy-loading.tsx[render,tags=snippet,indent=0,group=React]
376+
----
377+
[source,java]
378+
----
379+
include::{root}/src/main/java/com/vaadin/demo/component/combobox/ComboBoxCountryService.java[render,tags=snippet,indent=0,group=React]
380+
----
381+
[source,java]
382+
----
383+
include::{root}/src/main/java/com/vaadin/demo/component/combobox/ComboBoxCountryRepository.java[render,tags=snippet,indent=0,group=React]
384+
----
385+
endif::[]
386+
--
387+
388+
336389
// Basic Features
337390

338391
include::{articles}/components/_input-field-common-features.adoc[tags=basic-intro;label;helper;placeholder;tooltip;clear-button;prefix;aria-labels]
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import 'Frontend/demo/init'; // hidden-source-line
2+
import '@vaadin/combo-box';
3+
import { html, LitElement } from 'lit';
4+
import { customElement } from 'lit/decorators.js';
5+
import type {
6+
ComboBoxDataProviderCallback,
7+
ComboBoxDataProviderParams,
8+
} from '@vaadin/react-components';
9+
import type Country from 'Frontend/generated/com/vaadin/demo/domain/Country';
10+
import { ComboBoxCountryService } from 'Frontend/generated/endpoints';
11+
import { applyTheme } from 'Frontend/generated/theme';
12+
13+
@customElement('combo-box-lazy-loading')
14+
export class Example extends LitElement {
15+
protected override createRenderRoot() {
16+
const root = super.createRenderRoot();
17+
// Apply custom theme (only supported if your app uses one)
18+
applyTheme(root);
19+
return root;
20+
}
21+
22+
// tag::snippet[]
23+
async dataProvider(
24+
params: ComboBoxDataProviderParams,
25+
callback: ComboBoxDataProviderCallback<Country>
26+
) {
27+
// Convert the params to Spring Data Pageable
28+
const filter = params.filter;
29+
const pageable = {
30+
pageNumber: params.page,
31+
pageSize: params.pageSize,
32+
sort: { orders: [] },
33+
};
34+
// Call backend service with pageable and current combo box filter
35+
const pageItems = await ComboBoxCountryService.list(pageable, filter);
36+
37+
// Estimate the total count of filtered items
38+
let filteredCount;
39+
if (pageItems.length === params.pageSize) {
40+
filteredCount = (params.page + 1) * params.pageSize + 1;
41+
} else {
42+
filteredCount = params.page * params.pageSize + pageItems.length;
43+
}
44+
45+
callback(pageItems, filteredCount);
46+
}
47+
48+
protected override render() {
49+
return html`
50+
<vaadin-combo-box
51+
.dataProvider="${this.dataProvider}"
52+
label="Country"
53+
item-label-path="name"
54+
item-value-path="id"
55+
></vaadin-combo-box>
56+
`;
57+
}
58+
// end::snippet[]
59+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { reactExample } from 'Frontend/demo/react-example'; // hidden-source-line
2+
import React from 'react';
3+
import { useComboBoxDataProvider } from '@vaadin/hilla-react-crud';
4+
import { ComboBox } from '@vaadin/react-components/ComboBox.js';
5+
import { ComboBoxCountryService } from 'Frontend/generated/endpoints';
6+
7+
// tag::snippet[]
8+
function Example() {
9+
// Create a data provider that calls a backend service with a
10+
// Spring Data pageable and the current combo box filter
11+
const dataProvider = useComboBoxDataProvider(
12+
ComboBoxCountryService.list.bind(ComboBoxCountryService)
13+
);
14+
15+
return (
16+
<ComboBox dataProvider={dataProvider} label="Country" itemLabelPath="name" itemValuePath="id" />
17+
);
18+
}
19+
20+
// end::snippet[]
21+
22+
export default reactExample(Example); // hidden-source-line
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { getCountries } from 'Frontend/demo/domain/DataService';
2+
import { CrudMockService } from 'Frontend/demo/services/CrudService';
3+
import type Country from 'Frontend/generated/com/vaadin/demo/domain/Country';
4+
import type PropertyStringFilter from 'Frontend/generated/com/vaadin/hilla/crud/filter/PropertyStringFilter';
5+
import Matcher from 'Frontend/generated/com/vaadin/hilla/crud/filter/PropertyStringFilter/Matcher';
6+
import type Pageable from 'Frontend/generated/com/vaadin/hilla/mappedtypes/Pageable';
7+
8+
class ComboBoxCountryService {
9+
private mockService?: CrudMockService<Country>;
10+
11+
async list(pageable: Pageable, filter: string): Promise<Country[]> {
12+
await this.initMockService();
13+
14+
return this.mockService!.list(pageable, this.createFilter(filter));
15+
}
16+
17+
private createFilter(filter: string): PropertyStringFilter {
18+
return {
19+
'@type': 'propertyString',
20+
propertyId: 'name',
21+
filterValue: filter,
22+
matcher: Matcher.CONTAINS,
23+
};
24+
}
25+
26+
private async initMockService() {
27+
if (this.mockService) {
28+
return;
29+
}
30+
const data = await getCountries();
31+
this.mockService = new CrudMockService(data);
32+
}
33+
}
34+
35+
export default new ComboBoxCountryService();

frontend/demo/services/mocks.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
11
// This module should export all mock services used in live examples
22
// During the build, the `Frontend/generated/endpoints` import is replaced with this module
3+
import ComboBoxCountryService from 'Frontend/demo/services/ComboBoxCountryService';
34
import DashboardService from 'Frontend/demo/services/DashboardService';
45
import EmployeeService from 'Frontend/demo/services/EmployeeService';
56
import GridPersonService from 'Frontend/demo/services/GridPersonService';
67
import LLMChatService from 'Frontend/demo/services/LLMChatService';
78
import ProductService from 'Frontend/demo/services/ProductService';
9+
810
export * from 'Frontend/generated/endpoints.js';
911

10-
export { DashboardService, EmployeeService, ProductService, GridPersonService, LLMChatService };
12+
export {
13+
DashboardService,
14+
EmployeeService,
15+
ProductService,
16+
GridPersonService,
17+
ComboBoxCountryService,
18+
LLMChatService,
19+
};
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.vaadin.demo.component.combobox;
2+
3+
import com.vaadin.demo.domain.Country;
4+
import com.vaadin.demo.domain.DataService;
5+
import org.springframework.data.domain.Pageable;
6+
import org.springframework.stereotype.Component;
7+
8+
import java.util.List;
9+
10+
// hidden-source-line - We don't want to register an actual JPA repository here
11+
// hidden-source-line - So we put the source code to show in a comment and provide
12+
// hidden-source-line - a mock implementation that is hidden in the docs
13+
// tag::snippet[]
14+
/* // hidden-source-line
15+
public interface ComboBoxCountryRepository extends JpaRepository<Country, Long> {
16+
List<Country> findByNameContainingIgnoreCase(String name, Pageable pageable);
17+
}
18+
*/ // hidden-source-line
19+
// end::snippet[]
20+
21+
@Component // hidden-source-line
22+
public class ComboBoxCountryRepository { // hidden-source-line
23+
List<Country> findByNameContainingIgnoreCase(String name, Pageable pageable) { // hidden-source-line
24+
return DataService.getCountries().stream() // hidden-source-line
25+
.filter(country -> country.getName().toLowerCase().contains(name.toLowerCase())) // hidden-source-line
26+
.skip((long) pageable.getPageNumber() * pageable.getPageSize()) // hidden-source-line
27+
.limit(pageable.getPageSize()) // hidden-source-line
28+
.toList(); // hidden-source-line
29+
} // hidden-source-line
30+
}// hidden-source-line
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.vaadin.demo.component.combobox;
2+
3+
import com.vaadin.demo.domain.Country;
4+
import com.vaadin.flow.server.auth.AnonymousAllowed;
5+
import com.vaadin.hilla.BrowserCallable;
6+
import org.jspecify.annotations.NonNull;
7+
import org.springframework.data.domain.Pageable;
8+
9+
import java.util.List;
10+
11+
// tag::snippet[]
12+
// @BrowserCallable and @AnonymousAllowed are only required if you want
13+
// to use the service from a Hilla view
14+
@BrowserCallable
15+
@AnonymousAllowed
16+
public class ComboBoxCountryService {
17+
private final ComboBoxCountryRepository repository;
18+
19+
public ComboBoxCountryService(ComboBoxCountryRepository repository) {
20+
this.repository = repository;
21+
}
22+
23+
public @NonNull List<@NonNull Country> list(Pageable pageable, String filter) {
24+
// Implement your data fetching and filtering logic here
25+
// For this example, we're using a Spring Data repository
26+
return repository.findByNameContainingIgnoreCase(filter, pageable);
27+
}
28+
}
29+
// end::snippet[]
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.vaadin.demo.component.combobox;
2+
3+
import com.vaadin.demo.DemoExporter; // hidden-source-line
4+
import com.vaadin.demo.domain.Country;
5+
import com.vaadin.flow.component.combobox.ComboBox;
6+
import com.vaadin.flow.component.html.Div;
7+
import com.vaadin.flow.router.Route;
8+
9+
@Route("combo-box-lazy-loading")
10+
// tag::snippet[]
11+
public class ComboBoxLazyLoading extends Div {
12+
public ComboBoxLazyLoading(ComboBoxCountryService countryService) {
13+
ComboBox<Country> comboBox = new ComboBox<>("Country");
14+
comboBox.setItemLabelGenerator(Country::getName);
15+
// Connect the combo box to a Spring service that fetches data based
16+
// on a Spring Data pageable and the current combo box filter
17+
comboBox.setItemsPageable(countryService::list);
18+
add(comboBox);
19+
}
20+
21+
public static class Exporter extends DemoExporter<ComboBoxLazyLoading> { // hidden-source-line
22+
} // hidden-source-line
23+
}
24+
// end::snippet[]

0 commit comments

Comments
 (0)