Skip to content

Commit

Permalink
feat: add getItemIndex to DataView (#18306)
Browse files Browse the repository at this point in the history
* feat: add getItemIndex to DataView

New `getItemIndex` method in `DataView` affects `ListDataView` and `LazyDataView` implementations. `LazyDataView` introduces in addition a new method `setItemIndexProvider(ItemIndexProvider)`.
With `ListDataView`, `getItemIndex` works out-of-the-box with the in-memory data.
With `LazyDataView`, it's required to set item index provider first with `setItemIndexProvider(ItemIndexProvider)`. Otherwise `getItemIndex` will throw `UnsupportedOperationException`. Provider can be implemented to fetch correct item index using item and `Query` parameters. `Query` object is set up to fetch all items with sorting and filter.

Fixes: #18088

* chore: moved ItemIndexProvider to component from DataCommunicator

* chore: added JavaDoc

* chore: pass correct filter for item index provider

* chore: update javadoc

* chore: added since 24.4

* chore: changed to return Optional<Integer>

* Update flow-data/src/main/java/com/vaadin/flow/data/provider/ItemIndexProvider.java

Co-authored-by: Mikhail Shabarov <61410877+mshabarov@users.noreply.github.com>

* chore: fixed formatting

---------

Co-authored-by: Mikhail Shabarov <61410877+mshabarov@users.noreply.github.com>
  • Loading branch information
tltv and mshabarov committed Dec 20, 2023
1 parent 9c0395c commit 0fb22bd
Show file tree
Hide file tree
Showing 9 changed files with 237 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@

package com.vaadin.flow.data.provider;

import java.util.Optional;
import java.util.stream.Stream;

import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.ComponentUtil;
import com.vaadin.flow.function.SerializableConsumer;

/**
Expand Down Expand Up @@ -81,18 +83,34 @@ public T getItem(int index) {
return getDataCommunicator().getItem(index);
}

/**
* Gets the index of the given item by the item index provider set with
* {@link #setItemIndexProvider(ItemIndexProvider)}.
*
* @param item
* item to get index for
* @return index of the item or null if the item is not found
* @throws UnsupportedOperationException
* if the item index provider is not set with
* {@link #setItemIndexProvider(ItemIndexProvider)}
*/
@SuppressWarnings("unchecked")
@Override
public Optional<Integer> getItemIndex(T item) {
if (getItemIndexProvider() == null) {
throw new UnsupportedOperationException(
"getItemIndex method in the LazyDataView requires a callback to fetch the index. Set it with setItemIndexProvider.");
}
return Optional.ofNullable(getItemIndexProvider().apply(item,
getFilteredQueryForAllItems()));
}

@SuppressWarnings("unchecked")
@Override
public Stream<T> getItems() {
DataCommunicator<T> verifiedDataCommunicator = getDataCommunicator();
if (verifiedDataCommunicator.isDefinedSize()) {
return verifiedDataCommunicator.getDataProvider()
.fetch(verifiedDataCommunicator.buildQuery(0,
verifiedDataCommunicator.getItemCount()));
} else {
return verifiedDataCommunicator.getDataProvider().fetch(
verifiedDataCommunicator.buildQuery(0, Integer.MAX_VALUE));
}
return verifiedDataCommunicator.getDataProvider()
.fetch(getQueryForAllItems());
}

@Override
Expand Down Expand Up @@ -131,4 +149,44 @@ public void setItemCountUnknown() {
getDataCommunicator().setDefinedSize(false);
}

@Override
public void setItemIndexProvider(
ItemIndexProvider<T, ?> itemIndexProvider) {
ComponentUtil.setData(component, ItemIndexProvider.class,
itemIndexProvider);
}

/**
* Gets the item index provider for this data view's component.
*
* @return the item index provider. May be null.
*/
@SuppressWarnings("unchecked")
protected ItemIndexProvider<T, ?> getItemIndexProvider() {
return (ItemIndexProvider<T, ?>) ComponentUtil.getData(component,
ItemIndexProvider.class);
}

private Query getQueryForAllItems() {
DataCommunicator<T> verifiedDataCommunicator = getDataCommunicator();
if (verifiedDataCommunicator.isDefinedSize()) {
return verifiedDataCommunicator.buildQuery(0,
verifiedDataCommunicator.getItemCount());
}
return verifiedDataCommunicator.buildQuery(0, Integer.MAX_VALUE);
}

private Query getFilteredQueryForAllItems() {
Query baseQuery = getQueryForAllItems();
if (DataProviderWrapper.class.isAssignableFrom(
dataCommunicator.getDataProvider().getClass())) {
DataProviderWrapper<T, ?, ?> wrapper = (DataProviderWrapper<T, ?, ?>) dataCommunicator
.getDataProvider();
return new Query(baseQuery.getOffset(), baseQuery.getLimit(),
baseQuery.getSortOrders(), baseQuery.getInMemorySorting(),
wrapper.getFilter(baseQuery));
}
return baseQuery;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ public T getItem(int index) {
return getItems().skip(index).findFirst().orElse(null);
}

@Override
public Optional<Integer> getItemIndex(T item) {
int index = getItemIndex(item, getItems());
return index >= 0 ? Optional.of(index) : Optional.empty();
}

@SuppressWarnings("unchecked")
@Override
public Stream<T> getItems() {
Expand All @@ -94,7 +100,7 @@ public Stream<T> getItems() {

@Override
public Optional<T> getNextItem(T item) {
int index = getItemIndex(item);
int index = getItemIndex(item).orElse(-1);
if (index < 0) {
return Optional.empty();
}
Expand All @@ -103,7 +109,7 @@ public Optional<T> getNextItem(T item) {

@Override
public Optional<T> getPreviousItem(T item) {
int index = getItemIndex(item);
int index = getItemIndex(item).orElse(-1);
if (index <= 0) {
return Optional.empty();
}
Expand Down Expand Up @@ -320,10 +326,6 @@ private int getItemIndex(T item, Stream<T> stream) {
return index.get();
}

private int getItemIndex(T item) {
return getItemIndex(item, getItems());
}

private void removeItemIfPresent(T item, ListDataProvider<T> dataProvider) {
dataProvider.getItems().removeIf(nextItem -> equals(item, nextItem));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.vaadin.flow.data.provider;

import java.io.Serializable;
import java.util.Optional;
import java.util.stream.Stream;

import com.vaadin.flow.component.ComponentEventListener;
Expand Down Expand Up @@ -45,6 +46,16 @@ public interface DataView<T> extends Serializable {
*/
T getItem(int index);

/**
* Gets the index of the given item from the data available to the
* component. Data is filtered and sorted the same way as in the component.
*
* @param item
* item to get index for
* @return index of the item or empty optional if the item is not found
*/
Optional<Integer> getItemIndex(T item);

/**
* Get the full data available to the component. Data is filtered and sorted
* the same way as in the component.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright 2000-2023 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/

package com.vaadin.flow.data.provider;

import com.vaadin.flow.function.SerializableBiFunction;

/**
* A callback interface that is used to provide the index of an item.
* <p>
* Callback gives the target item and the query as parameters to fetch the
* index. The 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.
* <p>
* There will be inconsistent index 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 inconsistent index as a result.
* <p>
* Item index provider is only relevant with lazy data view implementations.
*
* @param <T>
* the type of the item
* @param <F>
* the type of the query filter
* @since @since 24.4
*/
@FunctionalInterface
public interface ItemIndexProvider<T, F>
extends SerializableBiFunction<T, Query<T, F>, Integer> {
/**
* Gets the index of the item in the filtered and sorted data set.
* <p>
* There will be inconsistent index 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 inconsistent index as a result.
* <p>
* The query parameter provides a filter object being set with
* {@link ConfigurableFilterDataProvider} or provided by a component, e.g. a
* string filter in ComboBox.
*
* @param item
* Target item to get the index for
* @param query
* Query prepared for fetching all items including filter and
* sorting.
* @return the index of the item in the filtered and sorted data set, or
* null if not found
*/
Integer apply(T item, Query<T, F> query);
}
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,17 @@ public interface LazyDataView<T> extends DataView<T> {
* items and faster scrolling down is desired.
*/
void setItemCountUnknown();

/**
* Sets the item index provider for this lazy data view. The provider is
* used to fetch the index of an item.
* {@link ItemIndexProvider#apply(Object, Query)} is called when
* {@link #getItemIndex(Object)} is called. It gives an item and a
* {@link Query} as parameters. Query is prepared for fetching all items
* including filter and sorting.
*
* @param itemIndexProvider
* the item index provider to use
*/
void setItemIndexProvider(ItemIndexProvider<T, ?> itemIndexProvider);
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Stream;
Expand Down Expand Up @@ -264,6 +265,11 @@ protected Class<?> getSupportedDataProviderType() {
public Item getItem(int index) {
return null;
}

@Override
public Optional<Integer> getItemIndex(Item item) {
return Optional.of(0);
}
}

static class CustomIdentityItemDataProvider extends ListDataProvider<Item> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.vaadin.flow.data.provider;

import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;

Expand Down Expand Up @@ -328,6 +329,28 @@ public void getItem_outsideOfRange_throws() {
dataView.getItem(3);
}

@Test
public void getItemIndex_withoutItemIndexProvider_throwUnsupportedOperationException() {
Assert.assertThrows(UnsupportedOperationException.class,
() -> dataView.getItemIndex("bar"));
}

@Test
public void getItemIndex_itemPresentedInDataSet_indexFound() {
dataView.setItemIndexProvider(
(item, query) -> "bar".equals(item) ? 1 : null);
Assert.assertEquals("Wrong index returned for item", Optional.of(1),
dataView.getItemIndex("bar"));
}

@Test
public void getItemIndex_itemNotPresentedInDataSet_indexNotFound() {
dataView.setItemIndexProvider(
(item, query) -> "bar".equals(item) ? 1 : null);
Assert.assertEquals("Wrong index returned for item", Optional.empty(),
dataView.getItemIndex("notPresent"));
}

@Test
public void refreshItem_itemPresentInDataSet_refreshesItem() {
Item item1 = new Item(0L, "value1");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -905,6 +905,41 @@ public void getItem_indexOutsideOfSize_throwsException() {
dataView.getItem(items.size());
}

@Test
public void getItemIndex_itemPresentedInDataSet_indexFound() {
Assert.assertEquals("Wrong index returned for item", Optional.of(1),
dataView.getItemIndex("middle"));
}

@Test
public void getItemIndex_itemNotPresentedInDataSet_indexNotFound() {
Assert.assertEquals("Wrong index returned for item", Optional.empty(),
dataView.getItemIndex("notPresent"));
}

@Test
public void getItemIndex_filteringApplied_indexFound() {
dataProvider
.setFilter(item -> "first".equals(item) || "last".equals(item));
Assert.assertEquals("Wrong index returned for item", Optional.of(1),
dataView.getItemIndex("last"));
}

@Test
public void getItemIndex_sortingApplied_indexFound() {
dataProvider.setSortOrder(item -> item, SortDirection.DESCENDING);
Assert.assertEquals("Wrong index returned for item", Optional.of(0),
dataView.getItemIndex("middle"));
}

@Test
public void getItemIndex_itemNotPresentedInDataSet_filteringApplied_indexNotFound() {
dataProvider
.setFilter(item -> "first".equals(item) || "last".equals(item));
Assert.assertEquals("Wrong index returned for item", Optional.empty(),
dataView.getItemIndex("middle"));
}

@Test
public void dataViewCreatedAndAPIUsed_beforeSettingDataProvider_verificationPassed() {
// Data provider verification should pass even if the developer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import com.vaadin.flow.data.provider.IdentifierProvider;
import com.vaadin.flow.data.provider.InMemoryDataProvider;
import com.vaadin.flow.data.provider.ItemCountChangeEvent;
import com.vaadin.flow.data.provider.ItemIndexProvider;
import com.vaadin.flow.data.provider.LazyDataView;
import com.vaadin.flow.data.provider.ListDataView;
import com.vaadin.flow.data.provider.SortDirection;
Expand Down Expand Up @@ -86,6 +87,12 @@ public void setItemCountUnknown() {

}

@Override
public void setItemIndexProvider(
ItemIndexProvider<String, ?> itemIndexProvider) {

}

@Override
public int getItemCount() {
return 0;
Expand Down Expand Up @@ -205,6 +212,11 @@ public String getItem(int index) {
return null;
}

@Override
public Optional<Integer> getItemIndex(String item) {
return Optional.of(0);
}

@Override
public Stream<String> getItems() {
return null;
Expand Down

0 comments on commit 0fb22bd

Please sign in to comment.