All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.vaadin.flow.component.listbox.ListBoxBase Maven / Gradle / Ivy

There is a newer version: 24.5.5
Show newest version
/*
 * Copyright 2000-2024 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.component.listbox;

import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

import com.vaadin.flow.component.AbstractSinglePropertyField;
import com.vaadin.flow.component.AttachEvent;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.ComponentUtil;
import com.vaadin.flow.component.DetachEvent;
import com.vaadin.flow.component.HasAriaLabel;
import com.vaadin.flow.component.HasSize;
import com.vaadin.flow.component.HasStyle;
import com.vaadin.flow.component.ItemLabelGenerator;
import com.vaadin.flow.component.Tag;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.dependency.JsModule;
import com.vaadin.flow.component.dependency.NpmPackage;
import com.vaadin.flow.component.listbox.dataview.ListBoxDataView;
import com.vaadin.flow.component.listbox.dataview.ListBoxListDataView;
import com.vaadin.flow.component.shared.HasTooltip;
import com.vaadin.flow.data.binder.HasItemComponents;
import com.vaadin.flow.data.provider.BackEndDataProvider;
import com.vaadin.flow.data.provider.DataChangeEvent.DataRefreshEvent;
import com.vaadin.flow.data.provider.DataProvider;
import com.vaadin.flow.data.provider.DataProviderWrapper;
import com.vaadin.flow.data.provider.DataViewUtils;
import com.vaadin.flow.data.provider.HasDataView;
import com.vaadin.flow.data.provider.HasListDataView;
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.ListDataProvider;
import com.vaadin.flow.data.provider.ListDataView;
import com.vaadin.flow.data.provider.Query;
import com.vaadin.flow.data.renderer.ComponentRenderer;
import com.vaadin.flow.data.renderer.TextRenderer;
import com.vaadin.flow.function.SerializableBiFunction;
import com.vaadin.flow.function.SerializableConsumer;
import com.vaadin.flow.function.SerializablePredicate;
import com.vaadin.flow.shared.Registration;

/**
 * Base class for the {@link ListBox} and {@link MultiSelectListBox}.
 *
 * @author Vaadin Ltd
 */
@Tag("vaadin-list-box")
@NpmPackage(value = "@vaadin/polymer-legacy-adapter", version = "24.3.19")
@JsModule("@vaadin/polymer-legacy-adapter/style-modules.js")
@NpmPackage(value = "@vaadin/list-box", version = "24.3.19")
@JsModule("@vaadin/list-box/src/vaadin-list-box.js")
public abstract class ListBoxBase, ITEM, VALUE>
        extends AbstractSinglePropertyField
        implements HasAriaLabel, HasItemComponents, HasSize,
        HasListDataView>,
        HasDataView>, HasStyle, HasTooltip {

    private final AtomicReference> dataProvider = new AtomicReference<>(
            DataProvider.ofItems());
    private List items;
    private ItemLabelGenerator itemLabelGenerator = String::valueOf;
    private ComponentRenderer itemRenderer = new TextRenderer<>(
            itemLabelGenerator);
    private SerializablePredicate itemEnabledProvider = item -> isEnabled();
    private Registration dataProviderListenerRegistration;

    private int lastNotifiedDataSize = -1;
    private volatile int lastFetchedDataSize = -1;
    private SerializableConsumer sizeRequest;

    

ListBoxBase(String propertyName, Class

elementPropertyType, VALUE defaultValue, SerializableBiFunction presentationToModel, SerializableBiFunction modelToPresentation) { super(propertyName, defaultValue, elementPropertyType, presentationToModel, modelToPresentation); } /** * Sets a generic data provider for the ListBox to use. *

* Use this method when none of the {@code setItems} methods are applicable, * e.g. when having a data provider with filter that cannot be transformed * to {@code DataProvider}. * * @param dataProvider * DataProvider instance to use, not null */ public void setDataProvider(DataProvider dataProvider) { this.dataProvider.set(Objects.requireNonNull(dataProvider)); DataViewUtils.removeComponentFilterAndSortComparator(this); clear(); rebuild(); setupDataProviderListener(this.dataProvider.get()); } private void setupDataProviderListener(DataProvider dataProvider) { if (dataProviderListenerRegistration != null) { dataProviderListenerRegistration.remove(); } dataProviderListenerRegistration = dataProvider .addDataProviderListener(event -> { if (event instanceof DataRefreshEvent) { refresh(((DataRefreshEvent) event).getItem()); } else { clear(); rebuild(); } }); } @Override protected void onAttach(AttachEvent attachEvent) { super.onAttach(attachEvent); if (getDataProvider() != null) { setupDataProviderListener(getDataProvider()); } } @Override protected void onDetach(DetachEvent detachEvent) { if (dataProviderListenerRegistration != null) { dataProviderListenerRegistration.remove(); dataProviderListenerRegistration = null; } super.onDetach(detachEvent); } /** * Gets the data provider used by this ListBox. * *

* To get information and control over the items in the ListBox, use either * {@link #getListDataView()} or {@link #getGenericDataView()} instead. * * @return the data provider used by this ListBox */ public DataProvider getDataProvider() { return dataProvider.get(); } /** * Returns the item component renderer. * * @return the item renderer * @see #setRenderer */ public ComponentRenderer getItemRenderer() { return itemRenderer; } /** * Sets the item renderer for this ListBox. The renderer is applied to each * item to create a component which represents the item. * * @param itemRenderer * the item renderer, not {@code null} */ public void setRenderer( ComponentRenderer itemRenderer) { this.itemRenderer = Objects.requireNonNull(itemRenderer); getItemComponents().forEach(this::refreshContent); } /** * Sets the item label generator that is used to produce the strings shown * in the ListBox for each item. By default, {@link String#valueOf(Object)} * is used. * * @param itemLabelGenerator * the item label provider to use, not null */ public void setItemLabelGenerator( ItemLabelGenerator itemLabelGenerator) { Objects.requireNonNull(itemLabelGenerator, "The item label generator can not be null"); this.itemLabelGenerator = itemLabelGenerator; setRenderer(new TextRenderer<>(this.itemLabelGenerator)); } /** * Gets the item label generator that is used to produce the strings shown * in the ListBox for each item. * * @return the item label generator used, not null */ public ItemLabelGenerator getItemLabelGenerator() { return itemLabelGenerator; } /** * Sets the item enabled predicate for this ListBox. The predicate is * applied to each item to determine whether the item should be enabled * ({@code true}) or disabled ({@code false}). Disabled items are displayed * as grayed out and the user cannot select them. The default predicate * always returns true (all the items are enabled). * * @param itemEnabledProvider * the item enable predicate, not {@code null} */ public void setItemEnabledProvider( SerializablePredicate itemEnabledProvider) { this.itemEnabledProvider = Objects.requireNonNull(itemEnabledProvider); getItemComponents().forEach(this::refreshEnabled); } /** * Returns the item enabled predicate. * * @return the item enabled predicate * @see #setItemEnabledProvider */ public SerializablePredicate getItemEnabledProvider() { return itemEnabledProvider; } /** * Not supported! *

* Not supported by the client-side web-component, see * issue in * GitHub. * * @throws UnsupportedOperationException */ @Override public void setRequiredIndicatorVisible(boolean requiredIndicatorVisible) { throw new UnsupportedOperationException( "Not supported by the client-side web-component: " + "https://github.com/vaadin/vaadin-list-box/issues/19"); } @SuppressWarnings("unchecked") private void rebuild() { removeAll(); synchronized (dataProvider) { final AtomicInteger itemCounter = new AtomicInteger(0); items = (List) getDataProvider() .fetch(DataViewUtils.getQuery(this)) .collect(Collectors.toList()); items.stream().map(this::createItemComponent).forEach(component -> { add(component); itemCounter.incrementAndGet(); }); lastFetchedDataSize = itemCounter.get(); // Ignore new size requests unless the last one has been executed // so as to avoid multiple beforeClientResponses. if (sizeRequest == null) { sizeRequest = ui -> { fireSizeEvent(); sizeRequest = null; }; // Size event is fired before client response so as to avoid // multiple size change events during server round trips runBeforeClientResponse(sizeRequest); } } } private VaadinItem createItemComponent(ITEM item) { VaadinItem itemComponent = new VaadinItem<>(item); refresh(itemComponent); return itemComponent; } private void refresh(ITEM item) { getItemComponents().stream() .filter(vaadinItem -> getItemId(vaadinItem.getItem()) .equals(getItemId(item))) .findFirst().ifPresent(this::refresh); } private void refresh(VaadinItem itemComponent) { refreshContent(itemComponent); refreshEnabled(itemComponent); } private void refreshContent(VaadinItem itemComponent) { itemComponent.removeAll(); itemComponent .add(itemRenderer.createComponent(itemComponent.getItem())); } @Override public void onEnabledStateChanged(boolean enabled) { super.onEnabledStateChanged(enabled); getItemComponents().forEach(this::refreshEnabled); } private void refreshEnabled(VaadinItem itemComponent) { itemComponent .setEnabled(itemEnabledProvider.test(itemComponent.getItem())); } List getItems() { return items; } @SuppressWarnings("unchecked") List> getItemComponents() { return getChildren().filter(VaadinItem.class::isInstance) .map(component -> (VaadinItem) component) .collect(Collectors.toList()); } /** * Set a generic data provider for the ListBox to use and returns the base * {@link ListBoxDataView} that provides API to get information on the * items. *

* This method should be used only when the data provider type is not either * {@link ListDataProvider} or {@link BackEndDataProvider}. * * @param dataProvider * DataProvider instance to use, not null * @return ListBoxDataView providing information on the data */ @Override public ListBoxDataView setItems( DataProvider dataProvider) { setDataProvider(dataProvider); return getGenericDataView(); } /** * Sets an in-memory data provider for the ListBox to use *

* Note! Using a {@link ListDataProvider} instead of a * {@link InMemoryDataProvider} is recommended to get access to * {@link ListBoxListDataView} API by using * {@link HasListDataView#setItems(ListDataProvider)}. * * @param inMemoryDataProvider * InMemoryDataProvider to use, not null * @return ListBoxDataView providing information on the data */ @Override public ListBoxDataView setItems( InMemoryDataProvider inMemoryDataProvider) { // We don't use DataProvider.withConvertedFilter() here because it's // implementation does not apply the filter converter if Query has a // null filter DataProvider convertedDataProvider = new DataProviderWrapper>( inMemoryDataProvider) { @Override protected SerializablePredicate getFilter( Query query) { // Just ignore the query filter (Void) and apply the // predicate only return Optional.ofNullable(inMemoryDataProvider.getFilter()) .orElse(item -> true); } }; return setItems(convertedDataProvider); } /** * Sets a ListDataProvider for the ListBox to use and returns a * {@link ListDataView} that provides information and allows operations on * the items. * * @param listDataProvider * ListDataProvider providing items to the ListBox. * @return ListBoxListDataView providing access to the items */ @Override public ListBoxListDataView setItems( ListDataProvider listDataProvider) { setDataProvider(listDataProvider); return getListDataView(); } /** * Gets the list data view for the ListBox. This data view should only be * used when the items are in-memory and set with: *

    *
  • {@link #setItems(Collection)}
  • *
  • {@link #setItems(Object[])}
  • *
  • {@link #setItems(ListDataProvider)}
  • *
* If the items are not in-memory an exception is thrown. * * @return the list data view that provides access to the data bound to the * ListBox */ @Override public ListBoxListDataView getListDataView() { return new ListBoxListDataView<>(this::getDataProvider, this, (filter, sorting) -> rebuild()); } /** * Gets the generic data view for the ListBox. This data view should only be * used when {@link #getListDataView()} is not applicable for the underlying * data provider. * * @return the generic DataView instance implementing * {@link ListBoxDataView} */ @Override public ListBoxDataView getGenericDataView() { return new ListBoxDataView<>(this::getDataProvider, this); } private void runBeforeClientResponse(SerializableConsumer command) { getElement().getNode().runWhenAttached(ui -> ui .beforeClientResponse(this, context -> command.accept(ui))); } private void fireSizeEvent() { final int newSize = lastFetchedDataSize; if (lastNotifiedDataSize != newSize) { lastNotifiedDataSize = newSize; fireEvent(new ItemCountChangeEvent<>(this, newSize, false)); } } protected Object getItemId(ITEM item) { return getIdentifierProvider().apply(item); } @SuppressWarnings("unchecked") private IdentifierProvider getIdentifierProvider() { IdentifierProvider identifierProviderObject = (IdentifierProvider) ComponentUtil .getData(this, IdentifierProvider.class); if (identifierProviderObject != null) return identifierProviderObject; DataProvider dataProvider = getDataProvider(); if (dataProvider != null) return dataProvider::getId; return IdentifierProvider.identity(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy