/* * Copyright 2000-2016 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.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Objects; import java.util.Set; import com.vaadin.data.Binder; import com.vaadin.data.HasDataProvider; import com.vaadin.data.SelectionModel; import com.vaadin.data.provider.DataGenerator; import com.vaadin.data.provider.DataProvider; import com.vaadin.data.provider.HierarchicalDataProvider; 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.event.selection.SelectionListener; import com.vaadin.server.Resource; import com.vaadin.shared.Registration; import com.vaadin.shared.ui.grid.HeightMode; import com.vaadin.shared.ui.tree.TreeRendererState; import com.vaadin.ui.Grid.SelectionMode; import com.vaadin.ui.renderers.AbstractRenderer; 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 <T> * the data type */ public class Tree<T> extends Composite implements HasDataProvider<T> { /** * String renderer that handles icon resources and stores their identifiers * into data objects. * * @since 8.1 */ public final class TreeRenderer extends AbstractRenderer<T, String> implements DataGenerator<T> { /** * Constructs a new TreeRenderer. */ protected TreeRenderer() { super(String.class); } private Map<T, String> 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<T> 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); } } private TreeGrid<T> treeGrid = new TreeGrid<>(); private ItemCaptionGenerator<T> captionGenerator = String::valueOf; private IconGenerator<T> iconProvider = t -> null; /** * Constructs a new Tree Component. */ public Tree() { setCompositionRoot(treeGrid); TreeRenderer 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"); setWidth("100%"); treeGrid.setHeightUndefined(); treeGrid.setHeightMode(HeightMode.UNDEFINED); treeGrid.addExpandListener(e -> fireExpandEvent(e.getExpandedItem(), e.isUserOriginated())); treeGrid.addCollapseListener(e -> fireCollapseEvent( e.getCollapsedItem(), e.isUserOriginated())); } /** * Constructs a new Tree Component. * * @param caption * the caption for component */ public Tree(String caption) { this(); setCaption(caption); } @Override public HierarchicalDataProvider<T, ?> getDataProvider() { return treeGrid.getDataProvider(); } @Override public void setDataProvider(DataProvider<T, ?> 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<T> 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<T> 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. * <p> * 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. * <p> * 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<T> items) { treeGrid.expand(items); } /** * Collapse the given items. * <p> * 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. * <p> * For items that are already collapsed, does nothing. * * @param items * the collection of items to collapse */ public void collapse(Collection<T> items) { treeGrid.collapse(items); } /** * This method is a shorthand that delegates to the currently set selection * model. * * @see #getSelectionModel() * * @return set of selected items */ public Set<T> 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. * <p> * <strong>NOTE:</strong> 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<T> 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<T> asSingleSelect() { return treeGrid.asSingleSelect(); } /** * Returns the selection model for this Tree. * * @return the selection model, not <code>null</code> */ public SelectionModel<T> getSelectionModel() { return treeGrid.getSelectionModel(); } @Override public void setItems(Collection<T> items) { treeGrid.setItems(items); } /** * 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 <code>null</code> */ public void setItemCaptionGenerator( ItemCaptionGenerator<T> 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 <code>null</code> for items with no icon. * * @see IconGenerator * * @param iconGenerator * the item icon generator to set, not <code>null</code> * @throws NullPointerException * if {@code itemIconGenerator} is {@code null} */ public void setItemIconGenerator(IconGenerator<T> iconGenerator) { Objects.requireNonNull(iconGenerator, "Item icon generator must not be null"); this.iconProvider = iconGenerator; treeGrid.getDataCommunicator().reset(); } /** * @deprecated This component's height is always set to be undefined. * Calling this method will have no effect. */ @Override @Deprecated public void setHeight(String height) { } /** * @deprecated This component's height is always set to be undefined. * Calling this method will have no effect. */ @Override @Deprecated public void setHeight(float height, Unit unit) { } /** * @deprecated This component's height is always set to be undefined. * Calling this method will have no effect. */ @Override @Deprecated public void setHeightUndefined() { } @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(); } }