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

com.vaadin.ui.AbstractListing Maven / Gradle / Ivy

There is a newer version: 8.27.3
Show newest version
/*
 * Copyright (C) 2000-2024 Vaadin Ltd
 *
 * This program is available under Vaadin Commercial License and Service Terms.
 *
 * See  for the full
 * license.
 */
package com.vaadin.ui;

import java.util.Objects;
import java.util.stream.Stream;

import org.jsoup.nodes.Attributes;
import org.jsoup.nodes.Element;

import com.vaadin.data.HasDataProvider;
import com.vaadin.data.HasFilterableDataProvider;
import com.vaadin.data.HasItems;
import com.vaadin.data.provider.DataCommunicator;
import com.vaadin.data.provider.DataGenerator;
import com.vaadin.data.provider.DataProvider;
import com.vaadin.data.provider.Query;
import com.vaadin.server.AbstractExtension;
import com.vaadin.server.Resource;
import com.vaadin.server.SerializableConsumer;
import com.vaadin.shared.extension.abstractlisting.AbstractListingExtensionState;
import com.vaadin.shared.ui.abstractlisting.AbstractListingState;
import com.vaadin.ui.Component.Focusable;
import com.vaadin.ui.declarative.DesignAttributeHandler;
import com.vaadin.ui.declarative.DesignContext;
import com.vaadin.ui.declarative.DesignException;
import com.vaadin.ui.declarative.DesignFormatter;

/**
 * A base class for listing components. Provides common handling for fetching
 * backend data items, selection logic, and server-client communication.
 * 

* Note: concrete component implementations should implement * the {@link HasDataProvider} or {@link HasFilterableDataProvider} interface. * * @author Vaadin Ltd. * @since 8.0 * * @param * the item data type * */ public abstract class AbstractListing extends AbstractComponent implements Focusable, HasItems { /** * The item icon caption provider. */ private ItemCaptionGenerator itemCaptionGenerator = String::valueOf; /** * The item icon provider. It is up to the implementing class to support * this or not. */ private IconGenerator itemIconGenerator = item -> null; /** * A helper base class for creating extensions for Listing components. This * class provides helpers for accessing the underlying parts of the * component and its communication mechanism. * * @param * the listing item type */ public abstract static class AbstractListingExtension extends AbstractExtension implements DataGenerator { /** * Adds this extension to the given parent listing. * * @param listing * the parent component to add to */ public void extend(AbstractListing listing) { super.extend(listing); listing.addDataGenerator(this); } @Override public void remove() { getParent().removeDataGenerator(this); super.remove(); } /** * Gets a data object based on its client-side identifier key. * * @param key * key for data object * @return the data object */ protected T getData(String key) { return getParent().getDataCommunicator().getKeyMapper().get(key); } @Override @SuppressWarnings("unchecked") public AbstractListing getParent() { return (AbstractListing) super.getParent(); } /** * A helper method for refreshing the client-side representation of a * single data item. * * @param item * the item to refresh */ protected void refresh(T item) { getParent().getDataCommunicator().refresh(item); } @Override protected AbstractListingExtensionState getState() { return (AbstractListingExtensionState) super.getState(); } @Override protected AbstractListingExtensionState getState(boolean markAsDirty) { return (AbstractListingExtensionState) super.getState(markAsDirty); } } private final DataCommunicator dataCommunicator; /** * Creates a new {@code AbstractListing} with a default data communicator. *

*/ protected AbstractListing() { this(new DataCommunicator<>()); } /** * Creates a new {@code AbstractListing} with the given custom data * communicator. *

* Note: This method is for creating an * {@code AbstractListing} with a custom communicator. In the common case * {@link AbstractListing#AbstractListing()} should be used. *

* * @param dataCommunicator * the data communicator to use, not null */ protected AbstractListing(DataCommunicator dataCommunicator) { Objects.requireNonNull(dataCommunicator, "dataCommunicator cannot be null"); this.dataCommunicator = dataCommunicator; addExtension(dataCommunicator); } protected void internalSetDataProvider(DataProvider dataProvider) { internalSetDataProvider(dataProvider, null); } protected SerializableConsumer internalSetDataProvider( DataProvider dataProvider, F initialFilter) { return getDataCommunicator().setDataProvider(dataProvider, initialFilter); } protected DataProvider internalGetDataProvider() { return getDataCommunicator().getDataProvider(); } /** * Gets the item caption generator that is used to produce the strings shown * in the combo box for each item. * * @return the item caption generator used, not null */ protected ItemCaptionGenerator getItemCaptionGenerator() { return itemCaptionGenerator; } /** * Sets the item caption generator that is used to produce the strings shown * in the combo box for each item. By default, * {@link String#valueOf(Object)} is used. * * @param itemCaptionGenerator * the item caption provider to use, not null */ protected void setItemCaptionGenerator( ItemCaptionGenerator itemCaptionGenerator) { Objects.requireNonNull(itemCaptionGenerator, "Item caption generators must not be null"); this.itemCaptionGenerator = itemCaptionGenerator; getDataCommunicator().reset(); } /** * Sets the item icon generator that is used to produce custom icons for * shown items. The generator can return null for items with no icon. *

* Implementations that support item icons make this method public. * * @see IconGenerator * * @param itemIconGenerator * the item icon generator to set, not null * @throws NullPointerException * if {@code itemIconGenerator} is {@code null} */ protected void setItemIconGenerator(IconGenerator itemIconGenerator) { Objects.requireNonNull(itemIconGenerator, "Item icon generator must not be null"); this.itemIconGenerator = itemIconGenerator; getDataCommunicator().reset(); } /** * Gets the currently used item icon generator. The default item icon * provider returns null for all items, resulting in no icons being used. *

* Implementations that support item icons make this method public. * * @see IconGenerator * @see #setItemIconGenerator(IconGenerator) * * @return the currently used item icon generator, not null */ protected IconGenerator getItemIconGenerator() { return itemIconGenerator; } /** * Adds the given data generator to this listing. If the generator was * already added, does nothing. * * @param generator * the data generator to add, not null */ protected void addDataGenerator(DataGenerator generator) { getDataCommunicator().addDataGenerator(generator); } /** * Removes the given data generator from this listing. If this listing does * not have the generator, does nothing. * * @param generator * the data generator to remove, not null */ protected void removeDataGenerator(DataGenerator generator) { getDataCommunicator().removeDataGenerator(generator); } /** * Returns the data communicator of this listing. * * @return the data communicator, not null */ public DataCommunicator getDataCommunicator() { return dataCommunicator; } @Override public void writeDesign(Element design, DesignContext designContext) { super.writeDesign(design, designContext); doWriteDesign(design, designContext); } /** * Writes listing specific state into the given design. *

* This method is separated from * {@link #writeDesign(Element, DesignContext)} to be overridable in * subclasses that need to replace this, but still must be able to call * {@code super.writeDesign(...)}. * * @see #doReadDesign(Element, DesignContext) * * @param design * The element to write the component state to. Any previous * attributes or child nodes are not cleared. * @param designContext * The DesignContext instance used for writing the design * */ protected void doWriteDesign(Element design, DesignContext designContext) { // Write options if warranted if (designContext.shouldWriteData(this)) { writeItems(design, designContext); } AbstractListing select = designContext.getDefaultInstance(this); Attributes attr = design.attributes(); DesignAttributeHandler.writeAttribute("readonly", attr, isReadOnly(), select.isReadOnly(), Boolean.class, designContext); } /** * Writes the data source items to a design. Hierarchical select components * should override this method to only write the root items. * * @param design * the element into which to insert the items * @param context * the DesignContext instance used in writing */ protected void writeItems(Element design, DesignContext context) { // ensure speedy closing in case the fetch opens any IO channels try (Stream stream = internalGetDataProvider() .fetch(new Query<>())) { stream.forEach(item -> writeItem(design, item, context)); } } /** * Writes a data source Item to a design. Hierarchical select components * should override this method to recursively write any child items as well. * * @param design * the element into which to insert the item * @param item * the item to write * @param context * the DesignContext instance used in writing * @return a JSOUP element representing the {@code item} */ protected Element writeItem(Element design, T item, DesignContext context) { Element element = design.appendElement("option"); String caption = getItemCaptionGenerator().apply(item); if (caption != null) { element.html(DesignFormatter.encodeForTextNode(caption)); } else { element.html(DesignFormatter.encodeForTextNode(item.toString())); } element.attr("item", serializeDeclarativeRepresentation(item)); Resource icon = getItemIconGenerator().apply(item); if (icon != null) { DesignAttributeHandler.writeAttribute("icon", element.attributes(), icon, null, Resource.class, context); } return element; } @Override public void readDesign(Element design, DesignContext context) { super.readDesign(design, context); doReadDesign(design, context); } /** * Reads the listing specific state from the given design. *

* This method is separated from {@link #readDesign(Element, DesignContext)} * to be overridable in subclasses that need to replace this, but still must * be able to call {@code super.readDesign(...)}. * * @see #doWriteDesign(Element, DesignContext) * * @param design * The element to obtain the state from * @param context * The DesignContext instance used for parsing the design */ protected void doReadDesign(Element design, DesignContext context) { Attributes attr = design.attributes(); if (attr.hasKey("readonly")) { setReadOnly(DesignAttributeHandler.readAttribute("readonly", attr, Boolean.class)); } setItemCaptionGenerator( new DeclarativeCaptionGenerator<>(getItemCaptionGenerator())); setItemIconGenerator( new DeclarativeIconGenerator<>(getItemIconGenerator())); readItems(design, context); } /** * Reads the data source items from the {@code design}. * * @param design * The element to obtain the state from * @param context * The DesignContext instance used for parsing the design */ protected abstract void readItems(Element design, DesignContext context); /** * Reads an Item from a design and inserts it into the data source. *

* Doesn't care about selection/value (if any). * * @param child * a child element representing the item * @param context * the DesignContext instance used in parsing * @return the item id of the new item * * @throws DesignException * if the tag name of the {@code child} element is not * {@code option}. */ @SuppressWarnings({ "rawtypes", "unchecked" }) protected T readItem(Element child, DesignContext context) { if (!"option".equals(child.tagName())) { throw new DesignException("Unrecognized child element in " + getClass().getSimpleName() + ": " + child.tagName()); } String serializedItem = ""; String caption = DesignFormatter.decodeFromTextNode(child.html()); if (child.hasAttr("item")) { serializedItem = child.attr("item"); } T item = deserializeDeclarativeRepresentation(serializedItem); ItemCaptionGenerator captionGenerator = getItemCaptionGenerator(); if (captionGenerator instanceof DeclarativeCaptionGenerator) { ((DeclarativeCaptionGenerator) captionGenerator).setCaption(item, caption); } else { throw new IllegalStateException(String.format("Don't know how " + "to set caption using current caption generator '%s'", captionGenerator.getClass().getName())); } IconGenerator iconGenerator = getItemIconGenerator(); if (child.hasAttr("icon")) { if (iconGenerator instanceof DeclarativeIconGenerator) { ((DeclarativeIconGenerator) iconGenerator).setIcon(item, DesignAttributeHandler.readAttribute("icon", child.attributes(), Resource.class)); } else { throw new IllegalStateException(String.format("Don't know how " + "to set icon using current caption generator '%s'", iconGenerator.getClass().getName())); } } return item; } /** * Deserializes a string to a data item. *

* Default implementation is able to handle only {@link String} as an item * type. There will be a {@link ClassCastException} if {@code T } is not a * {@link String}. * * @see #serializeDeclarativeRepresentation(Object) * * @param item * string to deserialize * @throws ClassCastException * if type {@code T} is not a {@link String} * @return deserialized item */ protected T deserializeDeclarativeRepresentation(String item) { return (T) item; } /** * Serializes an {@code item} to a string for saving declarative format. *

* Default implementation delegates a call to {@code item.toString()}. * * @see #deserializeDeclarativeRepresentation(String) * * @param item * a data item * @return string representation of the {@code item}. */ protected String serializeDeclarativeRepresentation(T item) { return item.toString(); } @Override protected AbstractListingState getState() { return (AbstractListingState) super.getState(); } @Override protected AbstractListingState getState(boolean markAsDirty) { return (AbstractListingState) super.getState(markAsDirty); } @Override public void focus() { super.focus(); } @Override public int getTabIndex() { return getState(false).tabIndex; } @Override public void setTabIndex(int tabIndex) { getState().tabIndex = tabIndex; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy