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

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

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

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;

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

import com.vaadin.data.Binder;
import com.vaadin.data.HasHierarchicalDataProvider;
import com.vaadin.data.SelectionModel;
import com.vaadin.data.TreeData;
import com.vaadin.data.provider.DataGenerator;
import com.vaadin.data.provider.DataProvider;
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.ConnectorEvent;
import com.vaadin.event.ContextClickEvent;
import com.vaadin.event.ExpandEvent;
import com.vaadin.event.ExpandEvent.ExpandListener;
import com.vaadin.event.SerializableEventListener;
import com.vaadin.event.selection.SelectionListener;
import com.vaadin.server.ErrorMessage;
import com.vaadin.server.Resource;
import com.vaadin.shared.EventId;
import com.vaadin.shared.MouseEventDetails;
import com.vaadin.shared.Registration;
import com.vaadin.shared.ui.ContentMode;
import com.vaadin.shared.ui.grid.HeightMode;
import com.vaadin.shared.ui.grid.ScrollDestination;
import com.vaadin.shared.ui.tree.TreeMultiSelectionModelState;
import com.vaadin.shared.ui.tree.TreeRendererState;
import com.vaadin.ui.Component.Focusable;
import com.vaadin.ui.Grid.SelectionMode;
import com.vaadin.ui.components.grid.MultiSelectionModelImpl;
import com.vaadin.ui.components.grid.NoSelectionModel;
import com.vaadin.ui.components.grid.SingleSelectionModelImpl;
import com.vaadin.ui.declarative.DesignAttributeHandler;
import com.vaadin.ui.declarative.DesignContext;
import com.vaadin.ui.renderers.AbstractRenderer;
import com.vaadin.util.ReflectTools;

import elemental.json.JsonObject;

/**
 * Tree component. A Tree can be used to select an item from a hierarchical set
 * of items.
 *
 * @author Vaadin Ltd
 * @since 8.1
 *
 * @param 
 *            the data type
 */
public class Tree extends Composite
        implements HasHierarchicalDataProvider, Focusable {

    @Deprecated
    private static final Method ITEM_CLICK_METHOD = ReflectTools
            .findMethod(ItemClickListener.class, "itemClick", ItemClick.class);
    private Registration contextClickRegistration = null;

    /**
     * A listener for item click events.
     *
     * @param 
     *            the tree item type
     *
     * @see ItemClick
     * @see Registration
     * @since 8.1
     */
    @FunctionalInterface
    public interface ItemClickListener extends SerializableEventListener {
        /**
         * Invoked when this listener receives a item click event from a Tree to
         * which it has been added.
         *
         * @param event
         *            the received event, not {@code null}
         */
        public void itemClick(Tree.ItemClick event);
    }

    /**
     * Tree item click event.
     *
     * @param 
     *            the data type of tree
     * @since 8.1
     */
    public static class ItemClick extends ConnectorEvent {

        private final T item;
        private final MouseEventDetails mouseEventDetails;

        /**
         * Constructs a new item click.
         *
         * @param source
         *            the tree component
         * @param item
         *            the clicked item
         * @param mouseEventDetails
         *            information about the original mouse event (mouse button
         *            clicked, coordinates if available etc.)
         */
        protected ItemClick(Tree source, T item,
                MouseEventDetails mouseEventDetails) {
            super(source);
            this.item = item;
            this.mouseEventDetails = mouseEventDetails;
        }

        /**
         * Returns the clicked item.
         *
         * @return the clicked item
         */
        public T getItem() {
            return item;
        }

        @SuppressWarnings("unchecked")
        @Override
        public Tree getSource() {
            return (Tree) super.getSource();
        }

        /**
         * Returns the mouse event details.
         *
         * @return the mouse event details
         */
        public MouseEventDetails getMouseEventDetails() {
            return mouseEventDetails;
        }
    }

    /**
     * String renderer that handles icon resources and stores their identifiers
     * into data objects.
     *
     * @since 8.1
     */
    public final class TreeRenderer extends AbstractRenderer
            implements DataGenerator {

        /**
         * Constructs a new TreeRenderer.
         */
        protected TreeRenderer() {
            super(String.class);
        }

        private Map resourceKeyMap = new HashMap<>();
        private int counter = 0;

        @Override
        public void generateData(T item, JsonObject jsonObject) {
            Resource resource = iconProvider.apply(item);
            if (resource == null) {
                destroyData(item);
                return;
            }

            if (!resourceKeyMap.containsKey(item)) {
                resourceKeyMap.put(item, "icon" + (counter++));
            }
            setResource(resourceKeyMap.get(item), resource);
            jsonObject.put("itemIcon", resourceKeyMap.get(item));
        }

        @Override
        public void destroyData(T item) {
            if (resourceKeyMap.containsKey(item)) {
                setResource(resourceKeyMap.get(item), null);
                resourceKeyMap.remove(item);
            }
        }

        @Override
        public void destroyAllData() {
            Set keys = new HashSet<>(resourceKeyMap.keySet());
            for (T key : keys) {
                destroyData(key);
            }
        }

        @Override
        protected TreeRendererState getState() {
            return (TreeRendererState) super.getState();
        }

        @Override
        protected TreeRendererState getState(boolean markAsDirty) {
            return (TreeRendererState) super.getState(markAsDirty);
        }
    }

    /**
     * Custom MultiSelectionModel for Tree. TreeMultiSelectionModel does not
     * show selection column.
     *
     * @param 
     *            the tree item type
     *
     * @since 8.1
     */
    public static final class TreeMultiSelectionModel
            extends MultiSelectionModelImpl {

        @Override
        protected TreeMultiSelectionModelState getState() {
            return (TreeMultiSelectionModelState) super.getState();
        }

        @Override
        protected TreeMultiSelectionModelState getState(boolean markAsDirty) {
            return (TreeMultiSelectionModelState) super.getState(markAsDirty);
        }
    }

    private TreeGrid treeGrid = createTreeGrid();

    /**
     * Create inner {@link TreeGrid} object. May be overridden in subclasses.
     *
     * @return new {@link TreeGrid}
     */
    protected TreeGrid createTreeGrid() {
        return new TreeGrid<>();
    }

    private ItemCaptionGenerator captionGenerator = String::valueOf;
    private IconGenerator iconProvider = t -> null;
    private final TreeRenderer renderer;
    private boolean autoRecalculateWidth = true;

    /**
     * Constructs a new Tree Component.
     */
    public Tree() {
        setCompositionRoot(treeGrid);
        renderer = new TreeRenderer();
        treeGrid.getDataCommunicator().addDataGenerator(renderer);
        treeGrid.addColumn(i -> captionGenerator.apply(i), renderer)
                .setId("column");
        treeGrid.setHierarchyColumn("column");
        while (treeGrid.getHeaderRowCount() > 0) {
            treeGrid.removeHeaderRow(0);
        }
        treeGrid.setPrimaryStyleName("v-tree8");
        treeGrid.setRowHeight(28);

        setWidth("100%");
        treeGrid.setHeightUndefined();
        treeGrid.setHeightMode(HeightMode.UNDEFINED);

        treeGrid.addExpandListener(event -> {
            fireExpandEvent(event.getExpandedItem(), event.isUserOriginated());
            if (autoRecalculateWidth) {
                treeGrid.recalculateColumnWidths();
            }
        });
        treeGrid.addCollapseListener(event -> {
            fireCollapseEvent(event.getCollapsedItem(),
                    event.isUserOriginated());
            if (autoRecalculateWidth) {
                treeGrid.recalculateColumnWidths();
            }
        });
        treeGrid.addItemClickListener(event -> fireEvent(new ItemClick<>(this,
                event.getItem(), event.getMouseEventDetails())));
    }

    /**
     * Constructs a new Tree Component with given caption.
     *
     * @param caption
     *            the caption for component
     */
    public Tree(String caption) {
        this();

        setCaption(caption);
    }

    /**
     * Constructs a new Tree Component with given caption and {@code TreeData}.
     *
     * @param caption
     *            the caption for component
     * @param treeData
     *            the tree data for component
     */
    public Tree(String caption, TreeData treeData) {
        this(caption, new TreeDataProvider<>(treeData));
    }

    /**
     * Constructs a new Tree Component with given caption and
     * {@code HierarchicalDataProvider}.
     *
     * @param caption
     *            the caption for component
     * @param dataProvider
     *            the hierarchical data provider for component
     */
    public Tree(String caption, HierarchicalDataProvider dataProvider) {
        this(caption);

        treeGrid.setDataProvider(dataProvider);
    }

    /**
     * Constructs a new Tree Component with given
     * {@code HierarchicalDataProvider}.
     *
     * @param dataProvider
     *            the hierarchical data provider for component
     */
    public Tree(HierarchicalDataProvider dataProvider) {
        this(null, dataProvider);
    }

    @Override
    public HierarchicalDataProvider getDataProvider() {
        return treeGrid.getDataProvider();
    }

    @Override
    public void setDataProvider(DataProvider dataProvider) {
        treeGrid.setDataProvider(dataProvider);
    }

    /**
     * Adds an ExpandListener to this Tree.
     *
     * @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 Tree.
     *
     * @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);
    }

    /**
     * Fires an expand event with given item.
     *
     * @param item
     *            the expanded item
     * @param userOriginated
     *            whether the expand was triggered by a user interaction or the
     *            server
     */
    protected void fireExpandEvent(T item, boolean userOriginated) {
        fireEvent(new ExpandEvent<>(this, item, userOriginated));
    }

    /**
     * Fires a collapse event with given item.
     *
     * @param item
     *            the collapsed item
     * @param userOriginated
     *            whether the collapse was triggered by a user interaction or
     *            the server
     */
    protected void fireCollapseEvent(T item, boolean userOriginated) {
        fireEvent(new CollapseEvent<>(this, item, userOriginated));
    }

    /**
     * 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(T... items) { treeGrid.expand(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) { treeGrid.expand(items); } /** * 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. * * @param items * the items to expand recursively * @param depth * the maximum depth of recursion * @since 8.4 */ public void expandRecursively(Collection items, int depth) { treeGrid.expandRecursively(items, depth); } /** * Collapse the given items. *

* For items that are already collapsed, does nothing. * * @param items * the collection of items to collapse */ public void collapse(T... items) { treeGrid.collapse(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) { treeGrid.collapse(items); } /** * 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. * * @param items * the items to expand recursively * @param depth * the maximum depth of recursion * @since 8.4 */ public void collapseRecursively(Collection items, int depth) { treeGrid.collapseRecursively(items, depth); } /** * 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 treeGrid.isExpanded(item); } /** * This method is a shorthand that delegates to the currently set selection * model. * * @see #getSelectionModel() * * @return set of selected items */ public Set getSelectedItems() { return treeGrid.getSelectedItems(); } /** * This method is a shorthand that delegates to the currently set selection * model. * * @param item * item to select * * @see SelectionModel#select(Object) * @see #getSelectionModel() */ public void select(T item) { treeGrid.select(item); } /** * This method is a shorthand that delegates to the currently set selection * model. * * @param item * item to deselect * * @see SelectionModel#deselect(Object) * @see #getSelectionModel() */ public void deselect(T item) { treeGrid.deselect(item); } /** * Adds a selection listener to the current selection model. *

* NOTE: If selection mode is switched with * {@link #setSelectionMode(SelectionMode)}, then this listener is not * triggered anymore when selection changes! * * @param listener * the listener to add * @return a registration handle to remove the listener * * @throws UnsupportedOperationException * if selection has been disabled with * {@link SelectionMode#NONE} */ public Registration addSelectionListener(SelectionListener listener) { return treeGrid.addSelectionListener(listener); } /** * Use this tree as a multi select in {@link Binder}. Throws * {@link IllegalStateException} if the tree is not using * {@link SelectionMode#MULTI}. * * @return the multi select wrapper that can be used in binder * @since 8.11 */ public MultiSelect asMultiSelect() { return treeGrid.asMultiSelect(); } /** * Use this tree as a single select in {@link Binder}. Throws * {@link IllegalStateException} if the tree is not using * {@link SelectionMode#SINGLE}. * * @return the single select wrapper that can be used in binder */ public SingleSelect asSingleSelect() { return treeGrid.asSingleSelect(); } /** * Returns the selection model for this Tree. * * @return the selection model, not null */ public SelectionModel getSelectionModel() { return treeGrid.getSelectionModel(); } /** * Sets the item caption generator that is used to produce the strings shown * as the text for each item. By default, {@link String#valueOf(Object)} is * used. * * @param captionGenerator * the item caption provider to use, not null */ public void setItemCaptionGenerator( ItemCaptionGenerator captionGenerator) { Objects.requireNonNull(captionGenerator, "Caption generator must not be null"); this.captionGenerator = captionGenerator; treeGrid.getDataCommunicator().reset(); } /** * Sets the item icon generator that is used to produce custom icons for * items. The generator can return null for items with no icon. * * @see IconGenerator * * @param iconGenerator * the item icon generator to set, not null * @throws NullPointerException * if {@code itemIconGenerator} is {@code null} */ public void setItemIconGenerator(IconGenerator iconGenerator) { Objects.requireNonNull(iconGenerator, "Item icon generator must not be null"); this.iconProvider = iconGenerator; treeGrid.getDataCommunicator().reset(); } /** * Sets the item collapse allowed provider for this Tree. 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. * * @param provider * the item collapse allowed provider, not {@code null} */ public void setItemCollapseAllowedProvider( ItemCollapseAllowedProvider provider) { treeGrid.setItemCollapseAllowedProvider(provider); } /** * Sets the style generator that is used for generating class names for * items in this tree. Returning null from the generator results in no * custom style name being set. * * @see StyleGenerator * * @param styleGenerator * the item style generator to set, not {@code null} * @throws NullPointerException * if {@code styleGenerator} is {@code null} */ public void setStyleGenerator(StyleGenerator styleGenerator) { treeGrid.setStyleGenerator(styleGenerator); } /** * Sets the description generator that is used for generating tooltip * descriptions for items. * * @since 8.2 * @param descriptionGenerator * the item description generator to set, or null to * remove a previously set generator */ public void setItemDescriptionGenerator( DescriptionGenerator descriptionGenerator) { treeGrid.setDescriptionGenerator(descriptionGenerator); } /** * Sets the description generator that is used for generating HTML tooltip * descriptions for items. * * @param descriptionGenerator * the item description generator to set, or null to * remove a previously set generator * @param contentMode * how client should interpret textual values * * @since 8.4 */ public void setItemDescriptionGenerator( DescriptionGenerator descriptionGenerator, ContentMode contentMode) { treeGrid.setDescriptionGenerator(descriptionGenerator, contentMode); } /** * Gets the item caption generator. * * @return the item caption generator */ public ItemCaptionGenerator getItemCaptionGenerator() { return captionGenerator; } /** * Gets the item icon generator. * * @see IconGenerator * * @return the item icon generator */ public IconGenerator getItemIconGenerator() { return iconProvider; } /** * Gets the item collapse allowed provider. * * @return the item collapse allowed provider */ public ItemCollapseAllowedProvider getItemCollapseAllowedProvider() { return treeGrid.getItemCollapseAllowedProvider(); } /** * Gets the style generator. * * @see StyleGenerator * * @return the item style generator */ public StyleGenerator getStyleGenerator() { return treeGrid.getStyleGenerator(); } /** * Gets the item description generator. * * @since 8.2 * @return the item description generator */ public DescriptionGenerator getItemDescriptionGenerator() { return treeGrid.getDescriptionGenerator(); } /** * Adds an item click listener. The listener is called when an item of this * {@code Tree} is clicked. * * @param listener * the item click listener, not null * @return a registration for the listener * @see #addContextClickListener */ public Registration addItemClickListener(ItemClickListener listener) { return addListener(ItemClick.class, listener, ITEM_CLICK_METHOD); } /** * Sets the tree's selection mode. *

* The built-in selection modes are: *

    *
  • {@link SelectionMode#SINGLE} the default model
  • *
  • {@link SelectionMode#MULTI}
  • *
  • {@link SelectionMode#NONE} preventing selection
  • *
* * @param selectionMode * the selection mode to switch to, not {@code null} * @return the used selection model * * @see SelectionMode */ public SelectionModel setSelectionMode(SelectionMode selectionMode) { Objects.requireNonNull(selectionMode, "Can not set selection mode to null"); switch (selectionMode) { case MULTI: TreeMultiSelectionModel model = new TreeMultiSelectionModel<>(); treeGrid.setSelectionModel(model); return model; default: return treeGrid.setSelectionMode(selectionMode); } } private SelectionMode getSelectionMode() { SelectionModel selectionModel = getSelectionModel(); SelectionMode mode = null; if (selectionModel.getClass().equals(SingleSelectionModelImpl.class)) { mode = SelectionMode.SINGLE; } else if (selectionModel.getClass() .equals(TreeMultiSelectionModel.class)) { mode = SelectionMode.MULTI; } else if (selectionModel.getClass().equals(NoSelectionModel.class)) { mode = SelectionMode.NONE; } return mode; } @Override public void setCaption(String caption) { treeGrid.setCaption(caption); } @Override public String getCaption() { return treeGrid.getCaption(); } @Override public void setIcon(Resource icon) { treeGrid.setIcon(icon); } @Override public Resource getIcon() { return treeGrid.getIcon(); } @Override public String getStyleName() { return treeGrid.getStyleName(); } @Override public void setStyleName(String style) { treeGrid.setStyleName(style); } @Override public void setStyleName(String style, boolean add) { treeGrid.setStyleName(style, add); } @Override public void addStyleName(String style) { treeGrid.addStyleName(style); } @Override public void removeStyleName(String style) { treeGrid.removeStyleName(style); } @Override public String getPrimaryStyleName() { return treeGrid.getPrimaryStyleName(); } @Override public void setPrimaryStyleName(String style) { treeGrid.setPrimaryStyleName(style); } @Override public void setId(String id) { treeGrid.setId(id); } @Override public String getId() { return treeGrid.getId(); } @Override public void setCaptionAsHtml(boolean captionAsHtml) { treeGrid.setCaptionAsHtml(captionAsHtml); } @Override public boolean isCaptionAsHtml() { return treeGrid.isCaptionAsHtml(); } @Override public void setDescription(String description) { treeGrid.setDescription(description); } @Override public void setDescription(String description, ContentMode mode) { treeGrid.setDescription(description, mode); } @Override public ErrorMessage getErrorMessage() { return treeGrid.getErrorMessage(); } @Override public ErrorMessage getComponentError() { return treeGrid.getComponentError(); } @Override public void setComponentError(ErrorMessage componentError) { treeGrid.setComponentError(componentError); } /** * Sets the height of a row. If -1 (default), the row height is calculated * based on the theme for an empty row before the Tree is displayed. * * @param rowHeight * The height of a row in pixels or -1 for automatic calculation */ public void setRowHeight(double rowHeight) { treeGrid.setRowHeight(rowHeight); } /** * Gets the currently set content mode of the item captions of this Tree. * * @since 8.1.3 * @see ContentMode * @return the content mode of the item captions of this Tree */ public ContentMode getContentMode() { return renderer.getState(false).mode; } /** * Sets the content mode of the item caption. * * @see ContentMode * @param contentMode * the content mode */ public void setContentMode(ContentMode contentMode) { renderer.getState().mode = contentMode; } /** * Returns the current state of automatic width recalculation. * * @return {@code true} if enabled; {@code false} if disabled * * @since 8.1.1 */ public boolean isAutoRecalculateWidth() { return autoRecalculateWidth; } /** * Sets the automatic width recalculation on or off. This feature is on by * default. * * @param autoRecalculateWidth * {@code true} to enable recalculation; {@code false} to turn it * off * * @since 8.1.1 */ public void setAutoRecalculateWidth(boolean autoRecalculateWidth) { this.autoRecalculateWidth = autoRecalculateWidth; treeGrid.getColumns().get(0) .setMinimumWidthFromContent(autoRecalculateWidth); treeGrid.recalculateColumnWidths(); } /** * Adds a context click listener that gets notified when a context click * happens. * * @param listener * the context click listener to add, not null actual event * provided to the listener is {@link TreeContextClickEvent} * @return a registration object for removing the listener * * @since 8.1 * @see #addItemClickListener * @see Registration */ @Override public Registration addContextClickListener( ContextClickEvent.ContextClickListener listener) { Registration registration = addListener(EventId.CONTEXT_CLICK, ContextClickEvent.class, listener, ContextClickEvent.CONTEXT_CLICK_METHOD); setupContextClickListener(); return () -> { registration.remove(); setupContextClickListener(); }; } @Override @Deprecated public void removeContextClickListener( ContextClickEvent.ContextClickListener listener) { super.removeContextClickListener(listener); setupContextClickListener(); } @Override public void writeDesign(Element design, DesignContext designContext) { super.writeDesign(design, designContext); Attributes attrs = design.attributes(); SelectionMode mode = getSelectionMode(); if (mode != null) { DesignAttributeHandler.writeAttribute("selection-mode", attrs, mode, SelectionMode.SINGLE, SelectionMode.class, designContext); } DesignAttributeHandler.writeAttribute("content-mode", attrs, getContentMode(), ContentMode.TEXT, ContentMode.class, designContext); if (designContext.shouldWriteData(this)) { writeItems(design, designContext); } } private void writeItems(Element design, DesignContext designContext) { getDataProvider().fetch(new HierarchicalQuery<>(null, null)) .forEach(item -> writeItem(design, designContext, item, null)); } private void writeItem(Element design, DesignContext designContext, T item, T parent) { Element itemElement = design.appendElement("node"); itemElement.attr("item", serializeDeclarativeRepresentation(item)); if (parent != null) { itemElement.attr("parent", serializeDeclarativeRepresentation(parent)); } if (getSelectionModel().isSelected(item)) { itemElement.attr("selected", true); } Resource icon = getItemIconGenerator().apply(item); DesignAttributeHandler.writeAttribute("icon", itemElement.attributes(), icon, null, Resource.class, designContext); String text = getItemCaptionGenerator().apply(item); itemElement.html( Optional.ofNullable(text).map(Object::toString).orElse("")); getDataProvider().fetch(new HierarchicalQuery<>(null, item)).forEach( childItem -> writeItem(design, designContext, childItem, item)); } @Override public void readDesign(Element design, DesignContext designContext) { super.readDesign(design, designContext); Attributes attrs = design.attributes(); if (attrs.hasKey("selection-mode")) { setSelectionMode(DesignAttributeHandler.readAttribute( "selection-mode", attrs, SelectionMode.class)); } if (attrs.hasKey("content-mode")) { setContentMode(DesignAttributeHandler.readAttribute("content-mode", attrs, ContentMode.class)); } readItems(design.children()); } private void readItems(Elements bodyItems) { if (bodyItems.isEmpty()) { return; } DeclarativeValueProvider valueProvider = new DeclarativeValueProvider<>(); setItemCaptionGenerator(item -> valueProvider.apply(item)); DeclarativeIconGenerator iconGenerator = new DeclarativeIconGenerator<>( item -> null); setItemIconGenerator(iconGenerator); getSelectionModel().deselectAll(); List selectedItems = new ArrayList<>(); TreeData data = new TreeData(); for (Element row : bodyItems) { 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); } valueProvider.addValue(item, row.html()); iconGenerator.setIcon(item, DesignAttributeHandler .readAttribute("icon", row.attributes(), Resource.class)); } setDataProvider(new TreeDataProvider<>(data)); selectedItems.forEach(getSelectionModel()::select); } /** * Deserializes a string to a data item. Used when reading from the * declarative format of this Tree. *

* 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}. * * @since 8.1.3 * * @see #serializeDeclarativeRepresentation(Object) * * @param item * string to deserialize * @throws ClassCastException * if type {@code T} is not a {@link String} * @return deserialized item */ @SuppressWarnings("unchecked") protected T deserializeDeclarativeRepresentation(String item) { if (item == null) { return (T) new String(UUID.randomUUID().toString()); } return (T) new String(item); } /** * Serializes an {@code item} to a string. Used when saving this Tree to its * declarative format. *

* Default implementation delegates a call to {@code item.toString()}. * * @since 8.1.3 * * @see #deserializeDeclarativeRepresentation(String) * * @param item * a data item * @return string representation of the {@code item}. */ protected String serializeDeclarativeRepresentation(T item) { return item.toString(); } private void setupContextClickListener() { if (hasListeners(ContextClickEvent.class)) { if (contextClickRegistration == null) { contextClickRegistration = treeGrid .addContextClickListener(event -> { T item = null; if (event instanceof Grid.GridContextClickEvent) { item = ((Grid.GridContextClickEvent) event) .getItem(); } fireEvent(new TreeContextClickEvent<>(this, event.getMouseEventDetails(), item)); }); } } else if (contextClickRegistration != null) { contextClickRegistration.remove(); contextClickRegistration = null; } } /** * ContextClickEvent for the Tree Component. *

* Usage: * *

     * tree.addContextClickListener(event -> Notification.show(
     *         ((TreeContextClickEvent<Person>) event).getItem() + " Clicked"));
     * 
* * @param * the tree bean type * @since 8.1 */ public static class TreeContextClickEvent extends ContextClickEvent { private final T item; /** * Creates a new context click event. * * @param source * the tree where the context click occurred * @param mouseEventDetails * details about mouse position * @param item * the item which was clicked or {@code null} if the click * happened outside any item */ public TreeContextClickEvent(Tree source, MouseEventDetails mouseEventDetails, T item) { super(source, mouseEventDetails); this.item = item; } /** * Returns the item of context clicked row. * * @return clicked item; {@code null} the click happened outside any * item */ public T getItem() { return item; } @Override public Tree getComponent() { return (Tree) super.getComponent(); } } /** * Scrolls to a certain item, using {@link ScrollDestination#ANY}. *

* If the item has an open details row, its size will also be taken into * account. * * @param row * zero based index of the item to scroll to in the current view. * @throws IllegalArgumentException * if the provided row is outside the item range * @since 8.2 */ public void scrollTo(int row) throws IllegalArgumentException { treeGrid.scrollTo(row, ScrollDestination.ANY); } /** * Scrolls to a certain item, using user-specified scroll destination. *

* If the item has an open details row, its size will also be taken into * account. * * @param row * zero based index of the item to scroll to in the current view. * @param destination * value specifying desired position of scrolled-to row, not * {@code null} * @throws IllegalArgumentException * if the provided row is outside the item range * @since 8.2 */ public void scrollTo(int row, ScrollDestination destination) { treeGrid.scrollTo(row, destination); } /** * Scrolls to the beginning of the first data row. * * @since 8.2 */ public void scrollToStart() { treeGrid.scrollToStart(); } /** * Scrolls to the end of the last data row. * * @since 8.2 */ public void scrollToEnd() { treeGrid.scrollToEnd(); } @Override public int getTabIndex() { return treeGrid.getTabIndex(); } @Override public void setTabIndex(int tabIndex) { treeGrid.setTabIndex(tabIndex); } @Override public void focus() { treeGrid.focus(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy