]> source.dussan.org Git - vaadin-framework.git/commitdiff
Optional expand and collapse animations for TreeTable (#6723)
authorJonatan Kronqvist <jonatan.kronqvist@itmill.com>
Wed, 17 Aug 2011 10:20:39 +0000 (10:20 +0000)
committerJonatan Kronqvist <jonatan.kronqvist@itmill.com>
Wed, 17 Aug 2011 10:20:39 +0000 (10:20 +0000)
svn changeset:20445/svn branch:6.7

12 files changed:
WebContent/VAADIN/themes/base/table/table.css
WebContent/VAADIN/themes/base/treetable/treetable.css
WebContent/VAADIN/themes/tests-components/styles.css
src/com/vaadin/terminal/gwt/client/ComputedStyle.java [new file with mode: 0644]
src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java
src/com/vaadin/terminal/gwt/client/ui/VTreeTable.java
src/com/vaadin/ui/Table.java
src/com/vaadin/ui/TreeTable.java
tests/src/com/vaadin/tests/components/table/Tables.java
tests/src/com/vaadin/tests/components/treetable/ExpandAnimationsInChameleon.java [new file with mode: 0644]
tests/src/com/vaadin/tests/components/treetable/TreeTableExpandWithRowStyle [new file with mode: 0644]
tests/src/com/vaadin/tests/components/treetable/TreeTableTest.java

index 425f8243d6ee0e01706cc87ddb565da80a7ce6cc..c618c0758738f78b6aff534012310f2426875ee3 100644 (file)
 }
 .v-table-row,
 .v-table-row-odd {
+    background: #fff;
        border: 0;
        margin: 0;
        padding: 0;
index ee5aaed861d2f86d5685a97c84e79f5b3b87c3ba..eec6c794d330245625b329418667feb69efb424c 100644 (file)
 .v-treetable .v-checkbox {
        display: inline-block;
        padding-bottom: 4px;
-}
\ No newline at end of file
+}
+
+.v-treetable .v-table-row .v-table-cell-content,
+.v-treetable .v-table-row-odd .v-table-cell-content {
+    position: relative;
+    z-index: 10;
+}
+
+.v-treetable .v-table-body .v-table-table .v-table-row-animating {
+       zoom:1;
+       z-index:1;
+}
+
+.v-treetable .v-table-body .v-table-table .v-table-row-animating,
+.v-treetable .v-table-body .v-table-table .v-table-row-animating .v-table-cell-content {
+    background:transparent;
+}
+
+.v-treetable-animation-clone {
+       border-spacing: 0;
+       zoom:1;
+}
+
+div.v-treetable-animation-clone-wrapper {
+       position: absolute;
+    z-index: 2;
+    background-color:#fff;
+}
+
+div.v-treetable-animation-clone-wrapper table.v-treetable-animation-clone {
+    background-color:#fff;
+}
+
+div table.v-treetable-animation-clone tr.v-table-row,
+div table.v-treetable-animation-clone tr.v-table-row-odd,
+div table.v-treetable-animation-clone tr.v-table-row td.v-table-cell-content,
+div table.v-treetable-animation-clone tr.v-table-row-odd td.v-table-cell-content {
+       visibility: visible;
+}
index 3e59aaec7a6f8cf85ab6b770704ad38db022ee36..da173964f45ffc90ee76d13a459ffaa8de7f881b 100644 (file)
@@ -32,3 +32,8 @@
 .border-blue-2px {\r
        border: 2px solid blue;\r
 }\r
+\r
+.v-table-row-tables-test-cell-style-red-row,\r
+.v-table-cell-content-tables-test-cell-style-red-row {\r
+       background: #f00;\r
+}
\ No newline at end of file
diff --git a/src/com/vaadin/terminal/gwt/client/ComputedStyle.java b/src/com/vaadin/terminal/gwt/client/ComputedStyle.java
new file mode 100644 (file)
index 0000000..5aac2ca
--- /dev/null
@@ -0,0 +1,178 @@
+package com.vaadin.terminal.gwt.client;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.user.client.Element;
+
+public class ComputedStyle {
+
+    protected final JavaScriptObject computedStyle;
+    private final Element elem;
+
+    /**
+     * Gets this element's computed style object which can be used to gather
+     * information about the current state of the rendered node.
+     * <p>
+     * Note that this method is expensive. Wherever possible, reuse the returned
+     * object.
+     * 
+     * @param elem
+     *            the element
+     * @return the computed style
+     */
+    public ComputedStyle(Element elem) {
+        computedStyle = getComputedStyle(elem);
+        this.elem = elem;
+    }
+
+    private static native JavaScriptObject getComputedStyle(Element elem)
+    /*-{
+      if(elem.nodeType != 1) {
+          return {};
+      }
+      
+      if($wnd.document.defaultView && $wnd.document.defaultView.getComputedStyle) {
+          return $wnd.document.defaultView.getComputedStyle(elem, null);
+      }
+      
+      if(elem.currentStyle) {
+          return elem.currentStyle;
+      }
+    }-*/;
+
+    /**
+     * 
+     * @param name
+     *            name of the CSS property in camelCase
+     * @return the value of the property, normalized for across browsers (each
+     *         browser returns pixel values whenever possible).
+     */
+    public final native String getProperty(String name)
+    /*-{
+        var cs = this.@com.vaadin.terminal.gwt.client.ComputedStyle::computedStyle;
+        var elem = this.@com.vaadin.terminal.gwt.client.ComputedStyle::elem;
+        
+        // Border values need to be checked separately. The width might have a 
+        // meaningful value even if the border style is "none". In that case the 
+        // value should be 0.
+        if(name.indexOf("border") > -1 && name.indexOf("Width") > -1) {
+            var borderStyleProp = name.substring(0,name.length-5) + "Style";
+            if(cs.getPropertyValue)
+                var borderStyle = cs.getPropertyValue(borderStyleProp);
+            else // IE
+                var borderStyle = cs[borderStyleProp];
+            if(borderStyle == "none")
+                return "0px";
+        }
+
+        if(cs.getPropertyValue) {
+        
+            // Convert name to dashed format
+            name = name.replace(/([A-Z])/g, "-$1").toLowerCase();
+            var ret = cs.getPropertyValue(name);
+            
+        } else {
+        
+            var ret = cs[name];
+            var style = elem.style;
+
+            // From the awesome hack by Dean Edwards
+            // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+
+            // If we're not dealing with a regular pixel number
+            // but a number that has a weird ending, we need to convert it to pixels
+            if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) {
+                // Remember the original values
+                var left = style.left, rsLeft = elem.runtimeStyle.left;
+
+                // Put in the new values to get a computed value out
+                elem.runtimeStyle.left = this.left;
+                style.left = ret || 0;
+                ret = style.pixelLeft + "px";
+
+                // Revert the changed values
+                style.left = left;
+                elem.runtimeStyle.left = rsLeft;
+            }
+            
+        }
+        
+        // Normalize margin values. This is not totally valid, but in most cases 
+        // it is what the user wants to know.
+        if(name.indexOf("margin") > -1 && ret == "auto") {
+            return "0px";
+        }
+        
+        // Some browsers return undefined width and height values as "auto", so
+        // we need to retrieve those ourselves.
+        if (name == "width" && ret == "auto") {
+            ret = elem.clientWidth + "px";
+        } else if (name == "height" && ret == "auto") {
+            ret = elem.clientHeight + "px";
+        }
+
+        return ret;
+        
+    }-*/;
+
+    public final int getIntProperty(String name) {
+        return parseInt(getProperty(name)).intValue();
+    }
+
+    /**
+     * Get current margin values from the DOM. The array order is the default
+     * CSS order: top, right, bottom, left.
+     */
+    public final int[] getMargin() {
+        int[] margin = { 0, 0, 0, 0 };
+        margin[0] = getIntProperty("marginTop");
+        margin[1] = getIntProperty("marginRight");
+        margin[2] = getIntProperty("marginBottom");
+        margin[3] = getIntProperty("marginLeft");
+        return margin;
+    }
+
+    /**
+     * Get current padding values from the DOM. The array order is the default
+     * CSS order: top, right, bottom, left.
+     */
+    public final int[] getPadding() {
+        int[] padding = { 0, 0, 0, 0 };
+        padding[0] = getIntProperty("paddingTop");
+        padding[1] = getIntProperty("paddingRight");
+        padding[2] = getIntProperty("paddingBottom");
+        padding[3] = getIntProperty("paddingLeft");
+        return padding;
+    }
+
+    /**
+     * Get current border values from the DOM. The array order is the default
+     * CSS order: top, right, bottom, left.
+     */
+    public final int[] getBorder() {
+        int[] border = { 0, 0, 0, 0 };
+        border[0] = getIntProperty("borderTopWidth");
+        border[1] = getIntProperty("borderRightWidth");
+        border[2] = getIntProperty("borderBottomWidth");
+        border[3] = getIntProperty("borderLeftWidth");
+        return border;
+    }
+
+    /**
+     * Takes a String value e.g. "12px" and parses that to int 12.
+     * 
+     * @param String
+     *            a value starting with a number
+     * @return int the value from the string before any non-numeric characters.
+     *         If the value cannot be parsed to a number, returns
+     *         <code>null</code>.
+     */
+    public static native Integer parseInt(final String value)
+    /*-{
+        var number = parseInt(value, 10);
+        if (isNaN(number))
+            return null;
+        else
+            return @java.lang.Integer::valueOf(I)(number);
+    }-*/;
+
+}
index ad69d8a751cbf2e78bc24f4113bff71b8d39afb7..fb51919b102243c7fcbac3f2a8151f77ae7f57ec 100644 (file)
@@ -876,7 +876,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
         UIDL partialRowUpdates = uidl.getChildByTagName("urows");
         if (partialRowUpdates != null || partialRowAdditions != null) {
             updateRowsInBody(partialRowUpdates);
-            addRowsToBody(partialRowAdditions);
+            addAndRemoveRows(partialRowAdditions);
         } else {
             UIDL rowData = uidl.getChildByTagName("rows");
             if (rowData != null) {
@@ -1355,7 +1355,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
 
         scrollBody.renderRows(uidl, firstRow, reqRows);
 
-        updateCache();
+        discardRowsOutsideCacheWindow();
     }
 
     private void updateRowsInBody(UIDL partialRowUpdates) {
@@ -1367,10 +1367,14 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
         scrollBody.unlinkRows(firstRowIx, count);
         scrollBody.insertRows(partialRowUpdates, firstRowIx, count);
 
-        updateCache();
+        discardRowsOutsideCacheWindow();
     }
 
-    private void updateCache() {
+    /**
+     * Updates the internal cache by unlinking rows that fall outside of the
+     * caching window.
+     */
+    protected void discardRowsOutsideCacheWindow() {
         final int optimalFirstRow = (int) (firstRowInViewPort - pageLength
                 * cache_rate);
         boolean cont = true;
@@ -1391,7 +1395,14 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
         scrollBody.restoreRowVisibility();
     }
 
-    private void addRowsToBody(UIDL partialRowAdditions) {
+    /**
+     * Inserts rows in the table body or removes them from the table body based
+     * on the commands in the UIDL.
+     * 
+     * @param partialRowAdditions
+     *            the UIDL containing row updates.
+     */
+    protected void addAndRemoveRows(UIDL partialRowAdditions) {
         if (partialRowAdditions == null) {
             return;
         }
@@ -1412,7 +1423,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
             }
         }
 
-        updateCache();
+        discardRowsOutsideCacheWindow();
     }
 
     /**
@@ -3890,14 +3901,26 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
             }
         }
 
-        protected void insertRows(UIDL rowData, int firstIndex, int rows) {
+        /**
+         * Inserts rows as provided in the rowData starting at firstIndex.
+         * 
+         * @param rowData
+         * @param firstIndex
+         * @param rows
+         *            the number of rows
+         * @return a list of the rows added.
+         */
+        protected List<VScrollTableRow> insertRows(UIDL rowData,
+                int firstIndex, int rows) {
             aligns = tHead.getColumnAlignments();
             final Iterator<?> it = rowData.getChildIterator();
+            List<VScrollTableRow> insertedRows = new ArrayList<VScrollTableRow>();
 
             if (firstIndex == lastRendered + 1) {
                 while (it.hasNext()) {
                     final VScrollTableRow row = prepareRow((UIDL) it.next());
                     addRow(row);
+                    insertedRows.add(row);
                     lastRendered++;
                 }
                 fixSpacers();
@@ -3910,18 +3933,22 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
                 }
                 for (i = 0; i < rows; i++) {
                     addRowBeforeFirstRendered(rowArray[i]);
+                    insertedRows.add(rowArray[i]);
                     firstRendered--;
                 }
             } else {
                 // insert in the middle
                 int realIx = firstIndex - firstRendered;
                 while (it.hasNext()) {
-                    insertRowAt(prepareRow((UIDL) it.next()), realIx);
+                    VScrollTableRow row = prepareRow((UIDL) it.next());
+                    insertRowAt(row, realIx);
+                    insertedRows.add(row);
                     lastRendered++;
                     realIx++;
                 }
                 fixSpacers();
             }
+            return insertedRows;
         }
 
         protected void insertRowsDeleteBelow(UIDL rowData, int firstIndex,
index 15ebcde35e69c8bb0193016baf71d6bb05abc64b..a3b7008e773ee15fb1ddf657fe33f34d7941525e 100644 (file)
@@ -4,17 +4,25 @@
 
 package com.vaadin.terminal.gwt.client.ui;
 
+import java.util.ArrayList;
 import java.util.Iterator;
+import java.util.List;
 
+import com.google.gwt.animation.client.Animation;
 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.Display;
 import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.dom.client.Style.Visibility;
 import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.user.client.DOM;
 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.BrowserInfo;
+import com.vaadin.terminal.gwt.client.ComputedStyle;
 import com.vaadin.terminal.gwt.client.RenderSpace;
 import com.vaadin.terminal.gwt.client.UIDL;
 import com.vaadin.terminal.gwt.client.VConsole;
@@ -29,6 +37,7 @@ public class VTreeTable extends VScrollTable {
     private int colIndexOfHierarchy;
     private String collapsedRowKey;
     private VTreeTableScrollBody scrollBody;
+    private boolean animationsEnabled;
 
     @Override
     public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
@@ -38,6 +47,7 @@ public class VTreeTable extends VScrollTable {
             widget = (FocusableScrollPanel) getWidget(1);
             scrollPosition = widget.getScrollPosition();
         }
+        animationsEnabled = uidl.getBooleanAttribute("animate");
         colIndexOfHierarchy = uidl
                 .hasAttribute(ATTRIBUTE_HIERARCHY_COLUMN_INDEX) ? uidl
                 .getIntAttribute(ATTRIBUTE_HIERARCHY_COLUMN_INDEX) : 0;
@@ -73,6 +83,36 @@ public class VTreeTable extends VScrollTable {
         return scrollBody;
     }
 
+    /*
+     * Overridden to allow animation of expands and collapses of nodes.
+     */
+    @Override
+    protected void addAndRemoveRows(UIDL partialRowAdditions) {
+        if (partialRowAdditions == null) {
+            return;
+        }
+
+        if (animationsEnabled && browserSupportsAnimation()) {
+            if (partialRowAdditions.hasAttribute("hide")) {
+                scrollBody.unlinkRowsAnimatedAndUpdateCacheWhenFinished(
+                        partialRowAdditions.getIntAttribute("firstprowix"),
+                        partialRowAdditions.getIntAttribute("numprows"));
+            } else {
+                scrollBody.insertRowsAnimated(partialRowAdditions,
+                        partialRowAdditions.getIntAttribute("firstprowix"),
+                        partialRowAdditions.getIntAttribute("numprows"));
+                discardRowsOutsideCacheWindow();
+            }
+        } else {
+            super.addAndRemoveRows(partialRowAdditions);
+        }
+    }
+
+    private boolean browserSupportsAnimation() {
+        BrowserInfo bi = BrowserInfo.get();
+        return !(bi.isIE6() || bi.isIE7() || bi.isSafari4());
+    }
+
     class VTreeTableScrollBody extends VScrollTable.VScrollTableBody {
         private int identWidth = -1;
 
@@ -241,6 +281,304 @@ public class VTreeTable extends VScrollTable {
                 next.setIdent();
             }
         }
+
+        protected void unlinkRowsAnimatedAndUpdateCacheWhenFinished(
+                final int firstIndex, final int rows) {
+            List<VScrollTableRow> rowsToDelete = new ArrayList<VScrollTableRow>();
+            for (int ix = firstIndex; ix < firstIndex + rows; ix++) {
+                VScrollTableRow row = getRowByRowIndex(ix);
+                if (row != null) {
+                    rowsToDelete.add(row);
+                }
+            }
+            RowCollapseAnimation anim = new RowCollapseAnimation(rowsToDelete) {
+                @Override
+                protected void onComplete() {
+                    super.onComplete();
+                    // Actually unlink the rows and update the cache after the
+                    // animation is done.
+                    unlinkRows(firstIndex, rows);
+                    discardRowsOutsideCacheWindow();
+                    ensureCacheFilled();
+                }
+            };
+            anim.run(150);
+        }
+
+        protected List<VScrollTableRow> insertRowsAnimated(UIDL rowData,
+                int firstIndex, int rows) {
+            List<VScrollTableRow> insertedRows = insertRows(rowData,
+                    firstIndex, rows);
+            RowExpandAnimation anim = new RowExpandAnimation(insertedRows);
+            anim.run(150);
+            return insertedRows;
+        }
+
+        /**
+         * Prepares the table for animation by copying the background colors of
+         * all TR elements to their respective TD elements if the TD element is
+         * transparent. This is needed, since if TDs have transparent
+         * backgrounds, the rows sliding behind them are visible.
+         */
+        private class AnimationPreparator {
+            private final int lastItemIx;
+
+            public AnimationPreparator(int lastItemIx) {
+                this.lastItemIx = lastItemIx;
+            }
+
+            public void prepareTableForAnimation() {
+                int ix = lastItemIx;
+                VScrollTableRow row = null;
+                while ((row = getRowByRowIndex(ix)) != null) {
+                    copyTRBackgroundsToTDs(row);
+                    --ix;
+                }
+            }
+
+            private void copyTRBackgroundsToTDs(VScrollTableRow row) {
+                Element tr = row.getElement();
+                ComputedStyle cs = new ComputedStyle(tr);
+                String backgroundAttachment = cs
+                        .getProperty("backgroundAttachment");
+                String backgroundClip = cs.getProperty("backgroundClip");
+                String backgroundColor = cs.getProperty("backgroundColor");
+                String backgroundImage = cs.getProperty("backgroundImage");
+                String backgroundOrigin = cs.getProperty("backgroundOrigin");
+                for (int ix = 0; ix < tr.getChildCount(); ix++) {
+                    Element td = tr.getChild(ix).cast();
+                    if (!elementHasBackground(td)) {
+                        td.getStyle().setProperty("backgroundAttachment",
+                                backgroundAttachment);
+                        td.getStyle().setProperty("backgroundClip",
+                                backgroundClip);
+                        td.getStyle().setProperty("backgroundColor",
+                                backgroundColor);
+                        td.getStyle().setProperty("backgroundImage",
+                                backgroundImage);
+                        td.getStyle().setProperty("backgroundOrigin",
+                                backgroundOrigin);
+                    }
+                }
+            }
+
+            private boolean elementHasBackground(Element element) {
+                ComputedStyle cs = new ComputedStyle(element);
+                String clr = cs.getProperty("backgroundColor");
+                String img = cs.getProperty("backgroundImage");
+                return !("rgba(0, 0, 0, 0)".equals(clr.trim())
+                        || "transparent".equals(clr.trim()) || img == null);
+            }
+
+            public void restoreTableAfterAnimation() {
+                int ix = lastItemIx;
+                VScrollTableRow row = null;
+                while ((row = getRowByRowIndex(ix)) != null) {
+                    restoreStyleForTDsInRow(row);
+
+                    --ix;
+                }
+            }
+
+            private void restoreStyleForTDsInRow(VScrollTableRow row) {
+                Element tr = row.getElement();
+                for (int ix = 0; ix < tr.getChildCount(); ix++) {
+                    Element td = tr.getChild(ix).cast();
+                    td.getStyle().clearProperty("backgroundAttachment");
+                    td.getStyle().clearProperty("backgroundClip");
+                    td.getStyle().clearProperty("backgroundColor");
+                    td.getStyle().clearProperty("backgroundImage");
+                    td.getStyle().clearProperty("backgroundOrigin");
+                }
+            }
+        }
+
+        /**
+         * Animates row expansion using the GWT animation framework.
+         * 
+         * The idea is as follows:
+         * 
+         * 1. Insert all rows normally
+         * 
+         * 2. Insert a newly created DIV containing a new TABLE element below
+         * the DIV containing the actual scroll table body.
+         * 
+         * 3. Clone the rows that were inserted in step 1 and attach the clones
+         * to the new TABLE element created in step 2.
+         * 
+         * 4. The new DIV from step 2 is absolutely positioned so that the last
+         * inserted row is just behind the row that was expanded.
+         * 
+         * 5. Hide the contents of the originally inserted rows by setting the
+         * DIV.v-table-cell-wrapper to display:none;.
+         * 
+         * 6. Set the height of the originally inserted rows to 0.
+         * 
+         * 7. The animation loop slides the DIV from step 2 downwards, while at
+         * the same pace growing the height of each of the inserted rows from 0
+         * to full height. The first inserted row grows from 0 to full and after
+         * this the second row grows from 0 to full, etc until all rows are full
+         * height.
+         * 
+         * 8. Remove the DIV from step 2
+         * 
+         * 9. Restore display:block; to the DIV.v-table-cell-wrapper elements.
+         * 
+         * 10. DONE
+         */
+        private class RowExpandAnimation extends Animation {
+
+            private final List<VScrollTableRow> rows;
+            private Element cloneDiv;
+            private Element cloneTable;
+            private AnimationPreparator preparator;
+
+            public RowExpandAnimation(List<VScrollTableRow> rows) {
+                this.rows = rows;
+                buildAndInsertAnimatingDiv();
+                preparator = new AnimationPreparator(rows.get(0).getIndex() - 1);
+                preparator.prepareTableForAnimation();
+                for (VScrollTableRow row : rows) {
+                    cloneAndAppendRow(row);
+                    row.addStyleName("v-table-row-animating");
+                    setCellWrapperDivsToDisplayNone(row);
+                    row.setHeight(getInitialHeight());
+                }
+            }
+
+            protected String getInitialHeight() {
+                return "0px";
+            }
+
+            private void cloneAndAppendRow(VScrollTableRow row) {
+                Element clonedTR = null;
+                clonedTR = row.getElement().cloneNode(true).cast();
+                clonedTR.getStyle().setVisibility(Visibility.VISIBLE);
+                cloneTable.appendChild(clonedTR);
+            }
+
+            protected double getBaseOffset() {
+                return rows.get(0).getAbsoluteTop()
+                        - rows.get(0).getParent().getAbsoluteTop()
+                        - rows.size() * getRowHeight();
+            }
+
+            private void buildAndInsertAnimatingDiv() {
+                cloneDiv = DOM.createDiv();
+                cloneDiv.addClassName("v-treetable-animation-clone-wrapper");
+                cloneTable = DOM.createTable();
+                cloneTable.addClassName("v-treetable-animation-clone");
+                cloneDiv.appendChild(cloneTable);
+                insertAnimatingDiv();
+            }
+
+            private void insertAnimatingDiv() {
+                Element tableBody = getElement().cast();
+                Element tableBodyParent = tableBody.getParentElement().cast();
+                tableBodyParent.insertAfter(cloneDiv, tableBody);
+            }
+
+            @Override
+            protected void onUpdate(double progress) {
+                animateDiv(progress);
+                animateRowHeights(progress);
+            }
+
+            private void animateDiv(double progress) {
+                double offset = calculateDivOffset(progress, getRowHeight());
+
+                cloneDiv.getStyle().setTop(getBaseOffset() + offset, Unit.PX);
+            }
+
+            private void animateRowHeights(double progress) {
+                double rh = getRowHeight();
+                double vlh = calculateHeightOfAllVisibleLines(progress, rh);
+                int ix = 0;
+
+                while (ix < rows.size()) {
+                    double height = vlh < rh ? vlh : rh;
+                    rows.get(ix).setHeight(height + "px");
+                    vlh -= height;
+                    ix++;
+                }
+            }
+
+            protected double calculateHeightOfAllVisibleLines(double progress,
+                    double rh) {
+                return rows.size() * rh * progress;
+            }
+
+            protected double calculateDivOffset(double progress, double rh) {
+                return progress * rows.size() * rh;
+            }
+
+            @Override
+            protected void onComplete() {
+                preparator.restoreTableAfterAnimation();
+                for (VScrollTableRow row : rows) {
+                    resetCellWrapperDivsDisplayProperty(row);
+                    row.removeStyleName("v-table-row-animating");
+                }
+                Element tableBodyParent = (Element) getElement()
+                        .getParentElement();
+                tableBodyParent.removeChild(cloneDiv);
+            }
+
+            private void setCellWrapperDivsToDisplayNone(VScrollTableRow row) {
+                Element tr = row.getElement();
+                for (int ix = 0; ix < tr.getChildCount(); ix++) {
+                    getWrapperDiv(tr, ix).getStyle().setDisplay(Display.NONE);
+                }
+            }
+
+            private Element getWrapperDiv(Element tr, int tdIx) {
+                Element td = tr.getChild(tdIx).cast();
+                return td.getChild(0).cast();
+            }
+
+            private void resetCellWrapperDivsDisplayProperty(VScrollTableRow row) {
+                Element tr = row.getElement();
+                for (int ix = 0; ix < tr.getChildCount(); ix++) {
+                    getWrapperDiv(tr, ix).getStyle().clearProperty("display");
+                }
+            }
+
+        }
+
+        /**
+         * This is the inverse of the RowExpandAnimation and is implemented by
+         * extending it and overriding the calculation of offsets and heights.
+         */
+        private class RowCollapseAnimation extends RowExpandAnimation {
+
+            private final List<VScrollTableRow> rows;
+
+            public RowCollapseAnimation(List<VScrollTableRow> rows) {
+                super(rows);
+                this.rows = rows;
+            }
+
+            @Override
+            protected String getInitialHeight() {
+                return getRowHeight() + "px";
+            }
+
+            @Override
+            protected double getBaseOffset() {
+                return getRowHeight();
+            }
+
+            @Override
+            protected double calculateHeightOfAllVisibleLines(double progress,
+                    double rh) {
+                return rows.size() * rh * (1 - progress);
+            }
+
+            @Override
+            protected double calculateDivOffset(double progress, double rh) {
+                return -super.calculateDivOffset(progress, rh);
+            }
+        }
     }
 
     /**
index fd18bf0fd78e10ac89c6e52253d69df6d314f203..5f0fd0727744e96d85d477c1dc172e27fe83f5df 100644 (file)
@@ -378,7 +378,7 @@ public class Table extends AbstractSelect implements Action.Container,
      * Table cell specific style generator
      */
     private CellStyleGenerator cellStyleGenerator = null;
-    
+
     /**
      * Table cell specific tooltip generator
      */
@@ -2867,8 +2867,8 @@ public class Table extends AbstractSelect implements Action.Container,
                     target.addAttribute("style-" + columnIdMap.key(columnId),
                             cellStyle);
                 }
-            }           
-            
+            }
+
             if ((iscomponent[currentColumn] || iseditable)
                     && Component.class.isInstance(cells[CELL_FIRSTCOL
                             + currentColumn][indexInRowbuffer])) {
@@ -2881,29 +2881,34 @@ public class Table extends AbstractSelect implements Action.Container,
                     c.paint(target);
                 }
             } else {
-                target.addText((String) cells[CELL_FIRSTCOL + currentColumn][indexInRowbuffer]);                
-                       paintCellTooltips(target, itemId, columnId);
+                target.addText((String) cells[CELL_FIRSTCOL + currentColumn][indexInRowbuffer]);
+                paintCellTooltips(target, itemId, columnId);
             }
         }
 
         target.endTag("tr");
     }
-    
-    private void paintCellTooltips(PaintTarget target, Object itemId, Object columnId) throws PaintException {
-        if(itemDescriptionGenerator != null) {
-               String itemDescription = itemDescriptionGenerator.generateDescription(this, itemId, columnId);
-               if(itemDescription != null && !itemDescription.equals("")) {
-                       target.addAttribute("descr-" + columnIdMap.key(columnId), itemDescription);
-               }               
-         }
+
+    private void paintCellTooltips(PaintTarget target, Object itemId,
+            Object columnId) throws PaintException {
+        if (itemDescriptionGenerator != null) {
+            String itemDescription = itemDescriptionGenerator
+                    .generateDescription(this, itemId, columnId);
+            if (itemDescription != null && !itemDescription.equals("")) {
+                target.addAttribute("descr-" + columnIdMap.key(columnId),
+                        itemDescription);
+            }
+        }
     }
-    
-    private void paintRowTooltips(PaintTarget target, Object itemId ) throws PaintException {    
-        if(itemDescriptionGenerator != null) {
-               String rowDescription = itemDescriptionGenerator.generateDescription(this, itemId, null);
-               if(rowDescription != null && !rowDescription.equals("")){
-                       target.addAttribute("rowdescr", rowDescription);
-               }
+
+    private void paintRowTooltips(PaintTarget target, Object itemId)
+            throws PaintException {
+        if (itemDescriptionGenerator != null) {
+            String rowDescription = itemDescriptionGenerator
+                    .generateDescription(this, itemId, null);
+            if (rowDescription != null && !rowDescription.equals("")) {
+                target.addAttribute("rowdescr", rowDescription);
+            }
         }
     }
 
@@ -2947,11 +2952,11 @@ public class Table extends AbstractSelect implements Action.Container,
             if (rowStyle != null && !rowStyle.equals("")) {
                 target.addAttribute("rowstyle", rowStyle);
             }
-        }               
-        
-        paintRowTooltips(target, itemId);        
-        
-        paintRowAttributes(target, itemId);        
+        }
+
+        paintRowTooltips(target, itemId);
+
+        paintRowAttributes(target, itemId);
     }
 
     protected void paintRowHeader(PaintTarget target, Object[][] cells,
@@ -3895,7 +3900,7 @@ public class Table extends AbstractSelect implements Action.Container,
      */
     public void setCellStyleGenerator(CellStyleGenerator cellStyleGenerator) {
         this.cellStyleGenerator = cellStyleGenerator;
-        requestRepaint();
+        refreshRenderedCells();
     }
 
     /**
@@ -4576,27 +4581,27 @@ public class Table extends AbstractSelect implements Action.Container,
         removeListener(VScrollTable.COLUMN_REORDER_EVENT_ID,
                 ColumnReorderEvent.class, listener);
     }
-    
+
     /**
-     * Set the item description generator which generates tooltips 
-     * for cells and rows in the Table
+     * Set the item description generator which generates tooltips for cells and
+     * rows in the Table
      * 
      * @param generator
-     *                 The generator to use or null to disable
+     *            The generator to use or null to disable
      */
-    public void setItemDescriptionGenerator(ItemDescriptionGenerator generator){
+    public void setItemDescriptionGenerator(ItemDescriptionGenerator generator) {
         if (generator != itemDescriptionGenerator) {
             itemDescriptionGenerator = generator;
             refreshRenderedCells();
         }
     }
-    
+
     /**
-     * Get the item description generator which generates tooltips 
-     * for cells and rows in the Table.
+     * Get the item description generator which generates tooltips for cells and
+     * rows in the Table.
      */
-    public ItemDescriptionGenerator getItemDescriptionGenerator(){
-       return itemDescriptionGenerator;
+    public ItemDescriptionGenerator getItemDescriptionGenerator() {
+        return itemDescriptionGenerator;
     }
 
 }
index 987e3a9bfd13983056603ccbbc7500fbeaa1ac2e..f64c81831385c3f391e91fa1a66aff8e3534bce8 100644 (file)
@@ -309,6 +309,7 @@ public class TreeTable extends Table implements Hierarchical {
     private Object focusedRowId = null;
     private Object hierarchyColumnId;
     private Object toggledItemId;
+    private boolean animationsEnabled;
 
     private ContainerStrategy getContainerStrategy() {
         if (cStrategy == null) {
@@ -408,6 +409,7 @@ public class TreeTable extends Table implements Hierarchical {
             target.addAttribute("focusedRow", itemIdMapper.key(focusedRowId));
             focusedRowId = null;
         }
+        target.addAttribute("animate", animationsEnabled);
         if (hierarchyColumnId != null) {
             Object[] visibleColumns2 = getVisibleColumns();
             for (int i = 0; i < visibleColumns2.length; i++) {
@@ -704,4 +706,24 @@ public class TreeTable extends Table implements Hierarchical {
         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;
+        requestRepaint();
+    }
+
 }
index 4ff42e7bc848a951dcda7b924c90bf1a4bb4974d..1f55cf538920956b536eed12143b01d5cefec4e4 100644 (file)
@@ -14,6 +14,7 @@ import com.vaadin.ui.AbstractSelect.MultiSelectMode;
 import com.vaadin.ui.Button;\r
 import com.vaadin.ui.Label;\r
 import com.vaadin.ui.Table;\r
+import com.vaadin.ui.Table.CellStyleGenerator;\r
 import com.vaadin.ui.Table.ColumnGenerator;\r
 import com.vaadin.ui.Table.ColumnResizeEvent;\r
 import com.vaadin.ui.Table.ColumnResizeListener;\r
@@ -269,6 +270,44 @@ public class Tables<T extends Table> extends AbstractSelectTestCase<T>
 \r
         }\r
     };\r
+\r
+    private class CellStyleInfo {\r
+        private final String styleName;\r
+        private final Object itemId;\r
+        private final Object propertyId;\r
+\r
+        public CellStyleInfo(String styleName, Object itemId, Object propertyId) {\r
+            this.styleName = styleName;\r
+            this.itemId = itemId;\r
+            this.propertyId = propertyId;\r
+        }\r
+\r
+        public boolean appliesTo(Object itemId, Object propertyId) {\r
+            return (this.itemId != null && this.itemId.equals(itemId))\r
+                    && (this.propertyId == propertyId || (this.propertyId != null && this.propertyId\r
+                            .equals(propertyId)));\r
+        }\r
+    }\r
+\r
+    private Command<T, CellStyleInfo> cellStyleCommand = new Command<T, CellStyleInfo>() {\r
+\r
+        public void execute(T c, final CellStyleInfo cellStyleInfo, Object data) {\r
+            if (cellStyleInfo == null) {\r
+                c.setCellStyleGenerator(null);\r
+            } else {\r
+                c.setCellStyleGenerator(new CellStyleGenerator() {\r
+\r
+                    public String getStyle(Object itemId, Object propertyId) {\r
+                        if (cellStyleInfo.appliesTo(itemId, propertyId)) {\r
+                            return cellStyleInfo.styleName;\r
+                        }\r
+                        return null;\r
+                    }\r
+                });\r
+            }\r
+        }\r
+    };\r
+\r
     private Command<T, Boolean> setSortEnabledCommand = new Command<T, Boolean>() {\r
 \r
         public void execute(T c, Boolean value, Object data) {\r
@@ -308,6 +347,7 @@ public class Tables<T extends Table> extends AbstractSelectTestCase<T>
 \r
         createColumnHeaderMode(CATEGORY_FEATURES);\r
         createAddGeneratedColumnAction(CATEGORY_FEATURES);\r
+        createCellStyleAction(CATEGORY_FEATURES);\r
 \r
         createBooleanAction("Sort enabled", CATEGORY_FEATURES, true,\r
                 setSortEnabledCommand);\r
@@ -350,6 +390,17 @@ public class Tables<T extends Table> extends AbstractSelectTestCase<T>
                         "", false));\r
     }\r
 \r
+    private void createCellStyleAction(String categoryFeatures) {\r
+        LinkedHashMap<String, CellStyleInfo> options = new LinkedHashMap<String, CellStyleInfo>();\r
+        options.put("None", null);\r
+        options.put("Red row", new CellStyleInfo(\r
+                "tables-test-cell-style-red-row", "Item 2", null));\r
+        options.put("Red cell", new CellStyleInfo(\r
+                "tables-test-cell-style-red-row", "Item 2", "Property 2"));\r
+        createSelectAction("Cell style generator", categoryFeatures, options,\r
+                "None", cellStyleCommand, true);\r
+    }\r
+\r
     private void createColumnHeaderMode(String category) {\r
         LinkedHashMap<String, Integer> columnHeaderModeOptions = new LinkedHashMap<String, Integer>();\r
         columnHeaderModeOptions.put("Hidden", Table.COLUMN_HEADER_MODE_HIDDEN);\r
@@ -600,7 +651,6 @@ public class Tables<T extends Table> extends AbstractSelectTestCase<T>
 \r
     // TODO:\r
     // setCurrentPageFirstItemIndex()\r
-    // Cell style generator\r
     // Editable\r
     // Cache rate\r
     // CurrentPageFirstItemId\r
diff --git a/tests/src/com/vaadin/tests/components/treetable/ExpandAnimationsInChameleon.java b/tests/src/com/vaadin/tests/components/treetable/ExpandAnimationsInChameleon.java
new file mode 100644 (file)
index 0000000..ca1c7ae
--- /dev/null
@@ -0,0 +1,126 @@
+package com.vaadin.tests.components.treetable;
+
+import com.vaadin.data.Container.Hierarchical;
+import com.vaadin.terminal.ThemeResource;
+import com.vaadin.tests.components.TestBase;
+import com.vaadin.ui.AbstractComponent;
+import com.vaadin.ui.Alignment;
+import com.vaadin.ui.Component;
+import com.vaadin.ui.GridLayout;
+import com.vaadin.ui.Layout;
+import com.vaadin.ui.Table;
+import com.vaadin.ui.TreeTable;
+
+public class ExpandAnimationsInChameleon extends TestBase {
+
+    @Override
+    protected void setup() {
+        Layout grid = getGridLayout();
+
+        TreeTable t = getTreeTable(null);
+        grid.addComponent(t);
+
+        t = getTreeTable("small");
+        grid.addComponent(t);
+
+        t = getTreeTable("big");
+        grid.addComponent(t);
+
+        t = getTreeTable("striped");
+        grid.addComponent(t);
+
+        t = getTreeTable("small striped");
+        grid.addComponent(t);
+
+        t = getTreeTable("big striped");
+        grid.addComponent(t);
+
+        t = getTreeTable("strong");
+        grid.addComponent(t);
+
+        t = getTreeTable("small strong");
+        grid.addComponent(t);
+
+        t = getTreeTable("big strong");
+        grid.addComponent(t);
+
+        t = getTreeTable("borderless");
+        grid.addComponent(t);
+
+        t = getTreeTable("striped");
+        t.setColumnHeaderMode(Table.COLUMN_HEADER_MODE_HIDDEN);
+        t.setCaption(t.getCaption() + ", hidden headers");
+        grid.addComponent(t);
+
+        addComponent(grid);
+    }
+
+    GridLayout getGridLayout() {
+        GridLayout grid = new GridLayout(3, 1) {
+            @Override
+            public void addComponent(Component c) {
+                super.addComponent(c);
+                setComponentAlignment(c, Alignment.MIDDLE_CENTER);
+                if (c.getStyleName() != "") {
+                    ((AbstractComponent) c).setDescription(c.getClass()
+                            .getSimpleName()
+                            + ".addStyleName(\""
+                            + c.getStyleName() + "\")");
+                } else {
+                    ((AbstractComponent) c).setDescription("new "
+                            + c.getClass().getSimpleName() + "()");
+                }
+            }
+        };
+        grid.setWidth("100%");
+        grid.setSpacing(true);
+        grid.setMargin(true);
+        grid.setStyleName("preview-grid");
+        return grid;
+    }
+
+    public TreeTable getTreeTable(String style) {
+        TreeTable t = new TreeTable();
+        t.setAnimationsEnabled(true);
+        t.setWidth("250px");
+        t.setPageLength(5);
+        t.setSelectable(true);
+        t.setColumnCollapsingAllowed(true);
+        t.setColumnReorderingAllowed(true);
+
+        if (style != null) {
+            t.setStyleName(style);
+            t.setCaption("Table.addStyleName(\"" + style + "\")");
+        }
+
+        t.addContainerProperty("First", String.class, null);
+        t.addContainerProperty("Second", String.class, null);
+        t.addContainerProperty("Third", String.class, null);
+
+        for (int j = 1; j < 100; j++) {
+            t.addItem(new Object[] { "Foo " + j, "Bar " + j, "Lorem " + j }, j);
+        }
+        Hierarchical hc = t.getContainerDataSource();
+        hc.setChildrenAllowed(2, true);
+        for (int j = 4; j < 100; j++) {
+            hc.setParent(j, 2);
+        }
+
+        t.setColumnIcon("Third", new ThemeResource(
+                "../runo/icons/16/document.png"));
+        t.select(1);
+
+        return t;
+    }
+
+    @Override
+    protected String getDescription() {
+        return "Colors should be correct while animating expands/collapses";
+    }
+
+    @Override
+    protected Integer getTicketNumber() {
+        return 6723;
+    }
+
+}
diff --git a/tests/src/com/vaadin/tests/components/treetable/TreeTableExpandWithRowStyle b/tests/src/com/vaadin/tests/components/treetable/TreeTableExpandWithRowStyle
new file mode 100644 (file)
index 0000000..70e9990
--- /dev/null
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="" />
+<title>New Test</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr><td rowspan="1" colspan="3">New Test</td></tr>
+</thead><tbody>
+<tr>
+    <td>open</td>
+    <td>/run/com.vaadin.tests.components.treetable.TreeTableTest?restartApplication</td>
+    <td></td>
+</tr>
+<tr>
+    <td>mouseClick</td>
+    <td>vaadin=runcomvaadintestscomponentstreetableTreeTableTest::PID_Smenu#item0</td>
+    <td>27,4</td>
+</tr>
+<tr>
+    <td>mouseClick</td>
+    <td>vaadin=runcomvaadintestscomponentstreetableTreeTableTest::Root/VOverlay[0]/VMenuBar[0]#item8</td>
+    <td>49,10</td>
+</tr>
+<tr>
+    <td>mouseClick</td>
+    <td>vaadin=runcomvaadintestscomponentstreetableTreeTableTest::Root/VOverlay[1]/VMenuBar[0]#item5</td>
+    <td>104,7</td>
+</tr>
+<tr>
+    <td>mouseClick</td>
+    <td>vaadin=runcomvaadintestscomponentstreetableTreeTableTest::Root/VOverlay[2]/VMenuBar[0]#item1</td>
+    <td>40,10</td>
+</tr>
+<tr>
+    <td>screenCapture</td>
+    <td></td>
+    <td>redrow</td>
+</tr>
+<tr>
+    <td>mouseClick</td>
+    <td>vaadin=runcomvaadintestscomponentstreetableTreeTableTest::PID_StestComponent/domChild[1]/domChild[0]/domChild[1]/domChild[0]/domChild[1]/domChild[0]/domChild[0]/domChild[0]</td>
+    <td>10,4</td>
+</tr>
+<tr>
+    <td>pause</td>
+    <td></td>
+    <td>200</td>
+</tr>
+<tr>
+    <td>screenCapture</td>
+    <td></td>
+    <td>redrowexpanded</td>
+</tr>
+
+</tbody></table>
+</body>
+</html>
index 7c63f7ba4b103fbde80f30286072f128b2e6348b..010c735047afd37c8ec3f652af27a319ba517e42 100644 (file)
@@ -98,6 +98,9 @@ public class TreeTableTest extends Tables<TreeTable> implements
         createListeners(CATEGORY_LISTENERS);\r
         // createItemStyleGenerator(CATEGORY_FEATURES);\r
 \r
+        createBooleanAction("Animate collapse/expand", CATEGORY_STATE, false,\r
+                animationCommand);\r
+\r
         // TODO: DropHandler\r
         // TODO: DragMode\r
         // TODO: ActionHandler\r
@@ -280,6 +283,13 @@ public class TreeTableTest extends Tables<TreeTable> implements
         }\r
     };\r
 \r
+    protected Command<TreeTable, Boolean> animationCommand = new Command<TreeTable, Boolean>() {\r
+\r
+        public void execute(TreeTable c, Boolean enabled, Object data) {\r
+            c.setAnimationsEnabled(enabled);\r
+        }\r
+    };\r
+\r
     public void nodeCollapse(CollapseEvent event) {\r
         log(event.getClass().getSimpleName() + ": " + event.getItemId());\r
     }\r