Skip to content

Commit

Permalink
feat: notify component about identifier provider changes (#13902)
Browse files Browse the repository at this point in the history
Components like Grid or MultiSelectComboBox are supposed to reuse the identifier provider of the Flow data classes to identify items, for example to manage a selection state. However retrieving the identifier provider from a component is problematic at the moment as developers can set an identifier provider on any data view, and there is no way to react to that apart from extending individual implementations of data view classes to implement a custom notification mechanism that the setIdentifierProvider method was called.

This change introduces a IdentifierProviderChangeEvent that is fired when the developer sets an identifier provider on a data view. Components like Grid or MultiSelectComboBox can listen for this event, and retrieve the new identifier provider from it.
  • Loading branch information
sissbruecker committed Jun 6, 2022
1 parent 5b22525 commit 44c0e4d
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 0 deletions.
Expand Up @@ -143,6 +143,8 @@ public void setIdentifierProvider(
"Item identity provider cannot be null");
ComponentUtil.setData(component, IdentifierProvider.class,
identifierProvider);
ComponentUtil.fireEvent(component, new IdentifierProviderChangeEvent<>(
component, identifierProvider));
}

@SuppressWarnings("unchecked")
Expand All @@ -162,6 +164,28 @@ protected IdentifierProvider<T> getIdentifierProvider() {
}
}

/**
* Add an identifier provider change listener that is fired when a custom
* identifier provider is set with
* {@link #setIdentifierProvider(IdentifierProvider)}.
* <p>
* Can be used by components to get notified that a new identifier provider
* has been set through the data view.
*
* @param listener
* identifier provider change listener to register
* @return registration for removing the listener
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public Registration addIdentifierProviderChangeListener(
ComponentEventListener<IdentifierProviderChangeEvent<T, ?>> listener) {
Objects.requireNonNull(listener,
"IdentifierProviderChangeListener cannot be null");
return ComponentUtil.addListener(component,
IdentifierProviderChangeEvent.class,
(ComponentEventListener) listener);
}

protected boolean equals(T item, T compareTo) {
return Objects.equals(
Objects.requireNonNull(getIdentifierProvider().apply(item),
Expand Down
@@ -0,0 +1,43 @@
package com.vaadin.flow.data.provider;

import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.ComponentEvent;

/**
* Event notifying the component that its identifier provider has been changed
* through a data view.
*
* @param <T>
* the type of item used by the identifier provider
* @param <C>
* the event source type
*/
public class IdentifierProviderChangeEvent<T, C extends Component>
extends ComponentEvent<C> {

private final IdentifierProvider<T> identifierProvider;

/**
* Creates a new event using the given source and the new identifier
* provider.
*
* @param source
* the source component
* @param identifierProvider
* the new identifier provider
*/
public IdentifierProviderChangeEvent(C source,
IdentifierProvider<T> identifierProvider) {
super(source, false);
this.identifierProvider = identifierProvider;
}

/**
* Returns the new identifier provider for the component.
*
* @return the new identifier provider
*/
public IdentifierProvider<T> getIdentifierProvider() {
return identifierProvider;
}
}
Expand Up @@ -27,11 +27,13 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.vaadin.flow.component.ComponentEventListener;
import com.vaadin.flow.component.Tag;
import com.vaadin.flow.function.SerializableBiConsumer;
import com.vaadin.flow.function.SerializableComparator;
import com.vaadin.flow.function.SerializablePredicate;
import com.vaadin.flow.function.ValueProvider;
import com.vaadin.flow.shared.Registration;
import com.vaadin.flow.tests.data.bean.Item;
import org.junit.Assert;
import org.junit.Before;
Expand Down Expand Up @@ -285,6 +287,37 @@ public void setIdentifierProvider_dataProviderHasChanged_identifierProviderRetai
beanDataView.contains(new Item(4L, "non present", "descr1")));
}

@SuppressWarnings({ "unchecked", "rawtypes" })
@Test
public void setIdentifierProvider_firesIdentifierProviderChangeEvent() {
ComponentEventListener mockEventListener = Mockito
.mock(ComponentEventListener.class);
beanDataView.addIdentifierProviderChangeListener(mockEventListener);
beanDataView.setIdentifierProvider(Item::getId);

Mockito.verify(mockEventListener, Mockito.times(1))
.onComponentEvent(Mockito.any());
}

@Test(expected = NullPointerException.class)
public void addIdentifierProviderChangeListener_doesNotAcceptNull() {
beanDataView.addIdentifierProviderChangeListener(null);
}

@SuppressWarnings({ "unchecked", "rawtypes" })
@Test()
public void addIdentifierProviderChangeListener_removeListener_listenerIsNotNotified() {
ComponentEventListener mockEventListener = Mockito
.mock(ComponentEventListener.class);
Registration registration = beanDataView
.addIdentifierProviderChangeListener(mockEventListener);
registration.remove();
beanDataView.setIdentifierProvider(Item::getId);

Mockito.verify(mockEventListener, Mockito.times(0))
.onComponentEvent(Mockito.any());
}

@Test
public void contains_filterApplied_itemFilteredOut() {
Assert.assertTrue(beanDataView.contains(new Item(1L, "value1")));
Expand Down

0 comments on commit 44c0e4d

Please sign in to comment.