diff options
author | Jonatan Kronqvist <jonatan.kronqvist@itmill.com> | 2011-06-16 07:55:38 +0000 |
---|---|---|
committer | Jonatan Kronqvist <jonatan.kronqvist@itmill.com> | 2011-06-16 07:55:38 +0000 |
commit | d9ca864ff7acf2eedb4edda46276fdb98ad35a7a (patch) | |
tree | ab810b3b0fb817d7a7a185064bf6b5d101710c64 /src/com | |
parent | 5271adfa5c18a458dc69fc444fea454ad3542b80 (diff) | |
download | vaadin-framework-d9ca864ff7acf2eedb4edda46276fdb98ad35a7a.tar.gz vaadin-framework-d9ca864ff7acf2eedb4edda46276fdb98ad35a7a.zip |
Merged TreeTable into Vaadin core (#5371)
svn changeset:19416/svn branch:6.7
Diffstat (limited to 'src/com')
-rw-r--r-- | src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java | 4 | ||||
-rw-r--r-- | src/com/vaadin/terminal/gwt/client/ui/VTreeTable.java | 345 | ||||
-rw-r--r-- | src/com/vaadin/ui/TreeTable.java | 579 | ||||
-rw-r--r-- | src/com/vaadin/ui/treetable/Collapsible.java | 66 | ||||
-rw-r--r-- | src/com/vaadin/ui/treetable/HierarchicalContainerOrderedWrapper.java | 58 |
5 files changed, 1050 insertions, 2 deletions
diff --git a/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java b/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java index d36c2137d8..fdf61b8292 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java @@ -1362,7 +1362,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, * The key to search with * @return */ - private VScrollTableRow getRenderedRowByKey(String key) { + protected VScrollTableRow getRenderedRowByKey(String key) { if (scrollBody != null) { final Iterator<Widget> it = scrollBody.iterator(); VScrollTableRow r = null; @@ -5452,7 +5452,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, * The row to where the selection head should move * @return Returns true if focus was moved successfully, else false */ - private boolean setRowFocus(VScrollTableRow row) { + protected boolean setRowFocus(VScrollTableRow row) { if (selectMode == SELECT_MODE_NONE) { return false; diff --git a/src/com/vaadin/terminal/gwt/client/ui/VTreeTable.java b/src/com/vaadin/terminal/gwt/client/ui/VTreeTable.java new file mode 100644 index 0000000000..974e96e8ae --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VTreeTable.java @@ -0,0 +1,345 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui; + +import java.util.Iterator; + +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.ImageElement; +import com.google.gwt.dom.client.SpanElement; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.event.dom.client.KeyCodes; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.RenderSpace; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.VConsole; +import com.vaadin.terminal.gwt.client.ui.VScrollTable.VScrollTableBody.VScrollTableRow; +import com.vaadin.terminal.gwt.client.ui.VTreeTable.VTreeTableScrollBody.VTreeTableRow; + +public class VTreeTable extends VScrollTable { + + public static final String ATTRIBUTE_HIERARCHY_COLUMN_INDEX = "hci"; + private boolean collapseRequest; + private boolean selectionPending; + private int colIndexOfHierarchy; + private String collapsedRowKey; + private VTreeTableScrollBody scrollBody; + + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + FocusableScrollPanel widget = null; + int scrollPosition = 0; + if (collapseRequest) { + widget = (FocusableScrollPanel) getWidget(1); + scrollPosition = widget.getScrollPosition(); + } + colIndexOfHierarchy = uidl + .hasAttribute(ATTRIBUTE_HIERARCHY_COLUMN_INDEX) ? uidl + .getIntAttribute(ATTRIBUTE_HIERARCHY_COLUMN_INDEX) : 0; + super.updateFromUIDL(uidl, client); + if (collapseRequest) { + if (collapsedRowKey != null && scrollBody != null) { + VScrollTableRow row = getRenderedRowByKey(collapsedRowKey); + if (row != null) { + setRowFocus(row); + focus(); + } + } + + int scrollPosition2 = widget.getScrollPosition(); + if (scrollPosition != scrollPosition2) { + VConsole.log("TT scrollpos from " + scrollPosition + " to " + + scrollPosition2); + widget.setScrollPosition(scrollPosition); + } + collapseRequest = false; + } + if (uidl.hasAttribute("focusedRow")) { + // TODO figure out if the row needs to focused at all + + // scrolled to parent by the server, focusedRow is probably the sam + // as the first row in view port + } + } + + @Override + protected VScrollTableBody createScrollBody() { + scrollBody = new VTreeTableScrollBody(); + return scrollBody; + } + + class VTreeTableScrollBody extends VScrollTable.VScrollTableBody { + private int identWidth = -1; + + VTreeTableScrollBody() { + super(); + } + + @Override + protected VScrollTableRow createRow(UIDL uidl, char[] aligns2) { + return new VTreeTableRow(uidl, aligns2); + } + + class VTreeTableRow extends + VScrollTable.VScrollTableBody.VScrollTableRow { + + private boolean isTreeCellAdded = false; + private SpanElement treeSpacer; + private boolean open; + private int depth; + private boolean canHaveChildren; + private Widget widgetInHierarchyColumn; + + public VTreeTableRow(UIDL uidl, char[] aligns2) { + super(uidl, aligns2); + } + + @Override + public void addCell(UIDL rowUidl, String text, char align, + String style, boolean textIsHTML, boolean isSorted) { + super.addCell(rowUidl, text, align, style, textIsHTML, isSorted); + + addTreeSpacer(rowUidl); + } + + private boolean addTreeSpacer(UIDL rowUidl) { + if (cellShowsTreeHierarchy(getElement().getChildCount() - 1)) { + Element container = (Element) getElement().getLastChild() + .getFirstChild(); + + if (rowUidl.hasAttribute("icon")) { + // icons are in first content cell in TreeTable + ImageElement icon = Document.get().createImageElement(); + icon.setClassName("v-icon"); + icon.setAlt("icon"); + icon.setSrc(client.translateVaadinUri(rowUidl + .getStringAttribute("icon"))); + container.insertFirst(icon); + } + + String classname = "v-treetable-treespacer"; + if (rowUidl.getBooleanAttribute("ca")) { + canHaveChildren = true; + open = rowUidl.getBooleanAttribute("open"); + classname += open ? " v-treetable-node-open" + : " v-treetable-node-closed"; + } + + treeSpacer = Document.get().createSpanElement(); + + treeSpacer.setClassName(classname); + container.insertFirst(treeSpacer); + depth = rowUidl.hasAttribute("depth") ? rowUidl + .getIntAttribute("depth") : 0; + setIdent(); + isTreeCellAdded = true; + return true; + } + return false; + } + + private boolean cellShowsTreeHierarchy(int curColIndex) { + if (isTreeCellAdded) { + return false; + } + return curColIndex == colIndexOfHierarchy + + (showRowHeaders ? 1 : 0); + } + + @Override + public void onBrowserEvent(Event event) { + if (event.getEventTarget().cast() == treeSpacer + && treeSpacer.getClassName().contains("node")) { + if (event.getTypeInt() == Event.ONMOUSEUP) { + sendToggleCollapsedUpdate(getKey()); + } + return; + } + super.onBrowserEvent(event); + } + + @Override + public void addCell(UIDL rowUidl, Widget w, char align, + String style, boolean isSorted) { + super.addCell(rowUidl, w, align, style, isSorted); + if (addTreeSpacer(rowUidl)) { + widgetInHierarchyColumn = w; + } + + } + + private void setIdent() { + if (getIdentWidth() > 0 && depth != 0) { + treeSpacer.getStyle().setWidth( + (depth + 1) * getIdentWidth(), Unit.PX); + } + } + + @Override + protected void onAttach() { + super.onAttach(); + if (getIdentWidth() < 0) { + detectIdent(this); + } + } + + @Override + public RenderSpace getAllocatedSpace(Widget child) { + if (widgetInHierarchyColumn == child) { + final int hierarchyAndIconWidth = getHierarchyAndIconWidth(); + final RenderSpace allocatedSpace = super + .getAllocatedSpace(child); + return new RenderSpace() { + @Override + public int getWidth() { + return allocatedSpace.getWidth() + - hierarchyAndIconWidth; + } + + @Override + public int getHeight() { + return allocatedSpace.getHeight(); + } + + }; + } + return super.getAllocatedSpace(child); + } + + private int getHierarchyAndIconWidth() { + int consumedSpace = treeSpacer.getOffsetWidth(); + if (treeSpacer.getParentElement().getChildCount() > 2) { + // icon next to tree spacer + consumedSpace += ((com.google.gwt.dom.client.Element) treeSpacer + .getNextSibling()).getOffsetWidth(); + } + return consumedSpace; + } + + } + + private int getIdentWidth() { + return identWidth; + } + + private void detectIdent(VTreeTableRow vTreeTableRow) { + identWidth = vTreeTableRow.treeSpacer.getOffsetWidth(); + if (identWidth == 0) { + identWidth = -1; + return; + } + Iterator<Widget> iterator = iterator(); + while (iterator.hasNext()) { + VTreeTableRow next = (VTreeTableRow) iterator.next(); + next.setIdent(); + } + } + } + + /** + * Icons rendered into first actual column in TreeTable, not to row header + * cell + */ + @Override + protected String buildCaptionHtmlSnippet(UIDL uidl) { + if (uidl.getTag().equals("column")) { + return super.buildCaptionHtmlSnippet(uidl); + } else { + String s = uidl.getStringAttribute("caption"); + return s; + } + } + + @Override + protected boolean handleNavigation(int keycode, boolean ctrl, boolean shift) { + VTreeTableRow focusedRow = (VTreeTableRow) getFocusedRow(); + if (focusedRow != null) { + if (focusedRow.canHaveChildren + && ((keycode == KeyCodes.KEY_RIGHT && !focusedRow.open) || (keycode == KeyCodes.KEY_LEFT && focusedRow.open))) { + if (!ctrl) { + client.updateVariable(paintableId, "selectCollapsed", true, + false); + } + sendToggleCollapsedUpdate(focusedRow.getKey()); + return true; + } else if (keycode == KeyCodes.KEY_RIGHT && focusedRow.open) { + // already expanded, move selection down if next is on a deeper + // level (is-a-child) + VTreeTableScrollBody body = (VTreeTableScrollBody) focusedRow + .getParent(); + Iterator<Widget> iterator = body.iterator(); + VTreeTableRow next = null; + while (iterator.hasNext()) { + next = (VTreeTableRow) iterator.next(); + if (next == focusedRow) { + next = (VTreeTableRow) iterator.next(); + break; + } + } + if (next != null) { + if (next.depth > focusedRow.depth) { + selectionPending = true; + return super.handleNavigation(getNavigationDownKey(), + ctrl, shift); + } + } else { + // Note, a minor change here for a bit false behavior if + // cache rows is disabled + last visible row + no childs for + // the node + selectionPending = true; + return super.handleNavigation(getNavigationDownKey(), ctrl, + shift); + } + } else if (keycode == KeyCodes.KEY_LEFT) { + // already collapsed move selection up to parent node + // do on the server side as the parent is not necessary + // rendered on the client, could check if parent is visible if + // a performance issue arises + + client.updateVariable(paintableId, "focusParent", + focusedRow.getKey(), true); + return true; + } + } + return super.handleNavigation(keycode, ctrl, shift); + } + + private void sendToggleCollapsedUpdate(String rowKey) { + collapsedRowKey = rowKey; + collapseRequest = true; + client.updateVariable(paintableId, "toggleCollapsed", rowKey, true); + } + + @Override + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + if (event.getTypeInt() == Event.ONKEYUP && selectionPending) { + sendSelectedRows(); + } + } + + @Override + protected void sendSelectedRows() { + super.sendSelectedRows(); + selectionPending = false; + } + + @Override + protected void reOrderColumn(String columnKey, int newIndex) { + super.reOrderColumn(columnKey, newIndex); + // current impl not intelligent enough to survive without visiting the + // server to redraw content + client.sendPendingVariableChanges(); + } + + @Override + public void setStyleName(String style) { + super.setStyleName(style + " v-treetable"); + } + +} diff --git a/src/com/vaadin/ui/TreeTable.java b/src/com/vaadin/ui/TreeTable.java new file mode 100644 index 0000000000..20fc8e44d7 --- /dev/null +++ b/src/com/vaadin/ui/TreeTable.java @@ -0,0 +1,579 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +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 com.google.gwt.user.client.ui.Tree; +import com.vaadin.data.Container; +import com.vaadin.data.Container.Hierarchical; +import com.vaadin.data.Container.ItemSetChangeEvent; +import com.vaadin.data.util.ContainerHierarchicalWrapper; +import com.vaadin.data.util.HierarchicalContainer; +import com.vaadin.terminal.PaintException; +import com.vaadin.terminal.PaintTarget; +import com.vaadin.terminal.Resource; +import com.vaadin.terminal.gwt.client.ui.VTreeTable; +import com.vaadin.ui.treetable.Collapsible; +import com.vaadin.ui.treetable.HierarchicalContainerOrderedWrapper; + +/** + * 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" }) +@ClientWidget(VTreeTable.class) +public class TreeTable extends Table 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. + */ + public int getDepth(Object itemId) { + int depth = 0; + Hierarchical hierarchicalContainer = getContainerDataSource(); + while (!hierarchicalContainer.isRoot(itemId)) { + depth++; + itemId = hierarchicalContainer.getParent(itemId); + } + return depth; + } + + 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(); + } + + public void toggleChildVisibility(Object itemId) { + c().setCollapsed(itemId, !c().isCollapsed(itemId)); + } + + public boolean isNodeOpen(Object itemId) { + return !c().isCollapsed(itemId); + } + + public int size() { + return TreeTable.super.size(); + } + + public Object getIdByIndex(int index) { + return TreeTable.super.getIdByIndex(index); + } + + public int indexOfId(Object id) { + return TreeTable.super.indexOfId(id); + } + + public boolean isLastId(Object itemId) { + // using the default impl + return TreeTable.super.isLastId(itemId); + } + + public Object lastItemId() { + // using the default impl + return TreeTable.super.lastItemId(); + } + + public Object nextItemId(Object itemId) { + return TreeTable.super.nextItemId(itemId); + } + + public Object prevItemId(Object itemId) { + return TreeTable.super.prevItemId(itemId); + } + + public Collection<?> getItemIds() { + return TreeTable.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>(); + + public boolean isNodeOpen(Object itemId) { + return openItems.contains(itemId); + } + + public int size() { + return getPreOrder().size(); + } + + public Collection<Object> getItemIds() { + return Collections.unmodifiableCollection(getPreOrder()); + } + + public boolean isLastId(Object itemId) { + return itemId.equals(lastItemId()); + } + + public Object lastItemId() { + if (getPreOrder().size() > 0) { + return getPreOrder().get(getPreOrder().size() - 1); + } else { + return null; + } + } + + 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); + } + } + + public Object prevItemId(Object itemId) { + int indexOf = getPreOrder().indexOf(itemId); + indexOf--; + if (indexOf < 0) { + return null; + } else { + return getPreOrder().get(indexOf); + } + } + + public void toggleChildVisibility(Object itemId) { + boolean removed = openItems.remove(itemId); + if (!removed) { + openItems.add(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); + } + } + } + + } + + public int indexOfId(Object id) { + return getPreOrder().indexOf(id); + } + + 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 TreeTable() { + super(null, new HierarchicalContainer()); + } + + /** + * Creates an empty TreeTable with a default container. + * + * @param caption + * the caption for the TreeTable + */ + public TreeTable(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 TreeTable(String caption, Container dataSource) { + super(caption, dataSource); + } + + private ContainerStrategy cStrategy; + private Object focusedRowId = null; + private Object hierarchyColumnId; + + 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); + toggleChildVisibility(itemId); + 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); + } + if (isSelectable()) { + if (isMultiSelect()) { + setValue(Collections.singleton(itemId)); + } else { + setValue(itemId); + } + } else { + // just instruct the VTreeTable to set focus the row (not to select) + setFocusedRow(itemId); + } + } + + private void setFocusedRow(Object itemId) { + focusedRowId = itemId; + requestRepaint(); + } + + @Override + public void paintContent(PaintTarget target) throws PaintException { + if (focusedRowId != null) { + target.addAttribute("focusedRow", itemIdMapper.key(focusedRowId)); + focusedRowId = null; + } + if (hierarchyColumnId != null) { + Object[] visibleColumns2 = getVisibleColumns(); + for (int i = 0; i < visibleColumns2.length; i++) { + Object object = visibleColumns2[i]; + if (hierarchyColumnId.equals(object)) { + target.addAttribute( + VTreeTable.ATTRIBUTE_HIERARCHY_COLUMN_INDEX, i); + break; + } + } + } + super.paintContent(target); + } + + private void toggleChildVisibility(Object itemId) { + getContainerStrategy().toggleChildVisibility(itemId); + // ensure that page still has first item in page, ignore buffer refresh + // (forced in this method) + setCurrentPageFirstItemIndex(getCurrentPageFirstItemIndex()); + + requestRepaint(); + } + + @Override + public int size() { + return getContainerStrategy().size(); + } + + @Override + public Hierarchical getContainerDataSource() { + return (Hierarchical) super.getContainerDataSource(); + } + + @Override + public void setContainerDataSource(Container newDataSource) { + cStrategy = null; + if (!(newDataSource instanceof Hierarchical)) { + newDataSource = new ContainerHierarchicalWrapper(newDataSource); + } + + if (!(newDataSource instanceof Ordered)) { + newDataSource = new HierarchicalContainerOrderedWrapper( + (Hierarchical) newDataSource); + } + + super.setContainerDataSource(newDataSource); + } + + @Override + public void containerItemSetChange( + com.vaadin.data.Container.ItemSetChangeEvent event) { + 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(); + } + + public boolean areChildrenAllowed(Object itemId) { + return getContainerDataSource().areChildrenAllowed(itemId); + } + + public Collection<?> getChildren(Object itemId) { + return getContainerDataSource().getChildren(itemId); + } + + public Object getParent(Object itemId) { + return getContainerDataSource().getParent(itemId); + } + + public boolean hasChildren(Object itemId) { + return getContainerDataSource().hasChildren(itemId); + } + + public boolean isRoot(Object itemId) { + return getContainerDataSource().isRoot(itemId); + } + + public Collection<?> rootItemIds() { + return getContainerDataSource().rootItemIds(); + } + + public boolean setChildrenAllowed(Object itemId, boolean areChildrenAllowed) + throws UnsupportedOperationException { + return getContainerDataSource().setChildrenAllowed(itemId, + areChildrenAllowed); + } + + public boolean setParent(Object itemId, Object newParentId) + throws UnsupportedOperationException { + return getContainerDataSource().setParent(itemId, newParentId); + } + + /** + * Sets the Item specified by given identifier collapsed or expanded. If the + * Item is collapsed, its children is not displayed in for 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) { + toggleChildVisibility(itemId); + } + } + + /** + * 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 + */ + 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; + } + +} diff --git a/src/com/vaadin/ui/treetable/Collapsible.java b/src/com/vaadin/ui/treetable/Collapsible.java new file mode 100644 index 0000000000..649687716c --- /dev/null +++ b/src/com/vaadin/ui/treetable/Collapsible.java @@ -0,0 +1,66 @@ +package com.vaadin.ui.treetable; + +import com.vaadin.data.Container; +import com.vaadin.data.Container.Hierarchical; +import com.vaadin.data.Container.Ordered; +import com.vaadin.data.Item; + +/** + * Container needed by large lazy loading hierarchies displayed in TreeTable. + * <p> + * Container of this type gets notified when a subtree is opened/closed in a + * component displaying its content. This allows container to lazy load subtrees + * and release memory when a sub-tree is no longer displayed. + * <p> + * Methods from {@link Container.Ordered} (and from {@linkContainer.Indexed} if + * implemented) are expected to work as in "preorder" of the currently visible + * hierarchy. This means for example that the return value of size method + * changes when subtree is collapsed/expanded. In other words items in collapsed + * sub trees should be "ignored" by container when the container is accessed + * with methods introduced in {@link Container.Ordered} or + * {@linkContainer.Indexed}. From the accessors point of view, items in + * collapsed subtrees don't exist. + * <p> + * + */ +public interface Collapsible extends Hierarchical, Ordered { + + /** + * <p> + * Collapsing the {@link Item} indicated by <code>itemId</code> hides all + * children, and their respective children, from the {@link Container}. + * </p> + * + * <p> + * If called on a leaf {@link Item}, this method does nothing. + * </p> + * + * @param itemId + * the identifier of the collapsed {@link Item} + * @param collapsed + * <code>true</code> if you want to collapse the children below + * this {@link Item}. <code>false</code> if you want to + * uncollapse the children. + */ + public void setCollapsed(Object itemId, boolean collapsed); + + /** + * <p> + * Checks whether the {@link Item}, identified by <code>itemId</code> is + * collapsed or not. + * </p> + * + * <p> + * If an {@link Item} is "collapsed" its children are not included in + * methods used to list Items in this container. + * </p> + * + * @param itemId + * The {@link Item}'s identifier that is to be checked. + * @return <code>true</code> iff the {@link Item} identified by + * <code>itemId</code> is currently collapsed, otherwise + * <code>false</code>. + */ + public boolean isCollapsed(Object itemId); + +} diff --git a/src/com/vaadin/ui/treetable/HierarchicalContainerOrderedWrapper.java b/src/com/vaadin/ui/treetable/HierarchicalContainerOrderedWrapper.java new file mode 100644 index 0000000000..0c00027f26 --- /dev/null +++ b/src/com/vaadin/ui/treetable/HierarchicalContainerOrderedWrapper.java @@ -0,0 +1,58 @@ +package com.vaadin.ui.treetable; + +import java.util.Collection; + +import com.vaadin.data.Container; +import com.vaadin.data.Container.Hierarchical; +import com.vaadin.data.util.ContainerOrderedWrapper; + +@SuppressWarnings({ "serial", "unchecked" }) +/** + * Helper for TreeTable. Does the same thing as ContainerOrderedWrapper + * to fit into table but retains Hierarchical feature. + */ +public class HierarchicalContainerOrderedWrapper extends + ContainerOrderedWrapper implements Hierarchical { + + private Hierarchical hierarchical; + + public HierarchicalContainerOrderedWrapper(Hierarchical toBeWrapped) { + super(toBeWrapped); + hierarchical = toBeWrapped; + } + + public boolean areChildrenAllowed(Object itemId) { + return hierarchical.areChildrenAllowed(itemId); + } + + public Collection<?> getChildren(Object itemId) { + return hierarchical.getChildren(itemId); + } + + public Object getParent(Object itemId) { + return hierarchical.getParent(itemId); + } + + public boolean hasChildren(Object itemId) { + return hierarchical.hasChildren(itemId); + } + + public boolean isRoot(Object itemId) { + return hierarchical.isRoot(itemId); + } + + public Collection<?> rootItemIds() { + return hierarchical.rootItemIds(); + } + + public boolean setChildrenAllowed(Object itemId, boolean areChildrenAllowed) + throws UnsupportedOperationException { + return hierarchical.setChildrenAllowed(itemId, areChildrenAllowed); + } + + public boolean setParent(Object itemId, Object newParentId) + throws UnsupportedOperationException { + return hierarchical.setParent(itemId, newParentId); + } + +} |