com.google.gwt.user.client.ui.TreeItem.java Source code

Java tutorial

Introduction

Here is the source code for com.google.gwt.user.client.ui.TreeItem.java

Source

/*
 * Copyright 2008 Google Inc.
 * 
 * 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.google.gwt.user.client.ui;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import com.google.gwt.animation.client.Animation;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.i18n.client.LocaleInfo;
import com.google.gwt.safehtml.client.HasSafeHtml;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.user.client.DOM;

import cc.alcina.framework.common.client.logic.domaintransform.lookup.LightMap;
import cc.alcina.framework.common.client.util.Ax;

/**
 * An item that can be contained within a
 * {@link com.google.gwt.user.client.ui.Tree}.
 * 
 * Each tree item is assigned a unique DOM id in order to support ARIA. See
 * {@link com.google.gwt.user.client.ui.Accessibility} for more information.
 * 
 * <p>
 * <h3>Example</h3> {@example com.google.gwt.examples.TreeExample}
 * </p>
 */
public class TreeItem extends UIObject implements IsTreeItem, HasTreeItems, HasHTML, HasSafeHtml {
    /*
     * For compatibility with UiBinder interface HasTreeItems should be declared
     * before HasHTML, so that children items and widgets are processed before
     * interpreting HTML.
     */
    /**
     * The margin applied to child items.
     */
    private static final double CHILD_MARGIN = 16.0;

    // By not overwriting the default tree padding and spacing, we traditionally
    // added 7 pixels between our image and content.
    // <2>|<1>image<1>|<2>|<1>content
    // So to preserve the current spacing we must add a 7 pixel pad when no
    // image
    // is supplied.
    static final int IMAGE_PAD = 7;

    /**
     * The duration of the animation.
     */
    private static final int ANIMATION_DURATION = 200;

    /**
     * The duration of the animation per child {@link TreeItem}. If the per item
     * duration times the number of child items is less than the duration above,
     * the smaller duration will be used.
     */
    private static final int ANIMATION_DURATION_PER_ITEM = 75;

    /**
     * The static animation used to open {@link TreeItem TreeItems}.
     */
    private static TreeItemAnimation itemAnimation = new TreeItemAnimation();

    /**
     * The structured table to hold images.
     */
    private static Element BASE_INTERNAL_ELEM;

    /**
     * The base tree item element that will be cloned.
     */
    private static Element BASE_BARE_ELEM;

    private static TreeItemImpl impl = GWT.create(TreeItemImpl.class);

    PotentialState potentialState = PotentialState.POTENTIAL;

    private ArrayList<TreeItem> children;

    private Element contentElem, childSpanElem, imageHolder;

    /**
     * Indicates that this item is a root item in a tree.
     */
    private boolean isRoot;

    private boolean open;

    private TreeItem parent;

    private boolean selected;

    private Object userObject;

    private Tree tree;

    private Widget widget;

    /**
     * Creates an empty tree item.
     */
    public TreeItem() {
        this(false);
    }

    /**
     * Constructs a tree item with the given HTML.
     * 
     * @param html
     *            the item's HTML
     */
    public TreeItem(SafeHtml html) {
        this(html.asString());
    }

    /**
     * Constructs a tree item with the given HTML.
     * 
     * @param html
     *            the item's HTML
     */
    public TreeItem(String html) {
        this();
        setHTML(html);
    }

    /**
     * Constructs a tree item with the given <code>Widget</code>.
     * 
     * @param widget
     *            the item's widget
     */
    public TreeItem(Widget widget) {
        this();
        setWidget(widget);
    }

    /**
     * Creates an empty tree item.
     * 
     * @param isRoot
     *            true if this item is the root of a tree
     */
    TreeItem(boolean isRoot) {
        this.isRoot = isRoot;
        ensureElements(isRoot);
    }

    protected void ensureElements(boolean isRoot) {
        ensureElements();
    }

    protected void ensureElements() {
        if (contentElem != null) {
            return;
        }
        Element elem = Document.get().createDivElement();
        elem.ensureId();
        setElement(elem);
        contentElem = Document.get().createDivElement();
        contentElem.ensureId();
        // The root item always has children.
        if (isRoot) {
            initChildren();
            impl.ensureState(this, PotentialState.INSTANTIATED);
        }
    }

    private List<Integer> getIndex() {
        ArrayList<Integer> list = new ArrayList<>();
        populateIndex(list);
        return list;
    }

    private void populateIndex(List<Integer> list) {
        if (getParentItem() == null) {
            list.add(0, 0);
            return;
        }
        list.add(0, getParentItem().getChildIndex(this));
        getParentItem().populateIndex(list);
    }

    /**
     * Adds another item as a child to this one.
     * 
     * @param isItem
     *            the wrapper of item to be added
     */
    public void addItem(IsTreeItem isItem) {
        TreeItem item = isItem.asTreeItem();
        addItem(item);
    }

    /**
     * Adds a child tree item containing the specified html.
     * 
     * @param itemHtml
     *            the item's HTML
     * @return the item that was added
     */
    public TreeItem addItem(SafeHtml itemHtml) {
        TreeItem ret = new TreeItem(itemHtml);
        addItem(ret);
        return ret;
    }

    /**
     * Adds a child tree item containing the specified html.
     * 
     * @param itemHtml
     *            the text to be added
     * @return the item that was added
     */
    public TreeItem addItem(String itemHtml) {
        TreeItem ret = new TreeItem(itemHtml);
        addItem(ret);
        return ret;
    }

    /**
     * Adds another item as a child to this one.
     * 
     * @param item
     *            the item to be added
     */
    public void addItem(TreeItem item) {
        // If this is the item's parent, removing the item will affect the child
        // count.
        maybeRemoveItemFromParent(item);
        insertItem(getChildCount(), item);
    }

    /**
     * Adds a child tree item containing the specified widget.
     * 
     * @param widget
     *            the widget to be added
     * @return the item that was added
     */
    public TreeItem addItem(Widget widget) {
        TreeItem ret = new TreeItem(widget);
        addItem(ret);
        return ret;
    }

    /**
     * Adds a child tree item containing the specified text.
     * 
     * @param itemText
     *            the text of the item to be added
     * @return the item that was added
     */
    public TreeItem addTextItem(String itemText) {
        TreeItem ret = new TreeItem();
        ret.setText(itemText);
        addItem(ret);
        return ret;
    }

    public TreeItem asTreeItem() {
        return this;
    }

    /**
     * Gets the child at the specified index.
     * 
     * @param index
     *            the index to be retrieved
     * @return the item at that index
     */
    public TreeItem getChild(int index) {
        if ((index < 0) || (index >= getChildCount())) {
            return null;
        }
        return children.get(index);
    }

    /**
     * Gets the number of children contained in this item.
     * 
     * @return this item's child count.
     */
    public int getChildCount() {
        if (children == null) {
            return 0;
        }
        return children.size();
    }

    /**
     * Gets the index of the specified child item.
     * 
     * @param child
     *            the child item to be found
     * @return the child's index, or <code>-1</code> if none is found
     */
    public int getChildIndex(TreeItem child) {
        if (children == null) {
            return -1;
        }
        return children.indexOf(child);
    }

    public String getHTML() {
        return DOM.getInnerHTML(contentElem);
    }

    /**
     * Gets this item's parent.
     * 
     * @return the parent item
     */
    public TreeItem getParentItem() {
        return parent;
    }

    /**
     * Gets whether this item's children are displayed.
     * 
     * @return <code>true</code> if the item is open
     */
    public boolean getState() {
        return open;
    }

    public String getText() {
        if (((com.google.gwt.dom.client.Element) contentElem) instanceof PotentialElement) {
            PotentialElement pe = contentElem.cast();
            return pe.getInnerText0();
        }
        return DOM.getInnerText(contentElem);
    }

    /**
     * Gets the tree that contains this item.
     * 
     * @return the containing tree
     */
    public final Tree getTree() {
        return tree;
    }

    /**
     * Gets the user-defined object associated with this item.
     * 
     * @return the item's user-defined object
     */
    public Object getUserObject() {
        return userObject;
    }

    /**
     * Gets the <code>Widget</code> associated with this tree item.
     * 
     * @return the widget
     */
    public Widget getWidget() {
        return widget;
    }

    /**
     * Inserts a child tree item at the specified index containing the specified
     * text.
     * 
     * @param beforeIndex
     *            the index where the item will be inserted
     * @param itemHtml
     *            the item's HTML
     * @return the item that was added
     * @throws IndexOutOfBoundsException
     *             if the index is out of range
     */
    public TreeItem insertItem(int beforeIndex, SafeHtml itemHtml) throws IndexOutOfBoundsException {
        TreeItem ret = new TreeItem(itemHtml);
        insertItem(beforeIndex, ret);
        return ret;
    }

    /**
     * Inserts a child tree item at the specified index containing the specified
     * text.
     * 
     * @param beforeIndex
     *            the index where the item will be inserted
     * @param itemText
     *            the text to be added
     * @return the item that was added
     * @throws IndexOutOfBoundsException
     *             if the index is out of range
     */
    public TreeItem insertItem(int beforeIndex, String itemText) throws IndexOutOfBoundsException {
        TreeItem ret = new TreeItem(itemText);
        insertItem(beforeIndex, ret);
        return ret;
    }

    /**
     * Inserts an item as a child to this one.
     * 
     * @param beforeIndex
     *            the index where the item will be inserted
     * @param item
     *            the item to be added
     * @throws IndexOutOfBoundsException
     *             if the index is out of range
     */
    public void insertItem(int beforeIndex, TreeItem item) throws IndexOutOfBoundsException {
        // Detach item from existing parent.
        maybeRemoveItemFromParent(item);
        // Check the index after detaching in case this item was already the
        // parent.
        int childCount = getChildCount();
        if (beforeIndex < 0 || beforeIndex > childCount) {
            throw new IndexOutOfBoundsException();
        }
        if (children == null) {
            initChildren();
        }
        // Physical attach.
        if (potentialState == PotentialState.INSTANTIATED) {
            impl.ensureState(item, PotentialState.POTENTIAL_CHILDREN);
            impl.ensureMargin(item, this);
            Element childContainer = isRoot ? tree.getElement() : childSpanElem;
            if (beforeIndex == childCount) {
                childContainer.appendChild(item.getElement());
            } else {
                Element beforeElem = getChild(beforeIndex).getElement();
                childContainer.insertBefore(item.getElement(), beforeElem);
            }
        }
        // Logical attach.
        // Explicitly set top-level items' parents to null if this is root.
        item.setParentItem(isRoot ? null : this);
        children.add(beforeIndex, item);
        // Adopt.
        item.setTree(tree);
        if (!isRoot && children.size() == 1) {
            updateState(false, false);
        }
    }

    /**
     * Inserts a child tree item at the specified index containing the specified
     * widget.
     * 
     * @param beforeIndex
     *            the index where the item will be inserted
     * @param widget
     *            the widget to be added
     * @return the item that was added
     * @throws IndexOutOfBoundsException
     *             if the index is out of range
     */
    public TreeItem insertItem(int beforeIndex, Widget widget) throws IndexOutOfBoundsException {
        TreeItem ret = new TreeItem(widget);
        insertItem(beforeIndex, ret);
        return ret;
    }

    /**
     * Determines whether this item is currently selected.
     * 
     * @return <code>true</code> if it is selected
     */
    public boolean isSelected() {
        return selected;
    }

    /**
     * Removes this item from its tree.
     */
    public void remove() {
        if (parent != null) {
            // If this item has a parent, remove self from it.
            parent.removeItem(this);
        } else if (tree != null) {
            // If the item has no parent, but is in the Tree, it must be a
            // top-level
            // element.
            tree.removeItem(this);
        }
    }

    /**
     * Removes one of this item's children.
     * 
     * @param isItem
     *            the wrapper of item to be removed
     */
    public void removeItem(IsTreeItem isItem) {
        if (isItem != null) {
            TreeItem item = isItem.asTreeItem();
            removeItem(item);
        }
    }

    /**
     * Removes one of this item's children.
     * 
     * @param item
     *            the item to be removed
     */
    public void removeItem(TreeItem item) {
        // Validate.
        if (children == null || !children.contains(item)) {
            return;
        }
        // Orphan.
        Tree oldTree = tree;
        item.setTree(null);
        // Physical detach.
        if (potentialState == PotentialState.INSTANTIATED) {
            if (isRoot) {
                oldTree.getElement().removeChild(item.getElement());
            } else {
                childSpanElem.removeChild(item.getElement());
            }
        }
        // Logical detach.
        item.setParentItem(null);
        children.remove(item);
        if (!isRoot && children.size() == 0) {
            updateState(false, false);
        }
    }

    /**
     * Removes all of this item's children.
     */
    public void removeItems() {
        while (getChildCount() > 0) {
            removeItem(getChild(0));
        }
    }

    public void setHTML(SafeHtml html) {
        setHTML(html.asString());
    }

    public void setHTML(String html) {
        setWidget(null);
        DOM.setInnerHTML(contentElem, html);
    }

    protected boolean isUnrendered() {
        return contentElem == null;
    }

    /**
     * Selects or deselects this item.
     * 
     * @param selected
     *            <code>true</code> to select the item, <code>false</code> to
     *            deselect it
     */
    public void setSelected(boolean selected) {
        if (this.selected == selected) {
            return;
        }
        this.selected = selected;
        setStyleName(getContentElem(), "gwt-TreeItem-selected", selected);
    }

    /**
     * Sets whether this item's children are displayed.
     * 
     * @param open
     *            whether the item is open
     */
    public void setState(boolean open) {
        setState(open, true);
    }

    /**
     * Sets whether this item's children are displayed.
     * 
     * @param open
     *            whether the item is open
     * @param fireEvents
     *            <code>true</code> to allow open/close events to be
     */
    public void setState(boolean open, boolean fireEvents) {
        if (open) {
            impl.ensureState(this, PotentialState.INSTANTIATED);
        }
        if (open && getChildCount() == 0) {
            return;
        }
        // Only do the physical update if it changes
        if (this.open != open) {
            this.open = open;
            updateState(true, fireEvents);
            if (fireEvents && tree != null) {
                tree.fireStateChanged(this, open);
            }
            if (open) {
                for (TreeItem child : children) {
                    impl.ensureState(child, PotentialState.POTENTIAL_CHILDREN);
                }
            }
        }
    }

    public void setText(String text) {
        setWidget(null);
        DOM.setInnerText(contentElem, text);
    }

    /**
     * Sets the user-defined object associated with this item.
     * 
     * @param userObj
     *            the item's user-defined object
     */
    public void setUserObject(Object userObj) {
        userObject = userObj;
    }

    /**
     * Sets the current widget. Any existing child widget will be removed.
     * 
     * @param newWidget
     *            Widget to set
     */
    public void setWidget(Widget newWidget) {
        if (widget == null && newWidget == null) {
            return;
        }
        // Detach new child from old parent.
        if (newWidget != null) {
            newWidget.removeFromParent();
        }
        // Detach old child from tree.
        if (widget != null) {
            try {
                if (tree != null) {
                    tree.orphan(widget);
                }
            } finally {
                // Physical detach old child.
                contentElem.removeChild(widget.getElement());
                widget = null;
            }
        }
        // Clear out any existing content before adding a widget.
        DOM.setInnerHTML(contentElem, "");
        // Logical detach old/attach new.
        widget = newWidget;
        if (newWidget != null) {
            // Physical attach new.
            DOM.appendChild(contentElem, newWidget.getElement());
            // Attach child to tree.
            if (tree != null) {
                tree.adopt(widget, this);
            }
            // Set tabIndex on the widget to -1, so that it doesn't mess up the
            // tab
            // order of the entire tree
            if (Tree.shouldTreeDelegateFocusToElement(widget.getElement())) {
                DOM.setElementAttribute(widget.getElement(), "tabIndex", "-1");
            }
        }
    }

    private void convertToFullNode() {
        impl.convertToFullNode(this);
    }

    @SuppressWarnings("unused")
    private Element resolve(Element elem) {
        if (PotentialElement.isPotential(elem)) {
            Element replacer = Document.get().createElement(elem.getTagName()).cast();
            replacer.setInnerHTML(elem.getInnerHTML());
            return replacer;
        }
        return elem;
    }

    private void updateStateRecursiveHelper() {
        updateState(false, false);
        for (int i = 0, n = getChildCount(); i < n; ++i) {
            children.get(i).updateStateRecursiveHelper();
        }
    }

    /**
     * Returns a suggested {@link Focusable} instance to use when this tree item
     * is selected. The tree maintains focus if this method returns null. By
     * default, if the tree item contains a focusable widget, that widget is
     * returned.
     * 
     * Note, the {@link Tree} will ignore this value if the user clicked on an
     * input element such as a button or text area when selecting this item.
     * 
     * @return the focusable item
     */
    protected Focusable getFocusable() {
        Focusable focus = getFocusableWidget();
        if (focus == null) {
            Widget w = getWidget();
            if (w instanceof Focusable) {
                focus = (Focusable) w;
            }
        }
        return focus;
    }

    /**
     * Returns the widget, if any, that should be focused on if this TreeItem is
     * selected.
     * 
     * @return widget to be focused.
     * @deprecated use {@link #getFocusable()} instead
     */
    @Deprecated
    protected HasFocus getFocusableWidget() {
        Widget w = getWidget();
        if (w instanceof HasFocus) {
            return (HasFocus) w;
        } else {
            return null;
        }
    }

    /**
     * <b>Affected Elements:</b>
     * <ul>
     * <li>-content = The text or {@link Widget} next to the image.</li>
     * <li>-child# = The child at the specified index.</li>
     * </ul>
     * 
     * @see UIObject#onEnsureDebugId(String)
     */
    @Override
    protected void onEnsureDebugId(String baseID) {
        super.onEnsureDebugId(baseID);
        ensureDebugId(contentElem, baseID, "content");
        if (imageHolder != null) {
            // The image itself may or may not exist.
            ensureDebugId(imageHolder, baseID, "image");
        }
        if (children != null) {
            int childCount = 0;
            for (TreeItem child : children) {
                child.ensureDebugId(baseID + "-child" + childCount);
                childCount++;
            }
        }
    }

    void addTreeItems(List<TreeItem> accum) {
        int size = getChildCount();
        for (int i = 0; i < size; i++) {
            TreeItem item = children.get(i);
            accum.add(item);
            item.addTreeItems(accum);
        }
    }

    ArrayList<TreeItem> getChildren() {
        return children;
    }

    Element getContentElem() {
        return contentElem;
    }

    Element getImageElement() {
        return DOM.getFirstChild(getImageHolderElement());
    }

    Element getImageHolderElement() {
        if (!isFullNode()) {
            convertToFullNode();
        }
        return imageHolder;
    }

    void initChildren() {
        // convertToFullNode();
        children = new ArrayList<TreeItem>();
    }

    void initChildSpanElement() {
        childSpanElem = DOM.createDiv();
        DOM.appendChild(getElement(), childSpanElem);
        DOM.setStyleAttribute(childSpanElem, "whiteSpace", "nowrap");
    }

    boolean isFullNode() {
        return imageHolder != null;
    }

    /**
     * Remove a tree item from its parent if it has one.
     * 
     * @param item
     *            the tree item to remove from its parent
     */
    void maybeRemoveItemFromParent(TreeItem item) {
        if ((item.getParentItem() != null) || (item.getTree() != null)) {
            item.remove();
        }
    }

    void setParentItem(TreeItem parent) {
        this.parent = parent;
        if (parent != null && parent.potentialState == PotentialState.INSTANTIATED) {
            impl.ensureState(this, PotentialState.POTENTIAL_CHILDREN);
        }
    }

    void setTree(Tree newTree) {
        // Early out.
        if (tree == newTree) {
            return;
        }
        // Remove this item from existing tree.
        if (tree != null) {
            if (tree.getSelectedItem() == this) {
                tree.setSelectedItem(null);
            }
            if (widget != null) {
                tree.orphan(widget);
            }
        }
        tree = newTree;
        for (int i = 0, n = getChildCount(); i < n; ++i) {
            children.get(i).setTree(newTree);
        }
        updateState(false, true);
        if (newTree != null) {
            if (widget != null) {
                // Add my widget to the new tree.
                newTree.adopt(widget, this);
            }
        }
    }

    void updateState(boolean animate, boolean updateTreeSelection) {
        // If the tree hasn't been set, there is no visual state to update.
        // If the tree is not attached, then update will be called on attach.
        // localdom - do it before attach
        if (tree == null) {
            return;
        }
        boolean decorateBeforeAttach = !tree.isAnimationEnabled();
        boolean decorate = tree.isAttached() || decorateBeforeAttach;
        if (!decorate) {
            return;
        }
        if (potentialState == PotentialState.POTENTIAL) {
            return;
        }
        maybeLazilyRender();
        if (getChildCount() == 0) {
            if (childSpanElem != null) {
                UIObject.setVisible(childSpanElem, false);
            }
            tree.showLeafImage(this);
            return;
        }
        // We must use 'display' rather than 'visibility' here,
        // or the children will always take up space.
        if (animate && (tree != null) && (tree.isAttached())) {
            itemAnimation.setItemState(this, tree.isAnimationEnabled());
        } else {
            itemAnimation.setItemState(this, false);
        }
        // Change the status image
        if (open) {
            tree.showOpenImage(this);
        } else {
            tree.showClosedImage(this);
        }
        // We may need to update the tree's selection in response to a tree
        // state
        // change. For example, if the tree's currently selected item is a
        // descendant of an item whose branch was just collapsed, then the item
        // itself should become the newly-selected item.
        if (updateTreeSelection) {
            tree.maybeUpdateSelection(this, this.open);
        }
    }

    protected void maybeLazilyRender() {
    }

    void updateStateRecursive() {
        updateStateRecursiveHelper();
        tree.maybeUpdateSelection(this, this.open);
    }

    /**
     * Implementation class for {@link TreeItem}.
     */
    public static class TreeItemImpl {
        public TreeItemImpl() {
            initializeClonableElements();
        }

        public void ensureMargin(TreeItem item, TreeItem parent) {
            // Set the margin.
            // Use no margin on top-most items.
            double margin = parent.isRoot ? 0.0 : CHILD_MARGIN;
            if (LocaleInfo.getCurrentLocale().isRTL()) {
                item.getElement().getStyle().setMarginRight(margin, Unit.PX);
            } else {
                item.getElement().getStyle().setMarginLeft(margin, Unit.PX);
            }
        }

        void convertToFullNode(TreeItem item) {
            if (item.imageHolder == null) {
                // Extract the Elements from the object
                Element itemDiv = DOM.clone(BASE_INTERNAL_ELEM, true);
                DOM.appendChild(item.getElement(), itemDiv);
                Element imgHolder = DOM.getFirstChild(itemDiv);
                Element contentHolder = DOM.getNextSibling(imgHolder);
                // Undoes padding from table element.
                DOM.setStyleAttribute(item.getElement(), "padding", "0px");
                DOM.appendChild(contentHolder, item.contentElem);
                item.imageHolder = imgHolder;
            }
        }

        void ensureState(TreeItem item, PotentialState state) {
            if (item.potentialState.ordinal() >= state.ordinal()) {
                return;
            }
            if (item.imageHolder == null) {
                item.ensureElements();
                // item.contentElem = item.resolve(item.contentElem);
                item.contentElem.setClassName("gwt-TreeItem");
                // item.setElement(item.resolve(item.getElement()));
                convertToFullNode(item);
                Ax.out("to full node: %s", item.getIndex());
            }
            if (state == PotentialState.INSTANTIATED) {
                item.initChildSpanElement();
            }
            TreeItem parentItem = item.getParentItem();
            item.potentialState = state;
            if (!item.isRoot && parentItem != null && item.getElement().getParentElement() == null) {
                ensureState(parentItem, PotentialState.INSTANTIATED);
                ensureMargin(item, parentItem);
                Element childContainer = parentItem.isRoot ? item.getTree().getElement() : parentItem.childSpanElem;
                childContainer.appendChild(item.getElement());
                item.updateState(false, false);
            }
        }

        /**
         * Setup clonable elements.
         */
        void initializeClonableElements() {
            if (GWT.isClient()) {
                // Create the base table element that will be cloned.
                BASE_INTERNAL_ELEM = DOM.createDiv();
                Element contentElem = DOM.createDiv();
                Element imageHolder = DOM.createDiv();
                DOM.appendChild(BASE_INTERNAL_ELEM, imageHolder);
                // note, error in original GWT code here used contentElem (which
                // is subsequently removed by later .append)
                DOM.appendChild(BASE_INTERNAL_ELEM, DOM.createDiv());
                setStyleName(contentElem, "gwt-TreeItem");
                setStyleName(imageHolder, "gwt-TreeItem-ImageHolder");
                setStyleName(BASE_INTERNAL_ELEM, "gwt-TreeItem-Holder");
                // Create the base element that will be cloned
                BASE_BARE_ELEM = DOM.createDiv();
                // Simulates padding from table element.
                DOM.setStyleAttribute(BASE_BARE_ELEM, "padding", "3px");
                DOM.appendChild(BASE_BARE_ELEM, contentElem);
                Accessibility.setRole(contentElem, Accessibility.ROLE_TREEITEM);
            }
        }
    }

    /**
     * IE specific implementation class for {@link TreeItem}.
     */
    public static class TreeItemImplIE8ToIE10 extends TreeItemImpl {
        @Override
        void convertToFullNode(TreeItem item) {
            super.convertToFullNode(item);
            item.getElement().getStyle().setProperty("marginBottom", "0px");
        }
    }

    /**
     * An {@link Animation} used to open the child elements. If a
     * {@link TreeItem} is in the process of opening, it will immediately be
     * opened and the new {@link TreeItem} will use this animation.
     */
    private static class TreeItemAnimation extends Animation {
        /**
         * The {@link TreeItem} currently being affected.
         */
        private TreeItem curItem = null;

        /**
         * Whether the item is being opened or closed.
         */
        private boolean opening = true;

        /**
         * The target height of the child items.
         */
        private int scrollHeight = 0;

        /**
         * Open the specified {@link TreeItem}.
         * 
         * @param item
         *            the {@link TreeItem} to open
         * @param animate
         *            true to animate, false to open instantly
         */
        public void setItemState(TreeItem item, boolean animate) {
            // Immediately complete previous open
            cancel();
            // Open the new item
            if (animate) {
                curItem = item;
                opening = item.open;
                run(Math.min(ANIMATION_DURATION, ANIMATION_DURATION_PER_ITEM * curItem.getChildCount()));
            } else {
                if (item.potentialState == PotentialState.INSTANTIATED) {
                    UIObject.setVisible(item.childSpanElem, item.open);
                }
            }
        }

        @Override
        protected void onComplete() {
            if (curItem != null) {
                if (opening) {
                    UIObject.setVisible(curItem.childSpanElem, true);
                    onUpdate(1.0);
                    DOM.setStyleAttribute(curItem.childSpanElem, "height", "auto");
                } else {
                    UIObject.setVisible(curItem.childSpanElem, false);
                }
                DOM.setStyleAttribute(curItem.childSpanElem, "overflow", "visible");
                DOM.setStyleAttribute(curItem.childSpanElem, "width", "auto");
                curItem = null;
            }
        }

        @Override
        protected void onStart() {
            scrollHeight = 0;
            // If the TreeItem is already open, we can get its scrollHeight
            // immediately.
            if (!opening) {
                scrollHeight = curItem.childSpanElem.getScrollHeight();
            }
            DOM.setStyleAttribute(curItem.childSpanElem, "overflow", "hidden");
            // If the TreeItem is already open, onStart will set its height to
            // its
            // natural height. If the TreeItem is currently closed, onStart will
            // set
            // its height to 1px (see onUpdate below), and then we make the
            // TreeItem
            // visible so we can get its correct scrollHeight.
            super.onStart();
            // If the TreeItem is currently closed, we need to make it visible
            // before
            // we can get its height.
            if (opening) {
                UIObject.setVisible(curItem.childSpanElem, true);
                scrollHeight = curItem.childSpanElem.getScrollHeight();
            }
        }

        @Override
        protected void onUpdate(double progress) {
            int height = (int) (progress * scrollHeight);
            if (!opening) {
                height = scrollHeight - height;
            }
            // Issue 2338: If the height is 0px, IE7 will display all of the
            // children
            // instead of hiding them completely.
            height = Math.max(height, 1);
            DOM.setStyleAttribute(curItem.childSpanElem, "height", height + "px");
            // We need to set the width explicitly of the item might be cropped
            int scrollWidth = DOM.getElementPropertyInt(curItem.childSpanElem, "scrollWidth");
            DOM.setStyleAttribute(curItem.childSpanElem, "width", scrollWidth + "px");
        }
    }

    enum PotentialState {
        POTENTIAL, POTENTIAL_CHILDREN, INSTANTIATED
    }

    AbstractImagePrototype getCurrentImagePrototype(Element child) {
        return currentImagePrototypesData.get(child);
    }

    void setCurrentImagePrototype(Element child, AbstractImagePrototype prototype) {
        currentImagePrototypesData.put(child, prototype);
    }

    Map<Element, AbstractImagePrototype> currentImagePrototypesData = new LightMap<>();
}