/* * Copyright 2000-2018 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.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.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 { @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 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(); } }