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

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

/*
 * 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.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;

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

import com.vaadin.data.HasHierarchicalDataProvider;
import com.vaadin.data.HasValue;
import com.vaadin.data.PropertyDefinition;
import com.vaadin.data.PropertySet;
import com.vaadin.data.TreeData;
import com.vaadin.data.ValueProvider;
import com.vaadin.data.provider.DataProvider;
import com.vaadin.data.provider.HierarchicalDataCommunicator;
import com.vaadin.data.provider.HierarchicalDataProvider;
import com.vaadin.data.provider.HierarchicalQuery;
import com.vaadin.data.provider.TreeDataProvider;
import com.vaadin.event.CollapseEvent;
import com.vaadin.event.CollapseEvent.CollapseListener;
import com.vaadin.event.ExpandEvent;
import com.vaadin.event.ExpandEvent.ExpandListener;
import com.vaadin.shared.Registration;
import com.vaadin.shared.ui.grid.ScrollDestination;
import com.vaadin.shared.ui.treegrid.FocusParentRpc;
import com.vaadin.shared.ui.treegrid.FocusRpc;
import com.vaadin.shared.ui.treegrid.NodeCollapseRpc;
import com.vaadin.shared.ui.treegrid.TreeGridState;
import com.vaadin.ui.declarative.DesignAttributeHandler;
import com.vaadin.ui.declarative.DesignContext;
import com.vaadin.ui.declarative.DesignFormatter;

/**
 * A grid component for displaying hierarchical tabular data.
 *
 * Visual hierarchy depth positioning of rows is done via styles, see
 * _treegrid.scss from Valo theme.
 *
 * @author Vaadin Ltd
 * @since 8.1
 *
 * @param 
 *            the grid bean type
 */
public class TreeGrid extends Grid
        implements HasHierarchicalDataProvider {

    /**
     * Creates a new {@code TreeGrid} without support for creating columns based
     * on property names. Use an alternative constructor, such as
     * {@link TreeGrid#TreeGrid(Class)}, to create a {@code TreeGrid} that
     * automatically sets up columns based on the type of presented data.
     */
    public TreeGrid() {
        this(new HierarchicalDataCommunicator<>());
    }

    /**
     * Creates a new {@code TreeGrid} that uses reflection based on the provided
     * bean type to automatically set up an initial set of columns. All columns
     * will be configured using the same {@link Object#toString()} renderer that
     * is used by {@link #addColumn(ValueProvider)}.
     *
     * @param beanType
     *            the bean type to use, not {@code null}
     */
    public TreeGrid(Class beanType) {
        super(beanType, new HierarchicalDataCommunicator<>());
        registerTreeGridRpc();
    }

    /**
     * Creates a new {@code TreeGrid} using the given
     * {@code HierarchicalDataProvider}, without support for creating columns
     * based on property names. Use an alternative constructor, such as
     * {@link TreeGrid#TreeGrid(Class)}, to create a {@code TreeGrid} that
     * automatically sets up columns based on the type of presented data.
     *
     * @param dataProvider
     *            the data provider, not {@code null}
     */
    public TreeGrid(HierarchicalDataProvider dataProvider) {
        this();
        setDataProvider(dataProvider);
    }

    /**
     * Creates a {@code TreeGrid} using a custom {@link PropertySet}
     * implementation and custom data communicator.
     * 

* Property set is used for configuring the initial columns and resolving * property names for {@link #addColumn(String)} and * {@link Column#setEditorComponent(HasValue)}. * * @param propertySet * the property set implementation to use, not {@code null} * @param dataCommunicator * the data communicator to use, not {@code null} */ protected TreeGrid(PropertySet propertySet, HierarchicalDataCommunicator dataCommunicator) { super(propertySet, dataCommunicator); registerTreeGridRpc(); } /** * Creates a new TreeGrid with the given data communicator and without * support for creating columns based on property names. * * @param dataCommunicator * the custom data communicator to set */ protected TreeGrid(HierarchicalDataCommunicator dataCommunicator) { this(new PropertySet() { @Override public Stream> getProperties() { // No columns configured by default return Stream.empty(); } @Override public Optional> getProperty(String name) { throw new IllegalStateException( "A TreeGrid created without a bean type class literal or a custom property set" + " doesn't support finding properties by name."); } }, dataCommunicator); } /** * Creates a {@code TreeGrid} using a custom {@link PropertySet} * implementation for creating a default set of columns and for resolving * property names with {@link #addColumn(String)} and * {@link Column#setEditorComponent(HasValue)}. *

* This functionality is provided as static method instead of as a public * constructor in order to make it possible to use a custom property set * without creating a subclass while still leaving the public constructors * focused on the common use cases. * * @see TreeGrid#TreeGrid() * @see TreeGrid#TreeGrid(Class) * * @param * the tree grid bean type * @param propertySet * the property set implementation to use, not {@code null} * @return a new tree grid using the provided property set, not {@code null} */ public static TreeGrid withPropertySet( PropertySet propertySet) { return new TreeGrid(propertySet, new HierarchicalDataCommunicator<>()); } private void registerTreeGridRpc() { registerRpc((NodeCollapseRpc) (rowKey, rowIndex, collapse, userOriginated) -> { T item = getDataCommunicator().getKeyMapper().get(rowKey); if (collapse && getDataCommunicator().isExpanded(item)) { getDataCommunicator().collapse(item, rowIndex); fireCollapseEvent( getDataCommunicator().getKeyMapper().get(rowKey), userOriginated); } else if (!collapse && !getDataCommunicator().isExpanded(item)) { getDataCommunicator().expand(item, rowIndex); fireExpandEvent( getDataCommunicator().getKeyMapper().get(rowKey), userOriginated); } }); registerRpc((FocusParentRpc) (rowKey, cellIndex) -> { Integer parentIndex = getDataCommunicator().getParentIndex( getDataCommunicator().getKeyMapper().get(rowKey)); if (parentIndex != null) { getRpcProxy(FocusRpc.class).focusCell(parentIndex, cellIndex); } }); } /** * This method is inherited from Grid but should never be called directly * with a TreeGrid. */ @Override @Deprecated public void scrollTo(int row) throws IllegalArgumentException { super.scrollTo(row); } /** * This method is inherited from Grid but should never be called directly * with a TreeGrid. */ @Deprecated @Override public void scrollTo(int row, ScrollDestination destination) { super.scrollTo(row, destination); } /** * Adds an ExpandListener to this TreeGrid. * * @see ExpandEvent * * @param listener * the listener to add * @return a registration for the listener */ public Registration addExpandListener(ExpandListener listener) { return addListener(ExpandEvent.class, listener, ExpandListener.EXPAND_METHOD); } /** * Adds a CollapseListener to this TreeGrid. * * @see CollapseEvent * * @param listener * the listener to add * @return a registration for the listener */ public Registration addCollapseListener(CollapseListener listener) { return addListener(CollapseEvent.class, listener, CollapseListener.COLLAPSE_METHOD); } @Override public void setDataProvider(DataProvider dataProvider) { if (!(dataProvider instanceof HierarchicalDataProvider)) { throw new IllegalArgumentException( "TreeGrid only accepts hierarchical data providers"); } super.setDataProvider(dataProvider); } /** * Get the currently set hierarchy column. * * @return the currently set hierarchy column, or {@code null} if no column * has been explicitly set */ public Column getHierarchyColumn() { return getColumnByInternalId(getState(false).hierarchyColumnId); } /** * Set the column that displays the hierarchy of this grid's data. By * default the hierarchy will be displayed in the first column. *

* Setting a hierarchy column by calling this method also sets the column to * be visible and not hidable. *

* Note: Changing the Renderer of the hierarchy column is * not supported. * * @param column * the column to use for displaying hierarchy */ public void setHierarchyColumn(Column column) { Objects.requireNonNull(column, "column may not be null"); if (!getColumns().contains(column)) { throw new IllegalArgumentException( "Given column is not a column of this TreeGrid"); } column.setHidden(false); column.setHidable(false); getState().hierarchyColumnId = getInternalIdForColumn(column); } /** * Set the column that displays the hierarchy of this grid's data. By * default the hierarchy will be displayed in the first column. *

* Setting a hierarchy column by calling this method also sets the column to * be visible and not hidable. *

* Note: Changing the Renderer of the hierarchy column is * not supported. * * @see Column#setId(String) * * @param id * id of the column to use for displaying hierarchy */ public void setHierarchyColumn(String id) { Objects.requireNonNull(id, "id may not be null"); if (getColumn(id) == null) { throw new IllegalArgumentException("No column found for given id"); } setHierarchyColumn(getColumn(id)); } /** * Sets the item collapse allowed provider for this TreeGrid. The provider * should return {@code true} for any item that the user can collapse. *

* Note: This callback will be accessed often when sending * data to the client. The callback should not do any costly operations. *

* This method is a shortcut to method with the same name in * {@link HierarchicalDataCommunicator}. * * @param provider * the item collapse allowed provider, not {@code null} * * @see HierarchicalDataCommunicator#setItemCollapseAllowedProvider(ItemCollapseAllowedProvider) */ public void setItemCollapseAllowedProvider( ItemCollapseAllowedProvider provider) { getDataCommunicator().setItemCollapseAllowedProvider(provider); } /** * Expands the given items. *

* If an item is currently expanded, does nothing. If an item does not have * any children, does nothing. * * @param items * the items to expand */ @SuppressWarnings("unchecked") public void expand(T... items) { expand(Arrays.asList(items)); } /** * Expands the given items. *

* If an item is currently expanded, does nothing. If an item does not have * any children, does nothing. * * @param items * the items to expand */ public void expand(Collection items) { HierarchicalDataCommunicator communicator = getDataCommunicator(); items.forEach(item -> { if (!communicator.isExpanded(item) && communicator.hasChildren(item)) { communicator.expand(item); fireExpandEvent(item, false); } }); } /** * Expands the given items and their children recursively until the given * depth. *

* {@code depth} describes the maximum distance between a given item and its * descendant, meaning that {@code expandRecursively(items, 0)} expands only * the given items while {@code expandRecursively(items, 2)} expands the * given items as well as their children and grandchildren. *

* This method will not fire events for expanded nodes. * * @param items * the items to expand recursively * @param depth * the maximum depth of recursion * @since 8.4 */ public void expandRecursively(Collection items, int depth) { expandRecursively(items.stream(), depth); } /** * Expands the given items and their children recursively until the given * depth. *

* {@code depth} describes the maximum distance between a given item and its * descendant, meaning that {@code expandRecursively(items, 0)} expands only * the given items while {@code expandRecursively(items, 2)} expands the * given items as well as their children and grandchildren. *

* This method will not fire events for expanded nodes. * * @param items * the items to expand recursively * @param depth * the maximum depth of recursion * @since 8.4 */ public void expandRecursively(Stream items, int depth) { if (depth < 0) { return; } HierarchicalDataCommunicator communicator = getDataCommunicator(); // ensure speedy closing in case the stream is connected to IO channels try (Stream stream = items) { stream.forEach(item -> { if (communicator.hasChildren(item)) { communicator.expand(item, false); expandRecursively( getDataProvider().fetchChildren( new HierarchicalQuery<>(null, item)), depth - 1); } }); } getDataProvider().refreshAll(); } /** * Collapse the given items. *

* For items that are already collapsed, does nothing. * * @param items * the collection of items to collapse */ @SuppressWarnings("unchecked") public void collapse(T... items) { collapse(Arrays.asList(items)); } /** * Collapse the given items. *

* For items that are already collapsed, does nothing. * * @param items * the collection of items to collapse */ public void collapse(Collection items) { HierarchicalDataCommunicator communicator = getDataCommunicator(); items.forEach(item -> { if (communicator.isExpanded(item)) { communicator.collapse(item); fireCollapseEvent(item, false); } }); } /** * Collapse the given items and their children recursively until the given * depth. *

* {@code depth} describes the maximum distance between a given item and its * descendant, meaning that {@code collapseRecursively(items, 0)} collapses * only the given items while {@code collapseRecursively(items, 2)} * collapses the given items as well as their children and grandchildren. *

* This method will not fire events for collapsed nodes. * * @param items * the items to collapse recursively * @param depth * the maximum depth of recursion * @since 8.4 */ public void collapseRecursively(Collection items, int depth) { collapseRecursively(items.stream(), depth); } /** * Collapse the given items and their children recursively until the given * depth. *

* {@code depth} describes the maximum distance between a given item and its * descendant, meaning that {@code collapseRecursively(items, 0)} collapses * only the given items while {@code collapseRecursively(items, 2)} * collapses the given items as well as their children and grandchildren. *

* This method will not fire events for collapsed nodes. * * @param items * the items to collapse recursively * @param depth * the maximum depth of recursion * @since 8.4 */ public void collapseRecursively(Stream items, int depth) { if (depth < 0) { return; } HierarchicalDataCommunicator communicator = getDataCommunicator(); // ensure speedy closing in case the stream is connected to IO channels try (Stream stream = items) { stream.forEach(item -> { if (communicator.hasChildren(item)) { collapseRecursively( getDataProvider().fetchChildren( new HierarchicalQuery<>(null, item)), depth - 1); communicator.collapse(item, false); } }); } getDataProvider().refreshAll(); } /** * Returns whether a given item is expanded or collapsed. * * @param item * the item to check * @return true if the item is expanded, false if collapsed */ public boolean isExpanded(T item) { return getDataCommunicator().isExpanded(item); } @Override protected TreeGridState getState() { return (TreeGridState) super.getState(); } @Override protected TreeGridState getState(boolean markAsDirty) { return (TreeGridState) super.getState(markAsDirty); } @Override public HierarchicalDataCommunicator getDataCommunicator() { return (HierarchicalDataCommunicator) super.getDataCommunicator(); } @Override public HierarchicalDataProvider getDataProvider() { if (!(super.getDataProvider() instanceof HierarchicalDataProvider)) { return null; } return (HierarchicalDataProvider) super.getDataProvider(); } @Override protected void doReadDesign(Element design, DesignContext context) { super.doReadDesign(design, context); Attributes attrs = design.attributes(); if (attrs.hasKey("hierarchy-column")) { setHierarchyColumn(DesignAttributeHandler .readAttribute("hierarchy-column", attrs, String.class)); } } @Override protected void readData(Element body, List> providers) { getSelectionModel().deselectAll(); List selectedItems = new ArrayList<>(); TreeData data = new TreeData(); for (Element row : body.children()) { T item = deserializeDeclarativeRepresentation(row.attr("item")); T parent = null; if (row.hasAttr("parent")) { parent = deserializeDeclarativeRepresentation( row.attr("parent")); } data.addItem(parent, item); if (row.hasAttr("selected")) { selectedItems.add(item); } Elements cells = row.children(); int i = 0; for (Element cell : cells) { providers.get(i).addValue(item, cell.html()); i++; } } setDataProvider(new TreeDataProvider<>(data)); selectedItems.forEach(getSelectionModel()::select); } @Override protected void doWriteDesign(Element design, DesignContext designContext) { super.doWriteDesign(design, designContext); if (getColumnByInternalId(getState(false).hierarchyColumnId) != null) { String hierarchyColumn = getColumnByInternalId( getState(false).hierarchyColumnId).getId(); DesignAttributeHandler.writeAttribute("hierarchy-column", design.attributes(), hierarchyColumn, null, String.class, designContext); } } @Override protected void writeData(Element body, DesignContext designContext) { // ensure speedy closing in case the stream is connected to IO channels try (Stream stream = getDataProvider() .fetch(new HierarchicalQuery<>(null, null))) { stream.forEach(item -> writeRow(body, item, null, designContext)); } } private void writeRow(Element container, T item, T parent, DesignContext context) { Element tableRow = container.appendElement("tr"); tableRow.attr("item", serializeDeclarativeRepresentation(item)); if (parent != null) { tableRow.attr("parent", serializeDeclarativeRepresentation(parent)); } if (getSelectionModel().isSelected(item)) { tableRow.attr("selected", true); } for (Column column : getColumns()) { Object value = column.getValueProvider().apply(item); tableRow.appendElement("td") .append(Optional.ofNullable(value).map(Object::toString) .map(DesignFormatter::encodeForTextNode) .orElse("")); } // ensure speedy closing in case the stream is connected to IO channels try (Stream stream = getDataProvider() .fetch(new HierarchicalQuery<>(null, item))) { stream.forEach( childItem -> writeRow(container, childItem, item, context)); } } /** * Emit an expand event. * * @param item * the item that was expanded * @param userOriginated * whether the expand was triggered by a user interaction or the * server */ private void fireExpandEvent(T item, boolean userOriginated) { fireEvent(new ExpandEvent<>(this, item, userOriginated)); } /** * Emit a collapse event. * * @param item * the item that was collapsed * @param userOriginated * whether the collapse was triggered by a user interaction or * the server */ private void fireCollapseEvent(T item, boolean userOriginated) { fireEvent(new CollapseEvent<>(this, item, userOriginated)); } /** * Gets the item collapse allowed provider. * * @return the item collapse allowed provider */ public ItemCollapseAllowedProvider getItemCollapseAllowedProvider() { return getDataCommunicator().getItemCollapseAllowedProvider(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy