| /* |
| * Copyright 2000-2014 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.io.Serializable; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| |
| import com.vaadin.data.Collapsible; |
| import com.vaadin.data.Container; |
| import com.vaadin.data.Container.Hierarchical; |
| import com.vaadin.data.util.ContainerHierarchicalWrapper; |
| import com.vaadin.data.util.HierarchicalContainer; |
| import com.vaadin.data.util.HierarchicalContainerOrderedWrapper; |
| import com.vaadin.server.PaintException; |
| import com.vaadin.server.PaintTarget; |
| import com.vaadin.server.Resource; |
| import com.vaadin.shared.ui.treetable.TreeTableConstants; |
| import com.vaadin.ui.Tree.CollapseEvent; |
| import com.vaadin.ui.Tree.CollapseListener; |
| import com.vaadin.ui.Tree.ExpandEvent; |
| import com.vaadin.ui.Tree.ExpandListener; |
| |
| /** |
| * TreeTable extends the {@link Table} component so that it can also visualize a |
| * hierarchy of its Items in a similar manner that {@link Tree} does. The tree |
| * hierarchy is always displayed in the first actual column of the TreeTable. |
| * <p> |
| * The TreeTable supports the usual {@link Table} features like lazy loading, so |
| * it should be no problem to display lots of items at once. Only required rows |
| * and some cache rows are sent to the client. |
| * <p> |
| * TreeTable supports standard {@link Hierarchical} container interfaces, but |
| * also a more fine tuned version - {@link Collapsible}. A container |
| * implementing the {@link Collapsible} interface stores the collapsed/expanded |
| * state internally and can this way scale better on the server side than with |
| * standard Hierarchical implementations. Developer must however note that |
| * {@link Collapsible} containers can not be shared among several users as they |
| * share UI state in the container. |
| */ |
| @SuppressWarnings({ "serial", "deprecation" }) |
| public class CustomTreeTable extends CustomTable implements Hierarchical { |
| |
| private interface ContainerStrategy extends Serializable { |
| public int size(); |
| |
| public boolean isNodeOpen(Object itemId); |
| |
| public int getDepth(Object itemId); |
| |
| public void toggleChildVisibility(Object itemId); |
| |
| public Object getIdByIndex(int index); |
| |
| public int indexOfId(Object id); |
| |
| public Object nextItemId(Object itemId); |
| |
| public Object lastItemId(); |
| |
| public Object prevItemId(Object itemId); |
| |
| public boolean isLastId(Object itemId); |
| |
| public Collection<?> getItemIds(); |
| |
| public void containerItemSetChange(ItemSetChangeEvent event); |
| } |
| |
| private abstract class AbstractStrategy implements ContainerStrategy { |
| |
| /** |
| * Consider adding getDepth to {@link Collapsible}, might help |
| * scalability with some container implementations. |
| */ |
| |
| @Override |
| public int getDepth(Object itemId) { |
| int depth = 0; |
| Hierarchical hierarchicalContainer = getContainerDataSource(); |
| while (!hierarchicalContainer.isRoot(itemId)) { |
| depth++; |
| itemId = hierarchicalContainer.getParent(itemId); |
| } |
| return depth; |
| } |
| |
| @Override |
| public void containerItemSetChange(ItemSetChangeEvent event) { |
| } |
| |
| } |
| |
| /** |
| * This strategy is used if current container implements {@link Collapsible} |
| * . |
| * |
| * open-collapsed logic diverted to container, otherwise use default |
| * implementations. |
| */ |
| private class CollapsibleStrategy extends AbstractStrategy { |
| |
| private Collapsible c() { |
| return (Collapsible) getContainerDataSource(); |
| } |
| |
| @Override |
| public void toggleChildVisibility(Object itemId) { |
| c().setCollapsed(itemId, !c().isCollapsed(itemId)); |
| } |
| |
| @Override |
| public boolean isNodeOpen(Object itemId) { |
| return !c().isCollapsed(itemId); |
| } |
| |
| @Override |
| public int size() { |
| return CustomTreeTable.super.size(); |
| } |
| |
| @Override |
| public Object getIdByIndex(int index) { |
| return CustomTreeTable.super.getIdByIndex(index); |
| } |
| |
| @Override |
| public int indexOfId(Object id) { |
| return CustomTreeTable.super.indexOfId(id); |
| } |
| |
| @Override |
| public boolean isLastId(Object itemId) { |
| // using the default impl |
| return CustomTreeTable.super.isLastId(itemId); |
| } |
| |
| @Override |
| public Object lastItemId() { |
| // using the default impl |
| return CustomTreeTable.super.lastItemId(); |
| } |
| |
| @Override |
| public Object nextItemId(Object itemId) { |
| return CustomTreeTable.super.nextItemId(itemId); |
| } |
| |
| @Override |
| public Object prevItemId(Object itemId) { |
| return CustomTreeTable.super.prevItemId(itemId); |
| } |
| |
| @Override |
| public Collection<?> getItemIds() { |
| return CustomTreeTable.super.getItemIds(); |
| } |
| |
| } |
| |
| /** |
| * Strategy for Hierarchical but not Collapsible container like |
| * {@link HierarchicalContainer}. |
| * |
| * Store collapsed/open states internally, fool Table to use preorder when |
| * accessing items from container via Ordered/Indexed methods. |
| */ |
| private class HierarchicalStrategy extends AbstractStrategy { |
| |
| private final HashSet<Object> openItems = new HashSet<Object>(); |
| |
| @Override |
| public boolean isNodeOpen(Object itemId) { |
| return openItems.contains(itemId); |
| } |
| |
| @Override |
| public int size() { |
| return getPreOrder().size(); |
| } |
| |
| @Override |
| public Collection<Object> getItemIds() { |
| return Collections.unmodifiableCollection(getPreOrder()); |
| } |
| |
| @Override |
| public boolean isLastId(Object itemId) { |
| if (itemId == null) { |
| return false; |
| } |
| |
| return itemId.equals(lastItemId()); |
| } |
| |
| @Override |
| public Object lastItemId() { |
| if (getPreOrder().size() > 0) { |
| return getPreOrder().get(getPreOrder().size() - 1); |
| } else { |
| return null; |
| } |
| } |
| |
| @Override |
| public Object nextItemId(Object itemId) { |
| int indexOf = getPreOrder().indexOf(itemId); |
| if (indexOf == -1) { |
| return null; |
| } |
| indexOf++; |
| if (indexOf == getPreOrder().size()) { |
| return null; |
| } else { |
| return getPreOrder().get(indexOf); |
| } |
| } |
| |
| @Override |
| public Object prevItemId(Object itemId) { |
| int indexOf = getPreOrder().indexOf(itemId); |
| indexOf--; |
| if (indexOf < 0) { |
| return null; |
| } else { |
| return getPreOrder().get(indexOf); |
| } |
| } |
| |
| @Override |
| public void toggleChildVisibility(Object itemId) { |
| boolean removed = openItems.remove(itemId); |
| if (!removed) { |
| openItems.add(itemId); |
| getLogger().log(Level.FINEST, "Item {0} is now expanded", |
| itemId); |
| } else { |
| getLogger().log(Level.FINEST, "Item {0} is now collapsed", |
| itemId); |
| } |
| clearPreorderCache(); |
| } |
| |
| private void clearPreorderCache() { |
| preOrder = null; // clear preorder cache |
| } |
| |
| List<Object> preOrder; |
| |
| /** |
| * Preorder of ids currently visible |
| * |
| * @return |
| */ |
| private List<Object> getPreOrder() { |
| if (preOrder == null) { |
| preOrder = new ArrayList<Object>(); |
| Collection<?> rootItemIds = getContainerDataSource() |
| .rootItemIds(); |
| for (Object id : rootItemIds) { |
| preOrder.add(id); |
| addVisibleChildTree(id); |
| } |
| } |
| return preOrder; |
| } |
| |
| private void addVisibleChildTree(Object id) { |
| if (isNodeOpen(id)) { |
| Collection<?> children = getContainerDataSource().getChildren( |
| id); |
| if (children != null) { |
| for (Object childId : children) { |
| preOrder.add(childId); |
| addVisibleChildTree(childId); |
| } |
| } |
| } |
| |
| } |
| |
| @Override |
| public int indexOfId(Object id) { |
| return getPreOrder().indexOf(id); |
| } |
| |
| @Override |
| public Object getIdByIndex(int index) { |
| return getPreOrder().get(index); |
| } |
| |
| @Override |
| public void containerItemSetChange(ItemSetChangeEvent event) { |
| // preorder becomes invalid on sort, item additions etc. |
| clearPreorderCache(); |
| super.containerItemSetChange(event); |
| } |
| |
| } |
| |
| /** |
| * Creates an empty TreeTable with a default container. |
| */ |
| public CustomTreeTable() { |
| super(null, new HierarchicalContainer()); |
| } |
| |
| /** |
| * Creates an empty TreeTable with a default container. |
| * |
| * @param caption |
| * the caption for the TreeTable |
| */ |
| public CustomTreeTable(String caption) { |
| this(); |
| setCaption(caption); |
| } |
| |
| /** |
| * Creates a TreeTable instance with given captions and data source. |
| * |
| * @param caption |
| * the caption for the component |
| * @param dataSource |
| * the dataSource that is used to list items in the component |
| */ |
| public CustomTreeTable(String caption, Container dataSource) { |
| super(caption, dataSource); |
| } |
| |
| private ContainerStrategy cStrategy; |
| private Object focusedRowId = null; |
| private Object hierarchyColumnId; |
| |
| /** |
| * The item id that was expanded or collapsed during this request. Reset at |
| * the end of paint and only used for determining if a partial or full paint |
| * should be done. |
| * |
| * Can safely be reset to null whenever a change occurs that would prevent a |
| * partial update from rendering the correct result, e.g. rows added or |
| * removed during an expand operation. |
| */ |
| private Object toggledItemId; |
| private boolean animationsEnabled; |
| private boolean clearFocusedRowPending; |
| |
| /** |
| * If the container does not send item set change events, always do a full |
| * repaint instead of a partial update when expanding/collapsing nodes. |
| */ |
| private boolean containerSupportsPartialUpdates; |
| |
| private ContainerStrategy getContainerStrategy() { |
| if (cStrategy == null) { |
| if (getContainerDataSource() instanceof Collapsible) { |
| cStrategy = new CollapsibleStrategy(); |
| } else { |
| cStrategy = new HierarchicalStrategy(); |
| } |
| } |
| return cStrategy; |
| } |
| |
| @Override |
| protected void paintRowAttributes(PaintTarget target, Object itemId) |
| throws PaintException { |
| super.paintRowAttributes(target, itemId); |
| target.addAttribute("depth", getContainerStrategy().getDepth(itemId)); |
| if (getContainerDataSource().areChildrenAllowed(itemId)) { |
| target.addAttribute("ca", true); |
| target.addAttribute("open", |
| getContainerStrategy().isNodeOpen(itemId)); |
| } |
| } |
| |
| @Override |
| protected void paintRowIcon(PaintTarget target, Object[][] cells, |
| int indexInRowbuffer) throws PaintException { |
| // always paint if present (in parent only if row headers visible) |
| if (getRowHeaderMode() == ROW_HEADER_MODE_HIDDEN) { |
| Resource itemIcon = getItemIcon(cells[CELL_ITEMID][indexInRowbuffer]); |
| if (itemIcon != null) { |
| target.addAttribute("icon", itemIcon); |
| } |
| } else if (cells[CELL_ICON][indexInRowbuffer] != null) { |
| target.addAttribute("icon", |
| (Resource) cells[CELL_ICON][indexInRowbuffer]); |
| } |
| } |
| |
| @Override |
| public void changeVariables(Object source, Map<String, Object> variables) { |
| super.changeVariables(source, variables); |
| |
| if (variables.containsKey("toggleCollapsed")) { |
| String object = (String) variables.get("toggleCollapsed"); |
| Object itemId = itemIdMapper.get(object); |
| toggledItemId = itemId; |
| toggleChildVisibility(itemId, false); |
| if (variables.containsKey("selectCollapsed")) { |
| // ensure collapsed is selected unless opened with selection |
| // head |
| if (isSelectable()) { |
| select(itemId); |
| } |
| } |
| } else if (variables.containsKey("focusParent")) { |
| String key = (String) variables.get("focusParent"); |
| Object refId = itemIdMapper.get(key); |
| Object itemId = getParent(refId); |
| focusParent(itemId); |
| } |
| } |
| |
| private void focusParent(Object itemId) { |
| boolean inView = false; |
| Object inPageId = getCurrentPageFirstItemId(); |
| for (int i = 0; inPageId != null && i < getPageLength(); i++) { |
| if (inPageId.equals(itemId)) { |
| inView = true; |
| break; |
| } |
| inPageId = nextItemId(inPageId); |
| i++; |
| } |
| if (!inView) { |
| setCurrentPageFirstItemId(itemId); |
| } |
| // Select the row if it is selectable. |
| if (isSelectable()) { |
| if (isMultiSelect()) { |
| setValue(Collections.singleton(itemId)); |
| } else { |
| setValue(itemId); |
| } |
| } |
| setFocusedRow(itemId); |
| } |
| |
| private void setFocusedRow(Object itemId) { |
| focusedRowId = itemId; |
| if (focusedRowId == null) { |
| // Must still inform the client that the focusParent request has |
| // been processed |
| clearFocusedRowPending = true; |
| } |
| markAsDirty(); |
| } |
| |
| @Override |
| public void paintContent(PaintTarget target) throws PaintException { |
| if (focusedRowId != null) { |
| target.addAttribute("focusedRow", itemIdMapper.key(focusedRowId)); |
| focusedRowId = null; |
| } else if (clearFocusedRowPending) { |
| // Must still inform the client that the focusParent request has |
| // been processed |
| target.addAttribute("clearFocusPending", true); |
| clearFocusedRowPending = false; |
| } |
| target.addAttribute("animate", animationsEnabled); |
| if (hierarchyColumnId != null) { |
| Object[] visibleColumns2 = getVisibleColumns(); |
| for (int i = 0; i < visibleColumns2.length; i++) { |
| Object object = visibleColumns2[i]; |
| if (hierarchyColumnId.equals(object)) { |
| target.addAttribute( |
| TreeTableConstants.ATTRIBUTE_HIERARCHY_COLUMN_INDEX, |
| i); |
| break; |
| } |
| } |
| } |
| super.paintContent(target); |
| toggledItemId = null; |
| } |
| |
| /* |
| * Override methods for partial row updates and additions when expanding / |
| * collapsing nodes. |
| */ |
| |
| @Override |
| protected boolean isPartialRowUpdate() { |
| return toggledItemId != null && containerSupportsPartialUpdates |
| && !isRowCacheInvalidated(); |
| } |
| |
| @Override |
| protected int getFirstAddedItemIndex() { |
| return indexOfId(toggledItemId) + 1; |
| } |
| |
| @Override |
| protected int getAddedRowCount() { |
| return countSubNodesRecursively(getContainerDataSource(), toggledItemId); |
| } |
| |
| private int countSubNodesRecursively(Hierarchical hc, Object itemId) { |
| int count = 0; |
| // we need the number of children for toggledItemId no matter if its |
| // collapsed or expanded. Other items' children are only counted if the |
| // item is expanded. |
| if (getContainerStrategy().isNodeOpen(itemId) |
| || itemId == toggledItemId) { |
| Collection<?> children = hc.getChildren(itemId); |
| if (children != null) { |
| count += children != null ? children.size() : 0; |
| for (Object id : children) { |
| count += countSubNodesRecursively(hc, id); |
| } |
| } |
| } |
| return count; |
| } |
| |
| @Override |
| protected int getFirstUpdatedItemIndex() { |
| return indexOfId(toggledItemId); |
| } |
| |
| @Override |
| protected int getUpdatedRowCount() { |
| return 1; |
| } |
| |
| @Override |
| protected boolean shouldHideAddedRows() { |
| return !getContainerStrategy().isNodeOpen(toggledItemId); |
| } |
| |
| private void toggleChildVisibility(Object itemId, boolean forceFullRefresh) { |
| getContainerStrategy().toggleChildVisibility(itemId); |
| // ensure that page still has first item in page, DON'T clear the |
| // caches. |
| setCurrentPageFirstItemIndex(getCurrentPageFirstItemIndex(), false); |
| |
| if (isCollapsed(itemId)) { |
| fireCollapseEvent(itemId); |
| } else { |
| fireExpandEvent(itemId); |
| } |
| |
| if (containerSupportsPartialUpdates && !forceFullRefresh) { |
| markAsDirty(); |
| } else { |
| // For containers that do not send item set change events, always do |
| // full repaint instead of partial row update. |
| refreshRowCache(); |
| } |
| } |
| |
| @Override |
| public int size() { |
| return getContainerStrategy().size(); |
| } |
| |
| @Override |
| public Hierarchical getContainerDataSource() { |
| return (Hierarchical) super.getContainerDataSource(); |
| } |
| |
| @Override |
| public void setContainerDataSource(Container newDataSource) { |
| cStrategy = null; |
| |
| // FIXME: This disables partial updates until TreeTable is fixed so it |
| // does not change component hierarchy during paint |
| containerSupportsPartialUpdates = (newDataSource instanceof ItemSetChangeNotifier) && false; |
| |
| if (newDataSource != null && !(newDataSource instanceof Hierarchical)) { |
| newDataSource = new ContainerHierarchicalWrapper(newDataSource); |
| } |
| |
| if (newDataSource != null && !(newDataSource instanceof Ordered)) { |
| newDataSource = new HierarchicalContainerOrderedWrapper( |
| (Hierarchical) newDataSource); |
| } |
| |
| super.setContainerDataSource(newDataSource); |
| } |
| |
| @Override |
| public void containerItemSetChange( |
| com.vaadin.data.Container.ItemSetChangeEvent event) { |
| // Can't do partial repaints if items are added or removed during the |
| // expand/collapse request |
| toggledItemId = null; |
| getContainerStrategy().containerItemSetChange(event); |
| super.containerItemSetChange(event); |
| } |
| |
| @Override |
| protected Object getIdByIndex(int index) { |
| return getContainerStrategy().getIdByIndex(index); |
| } |
| |
| @Override |
| protected int indexOfId(Object itemId) { |
| return getContainerStrategy().indexOfId(itemId); |
| } |
| |
| @Override |
| public Object nextItemId(Object itemId) { |
| return getContainerStrategy().nextItemId(itemId); |
| } |
| |
| @Override |
| public Object lastItemId() { |
| return getContainerStrategy().lastItemId(); |
| } |
| |
| @Override |
| public Object prevItemId(Object itemId) { |
| return getContainerStrategy().prevItemId(itemId); |
| } |
| |
| @Override |
| public boolean isLastId(Object itemId) { |
| return getContainerStrategy().isLastId(itemId); |
| } |
| |
| @Override |
| public Collection<?> getItemIds() { |
| return getContainerStrategy().getItemIds(); |
| } |
| |
| @Override |
| public boolean areChildrenAllowed(Object itemId) { |
| return getContainerDataSource().areChildrenAllowed(itemId); |
| } |
| |
| @Override |
| public Collection<?> getChildren(Object itemId) { |
| return getContainerDataSource().getChildren(itemId); |
| } |
| |
| @Override |
| public Object getParent(Object itemId) { |
| return getContainerDataSource().getParent(itemId); |
| } |
| |
| @Override |
| public boolean hasChildren(Object itemId) { |
| return getContainerDataSource().hasChildren(itemId); |
| } |
| |
| @Override |
| public boolean isRoot(Object itemId) { |
| return getContainerDataSource().isRoot(itemId); |
| } |
| |
| @Override |
| public Collection<?> rootItemIds() { |
| return getContainerDataSource().rootItemIds(); |
| } |
| |
| @Override |
| public boolean setChildrenAllowed(Object itemId, boolean areChildrenAllowed) |
| throws UnsupportedOperationException { |
| return getContainerDataSource().setChildrenAllowed(itemId, |
| areChildrenAllowed); |
| } |
| |
| @Override |
| public boolean setParent(Object itemId, Object newParentId) |
| throws UnsupportedOperationException { |
| return getContainerDataSource().setParent(itemId, newParentId); |
| } |
| |
| /** |
| * Sets the Item specified by given identifier as collapsed or expanded. If |
| * the Item is collapsed, its children are not displayed to the user. |
| * |
| * @param itemId |
| * the identifier of the Item |
| * @param collapsed |
| * true if the Item should be collapsed, false if expanded |
| */ |
| public void setCollapsed(Object itemId, boolean collapsed) { |
| if (isCollapsed(itemId) != collapsed) { |
| if (null == toggledItemId && !isRowCacheInvalidated() |
| && getVisibleItemIds().contains(itemId)) { |
| // optimization: partial refresh if only one item is |
| // collapsed/expanded |
| toggledItemId = itemId; |
| toggleChildVisibility(itemId, false); |
| } else { |
| // make sure a full refresh takes place - otherwise neither |
| // partial nor full repaint of table content is performed |
| toggledItemId = null; |
| toggleChildVisibility(itemId, true); |
| } |
| } |
| } |
| |
| /** |
| * Checks if Item with given identifier is collapsed in the UI. |
| * |
| * <p> |
| * |
| * @param itemId |
| * the identifier of the checked Item |
| * @return true if the Item with given id is collapsed |
| * @see Collapsible#isCollapsed(Object) |
| */ |
| public boolean isCollapsed(Object itemId) { |
| return !getContainerStrategy().isNodeOpen(itemId); |
| } |
| |
| /** Explicitly sets the column in which the TreeTable visualizes the |
| * hierarchy. If hierarchyColumnId is not set, the hierarchy is visualized |
| * in the first visible column. |
| * |
| * @param hierarchyColumnId |
| * the new hierarchy column |
| */ |
| public void setHierarchyColumn(Object hierarchyColumnId) { |
| this.hierarchyColumnId = hierarchyColumnId; |
| } |
| |
| /** |
| * @return the identifier of column into which the hierarchy will be |
| * visualized or null if the column is not explicitly defined. |
| */ |
| public Object getHierarchyColumnId() { |
| return hierarchyColumnId; |
| } |
| |
| /** |
| * Adds an expand listener. |
| * |
| * @param listener |
| * the Listener to be added. |
| */ |
| public void addExpandListener(ExpandListener listener) { |
| addListener(ExpandEvent.class, listener, ExpandListener.EXPAND_METHOD); |
| } |
| |
| /** Adds the listener. |
| * |
| * @param listener |
| * the listener |
| * @deprecated As of 7.0, replaced by |
| * {@link #addExpandListener(ExpandListener)} |
| */ |
| @Deprecated |
| public void addListener(ExpandListener listener) { |
| addExpandListener(listener); |
| } |
| |
| /** |
| * Removes an expand listener. |
| * |
| * @param listener |
| * the Listener to be removed. |
| */ |
| public void removeExpandListener(ExpandListener listener) { |
| removeListener(ExpandEvent.class, listener, |
| ExpandListener.EXPAND_METHOD); |
| } |
| |
| /** Removes the listener. |
| * |
| * @param listener |
| * the listener |
| * @deprecated As of 7.0, replaced by |
| * {@link #removeExpandListener(ExpandListener)} |
| */ |
| @Deprecated |
| public void removeListener(ExpandListener listener) { |
| removeExpandListener(listener); |
| } |
| |
| /** |
| * Emits an expand event. |
| * |
| * @param itemId |
| * the item id. |
| */ |
| protected void fireExpandEvent(Object itemId) { |
| fireEvent(new ExpandEvent(this, itemId)); |
| } |
| |
| /** |
| * Adds a collapse listener. |
| * |
| * @param listener |
| * the Listener to be added. |
| */ |
| public void addCollapseListener(CollapseListener listener) { |
| addListener(CollapseEvent.class, listener, |
| CollapseListener.COLLAPSE_METHOD); |
| } |
| |
| /** Adds the listener. |
| * |
| * @param listener |
| * the listener |
| * @deprecated As of 7.0, replaced by |
| * {@link #addCollapseListener(CollapseListener)} |
| */ |
| @Deprecated |
| public void addListener(CollapseListener listener) { |
| addCollapseListener(listener); |
| } |
| |
| /** |
| * Removes a collapse listener. |
| * |
| * @param listener |
| * the Listener to be removed. |
| */ |
| public void removeCollapseListener(CollapseListener listener) { |
| removeListener(CollapseEvent.class, listener, |
| CollapseListener.COLLAPSE_METHOD); |
| } |
| |
| /** Removes the listener. |
| * |
| * @param listener |
| * the listener |
| * @deprecated As of 7.0, replaced by |
| * {@link #removeCollapseListener(CollapseListener)} |
| */ |
| @Deprecated |
| public void removeListener(CollapseListener listener) { |
| removeCollapseListener(listener); |
| } |
| |
| /** |
| * Emits a collapse event. |
| * |
| * @param itemId |
| * the item id. |
| */ |
| protected void fireCollapseEvent(Object itemId) { |
| fireEvent(new CollapseEvent(this, itemId)); |
| } |
| |
| /** |
| * @return true if animations are enabled |
| */ |
| public boolean isAnimationsEnabled() { |
| return animationsEnabled; |
| } |
| |
| /** |
| * Animations can be enabled by passing true to this method. Currently |
| * expanding rows slide in from the top and collapsing rows slide out the |
| * same way. NOTE! not supported in Internet Explorer 6 or 7. |
| * |
| * @param animationsEnabled |
| * true or false whether to enable animations or not. |
| */ |
| public void setAnimationsEnabled(boolean animationsEnabled) { |
| this.animationsEnabled = animationsEnabled; |
| markAsDirty(); |
| } |
| |
| private static final Logger getLogger() { |
| return Logger.getLogger(TreeTable.class.getName()); |
| } |
| |
| @Override |
| protected List<Object> getItemIds(int firstIndex, int rows) { |
| List<Object> itemIds = new ArrayList<Object>(); |
| for (int i = firstIndex; i < firstIndex + rows; i++) { |
| itemIds.add(getIdByIndex(i)); |
| } |
| return itemIds; |
| } |
| } |