diff --git a/selection-grid-pro-flow-demo/pom.xml b/selection-grid-pro-flow-demo/pom.xml index 541b335..3a1ea9d 100644 --- a/selection-grid-pro-flow-demo/pom.xml +++ b/selection-grid-pro-flow-demo/pom.xml @@ -18,7 +18,7 @@ - 24.3.1 + 24.3.14 17 17 UTF-8 diff --git a/selection-grid-pro-flow-demo/src/main/java/com/vaadin/componentfactory/selectiongridpro/LazyDataWithFilterView.java b/selection-grid-pro-flow-demo/src/main/java/com/vaadin/componentfactory/selectiongridpro/LazyDataWithFilterView.java new file mode 100644 index 0000000..88c1354 --- /dev/null +++ b/selection-grid-pro-flow-demo/src/main/java/com/vaadin/componentfactory/selectiongridpro/LazyDataWithFilterView.java @@ -0,0 +1,140 @@ +package com.vaadin.componentfactory.selectiongridpro; + +import com.vaadin.componentfactory.selectiongridpro.bean.Person; +import com.vaadin.componentfactory.selectiongridpro.service.PersonData; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.component.icon.Icon; +import com.vaadin.flow.component.icon.VaadinIcon; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.data.provider.AbstractBackEndDataProvider; +import com.vaadin.flow.data.provider.ConfigurableFilterDataProvider; +import com.vaadin.flow.data.provider.Query; +import com.vaadin.flow.data.provider.QuerySortOrder; +import com.vaadin.flow.data.provider.SortDirection; +import com.vaadin.flow.data.value.ValueChangeMode; +import com.vaadin.flow.router.Route; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Stream; + +/** + * Demo showing lazy data loading with filtering and sorting. + */ +@Route(value = "lazyfilter", layout = MainLayout.class) +public class LazyDataWithFilterView extends VerticalLayout { + + private static final long serialVersionUID = 2692354461215796348L; + + private PersonDataProvider personDataProvider = new PersonDataProvider(); + + private PersonFilter personFilter = new PersonFilter(); + + private ConfigurableFilterDataProvider filterDataProvider = personDataProvider + .withConfigurableFilter(); + + public LazyDataWithFilterView() + { + Div messageDiv = new Div(); + + SelectionGridPro grid = new SelectionGridPro<>(); + grid.setDataProvider(filterDataProvider); + + grid.addEditColumn(Person::getFirstName).text(Person::setFirstName).setHeader("First Name").setSortProperty("firstName"); + grid.addEditColumn(Person::getLastName).text(Person::setLastName).setHeader("Last Name"); + grid.addColumn(Person::getAge).setHeader("Age"); + grid.addEditColumn(Person::isSubscriber).checkbox(Person::setSubscriber) + .setHeader("Subscriber"); + grid.setSelectionMode(Grid.SelectionMode.MULTI); + + grid.asMultiSelect().addValueChangeListener(event -> { + String message = String.format("Selection changed from %s to %s", + event.getOldValue(), event.getValue()); + messageDiv.setText(message); + }); + + TextField searchField = new TextField(); + searchField.setWidth("50%"); + searchField.setPlaceholder("Search"); + searchField.setClearButtonVisible(true); + searchField.setPrefixComponent(new Icon(VaadinIcon.SEARCH)); + searchField.setValueChangeMode(ValueChangeMode.EAGER); + searchField.addValueChangeListener(e -> { + personFilter.setSearchTerm(e.getValue()); + filterDataProvider.setFilter(personFilter); + }); + + VerticalLayout layout = new VerticalLayout(searchField, grid, messageDiv); + layout.setPadding(false); + add(layout); + } + + public static class PersonFilter { + + private String searchTerm; + + public void setSearchTerm(String searchTerm) { + this.searchTerm = searchTerm; + } + + public boolean test(Person person) { + return matches(person.getFirstName(), searchTerm); + } + + private boolean matches(String value, String searchTerm) { + return searchTerm == null || searchTerm.isEmpty() + || value.toLowerCase().contains(searchTerm.toLowerCase()); + } + } + + public static class PersonDataProvider extends AbstractBackEndDataProvider { + PersonData data = new PersonData(); + final List database = new ArrayList<>(data.getPersons()); + + @Override + protected Stream fetchFromBackEnd(Query query) { + // A real app should use a real database or a service + // to fetch, filter and sort data. + Stream stream = database.stream(); + + // Filtering + if (query.getFilter().isPresent()) { + stream = stream.filter(person -> query.getFilter().get().test(person)); + } + + // Sorting + if (query.getSortOrders().size() > 0) { + stream = stream.sorted(sortComparator(query.getSortOrders())); + } + + // Pagination + return stream.skip(query.getOffset()).limit(query.getLimit()); + } + + @Override + protected int sizeInBackEnd(Query query) { + return (int) fetchFromBackEnd(query).count(); + } + + private static Comparator sortComparator(List sortOrders) { + return sortOrders.stream().map(sortOrder -> { + Comparator comparator = personFieldComparator(sortOrder.getSorted()); + + if (sortOrder.getDirection() == SortDirection.DESCENDING) { + comparator = comparator.reversed(); + } + + return comparator; + }).reduce(Comparator::thenComparing).orElse((p1, p2) -> 0); + } + + private static Comparator personFieldComparator(String sorted) { + if (sorted.equals("firstName")) { + return Comparator.comparing(person -> person.getFirstName()); + } + return (p1, p2) -> 0; + } + } +} \ No newline at end of file diff --git a/selection-grid-pro-flow-demo/src/main/java/com/vaadin/componentfactory/selectiongridpro/MainLayout.java b/selection-grid-pro-flow-demo/src/main/java/com/vaadin/componentfactory/selectiongridpro/MainLayout.java index 667cbf4..7c7976e 100644 --- a/selection-grid-pro-flow-demo/src/main/java/com/vaadin/componentfactory/selectiongridpro/MainLayout.java +++ b/selection-grid-pro-flow-demo/src/main/java/com/vaadin/componentfactory/selectiongridpro/MainLayout.java @@ -12,7 +12,8 @@ public MainLayout() { final RouterLink simple = new RouterLink("Selection Grid Pro", SimpleView.class); final RouterLink beanGrid = new RouterLink("Selection Grid Pro with a bean class", BeanGridView.class); final RouterLink lazy = new RouterLink("Lazy Grid Pro view", LazyDataView.class); - final VerticalLayout menuLayout = new VerticalLayout(simple, beanGrid, lazy); + final RouterLink lazyFilter = new RouterLink("Lazy Grid Pro with Filter", LazyDataWithFilterView.class); + final VerticalLayout menuLayout = new VerticalLayout(simple, beanGrid, lazy, lazyFilter); addToDrawer(menuLayout); addToNavbar(drawerToggle); } diff --git a/selection-grid-pro-flow/src/main/java/com/vaadin/componentfactory/selectiongridpro/SelectionGridPro.java b/selection-grid-pro-flow/src/main/java/com/vaadin/componentfactory/selectiongridpro/SelectionGridPro.java index 3a92e65..6a1d71b 100644 --- a/selection-grid-pro-flow/src/main/java/com/vaadin/componentfactory/selectiongridpro/SelectionGridPro.java +++ b/selection-grid-pro-flow/src/main/java/com/vaadin/componentfactory/selectiongridpro/SelectionGridPro.java @@ -1,13 +1,5 @@ package com.vaadin.componentfactory.selectiongridpro; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; - /* * #%L * selection-grid-pro-flow @@ -32,12 +24,22 @@ import com.vaadin.flow.component.gridpro.GridPro; import com.vaadin.flow.data.provider.DataCommunicator; import com.vaadin.flow.data.selection.SelectionModel; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; @Tag("vaadin-selection-grid-pro") @CssImport(value = "./styles/grid.css", themeFor = "vaadin-selection-grid-pro") @JsModule("./src/vcf-selection-grid-pro.js") @JsModule("./src/selection-grid-pro.js") public class SelectionGridPro extends GridPro { + + private Integer selectRangeOnlyFromIndex = null; + private Set selectRangeOnlySelection = new HashSet(); /** * @see Grid#Grid() @@ -190,13 +192,49 @@ private Set obtainNewSelectedItems(int fromIndex, int toIndex) { private void selectRangeOnly(int fromIndex, int toIndex) { GridSelectionModel model = getSelectionModel(); if (model instanceof GridMultiSelectionModel) { - Set newSelectedItems = obtainNewSelectedItems(fromIndex, toIndex); + + Set newSelectedItems = new HashSet(); + + int calculatedFromIndex = fromIndex; + + // selectRangeOnlySelection will keep the items already selected so there's no unnecessary + // call to backend done + if (!selectRangeOnlySelection.isEmpty()) { + int firstKey = selectRangeOnlyFromIndex; + int lastKey = selectRangeOnlySelection.size() - 1; + + // recalculate from index so already selected items are not re-selected and no + // unnecessary call to backend is done + if (fromIndex == firstKey && toIndex > lastKey) { + calculatedFromIndex = lastKey + 1; + newSelectedItems.addAll(selectRangeOnlySelection); + } + } + + newSelectedItems.addAll(obtainNewSelectedItems(calculatedFromIndex, toIndex)); HashSet oldSelectedItems = new HashSet<>(getSelectedItems()); oldSelectedItems.removeAll(newSelectedItems); asMultiSelect().updateSelection(newSelectedItems, oldSelectedItems); + + // update selectRangeOnlySelection with new selected items + selectRangeOnlySelection = new HashSet(getSelectedItems()); + selectRangeOnlyFromIndex = fromIndex; } } - + + /** + * Select the range on click and makes sure selectRangeOnlySelection is cleared. + * + * @param fromIndex + * @param toIndex + */ + @ClientCallable + private void selectRangeOnlyOnClick(int fromIndex, int toIndex) { + selectRangeOnlySelection.clear(); + selectRangeOnlyFromIndex = null; + this.selectRangeOnly(fromIndex, toIndex); + } + @Override protected void setSelectionModel(GridSelectionModel model, SelectionMode selectionMode) { if (selectionMode == SelectionMode.MULTI) { diff --git a/selection-grid-pro-flow/src/main/resources/META-INF/resources/frontend/src/helpers.js b/selection-grid-pro-flow/src/main/resources/META-INF/resources/frontend/src/helpers.js index f216dc6..aad612e 100644 --- a/selection-grid-pro-flow/src/main/resources/META-INF/resources/frontend/src/helpers.js +++ b/selection-grid-pro-flow/src/main/resources/META-INF/resources/frontend/src/helpers.js @@ -27,7 +27,7 @@ export function _selectionGridSelectRowWithItem(e, item, index) { // if click select only this row if (!ctrlKey && !e.shiftKey) { if (this.$server) { - this.$server.selectRangeOnly(index, index); + this.$server.selectRangeOnlyOnClick(index, index); } else { this.selectedItems = []; this.selectItem(item); @@ -58,7 +58,7 @@ export function _selectionGridSelectRowWithItem(e, item, index) { } else { if (!ctrlKey) { if (this.$server) { - this.$server.selectRangeOnly(index, index); + this.$server.selectRangeOnlyOnClick(index, index); } } else { if (this.selectedItems && this.selectedItems.some((i) => i.key === item.key)) { @@ -88,7 +88,7 @@ export function _selectionGridRightClickSelectRow(e) { // keep all selected return; } else { - this.$server.selectRangeOnly(index, index); + this.$server.selectRangeOnlyOnClick(index, index); } } }