From 63411e8a5478aef8316cd270f045d82c83caee68 Mon Sep 17 00:00:00 2001 From: Jonatan Kronqvist Date: Wed, 17 Aug 2011 10:20:39 +0000 Subject: [PATCH] Optional expand and collapse animations for TreeTable (#6723) svn changeset:20445/svn branch:6.7 --- WebContent/VAADIN/themes/base/table/table.css | 1 + .../themes/base/treetable/treetable.css | 40 ++- .../VAADIN/themes/tests-components/styles.css | 5 + .../terminal/gwt/client/ComputedStyle.java | 178 +++++++++ .../terminal/gwt/client/ui/VScrollTable.java | 43 ++- .../terminal/gwt/client/ui/VTreeTable.java | 338 ++++++++++++++++++ src/com/vaadin/ui/Table.java | 77 ++-- src/com/vaadin/ui/TreeTable.java | 22 ++ .../vaadin/tests/components/table/Tables.java | 52 ++- .../ExpandAnimationsInChameleon.java | 126 +++++++ .../treetable/TreeTableExpandWithRowStyle | 62 ++++ .../components/treetable/TreeTableTest.java | 10 + 12 files changed, 908 insertions(+), 46 deletions(-) create mode 100644 src/com/vaadin/terminal/gwt/client/ComputedStyle.java create mode 100644 tests/src/com/vaadin/tests/components/treetable/ExpandAnimationsInChameleon.java create mode 100644 tests/src/com/vaadin/tests/components/treetable/TreeTableExpandWithRowStyle diff --git a/WebContent/VAADIN/themes/base/table/table.css b/WebContent/VAADIN/themes/base/table/table.css index 425f8243d6..c618c07587 100644 --- a/WebContent/VAADIN/themes/base/table/table.css +++ b/WebContent/VAADIN/themes/base/table/table.css @@ -136,6 +136,7 @@ } .v-table-row, .v-table-row-odd { + background: #fff; border: 0; margin: 0; padding: 0; diff --git a/WebContent/VAADIN/themes/base/treetable/treetable.css b/WebContent/VAADIN/themes/base/treetable/treetable.css index ee5aaed861..eec6c794d3 100644 --- a/WebContent/VAADIN/themes/base/treetable/treetable.css +++ b/WebContent/VAADIN/themes/base/treetable/treetable.css @@ -17,4 +17,42 @@ .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; +} diff --git a/WebContent/VAADIN/themes/tests-components/styles.css b/WebContent/VAADIN/themes/tests-components/styles.css index 3e59aaec7a..da173964f4 100644 --- a/WebContent/VAADIN/themes/tests-components/styles.css +++ b/WebContent/VAADIN/themes/tests-components/styles.css @@ -32,3 +32,8 @@ .border-blue-2px { border: 2px solid blue; } + +.v-table-row-tables-test-cell-style-red-row, +.v-table-cell-content-tables-test-cell-style-red-row { + background: #f00; +} \ 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 index 0000000000..5aac2caf47 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ComputedStyle.java @@ -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. + *

+ * 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 + * null. + */ + 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); + }-*/; + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java b/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java index ad69d8a751..fb51919b10 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java @@ -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 insertRows(UIDL rowData, + int firstIndex, int rows) { aligns = tHead.getColumnAlignments(); final Iterator it = rowData.getChildIterator(); + List insertedRows = new ArrayList(); 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, diff --git a/src/com/vaadin/terminal/gwt/client/ui/VTreeTable.java b/src/com/vaadin/terminal/gwt/client/ui/VTreeTable.java index 15ebcde35e..a3b7008e77 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VTreeTable.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VTreeTable.java @@ -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 rowsToDelete = new ArrayList(); + 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 insertRowsAnimated(UIDL rowData, + int firstIndex, int rows) { + List 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 rows; + private Element cloneDiv; + private Element cloneTable; + private AnimationPreparator preparator; + + public RowExpandAnimation(List 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 rows; + + public RowCollapseAnimation(List 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); + } + } } /** diff --git a/src/com/vaadin/ui/Table.java b/src/com/vaadin/ui/Table.java index fd18bf0fd7..5f0fd07277 100644 --- a/src/com/vaadin/ui/Table.java +++ b/src/com/vaadin/ui/Table.java @@ -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; } } diff --git a/src/com/vaadin/ui/TreeTable.java b/src/com/vaadin/ui/TreeTable.java index 987e3a9bfd..f64c818313 100644 --- a/src/com/vaadin/ui/TreeTable.java +++ b/src/com/vaadin/ui/TreeTable.java @@ -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(); + } + } diff --git a/tests/src/com/vaadin/tests/components/table/Tables.java b/tests/src/com/vaadin/tests/components/table/Tables.java index 4ff42e7bc8..1f55cf5389 100644 --- a/tests/src/com/vaadin/tests/components/table/Tables.java +++ b/tests/src/com/vaadin/tests/components/table/Tables.java @@ -14,6 +14,7 @@ import com.vaadin.ui.AbstractSelect.MultiSelectMode; import com.vaadin.ui.Button; import com.vaadin.ui.Label; import com.vaadin.ui.Table; +import com.vaadin.ui.Table.CellStyleGenerator; import com.vaadin.ui.Table.ColumnGenerator; import com.vaadin.ui.Table.ColumnResizeEvent; import com.vaadin.ui.Table.ColumnResizeListener; @@ -269,6 +270,44 @@ public class Tables extends AbstractSelectTestCase } }; + + private class CellStyleInfo { + private final String styleName; + private final Object itemId; + private final Object propertyId; + + public CellStyleInfo(String styleName, Object itemId, Object propertyId) { + this.styleName = styleName; + this.itemId = itemId; + this.propertyId = propertyId; + } + + public boolean appliesTo(Object itemId, Object propertyId) { + return (this.itemId != null && this.itemId.equals(itemId)) + && (this.propertyId == propertyId || (this.propertyId != null && this.propertyId + .equals(propertyId))); + } + } + + private Command cellStyleCommand = new Command() { + + public void execute(T c, final CellStyleInfo cellStyleInfo, Object data) { + if (cellStyleInfo == null) { + c.setCellStyleGenerator(null); + } else { + c.setCellStyleGenerator(new CellStyleGenerator() { + + public String getStyle(Object itemId, Object propertyId) { + if (cellStyleInfo.appliesTo(itemId, propertyId)) { + return cellStyleInfo.styleName; + } + return null; + } + }); + } + } + }; + private Command setSortEnabledCommand = new Command() { public void execute(T c, Boolean value, Object data) { @@ -308,6 +347,7 @@ public class Tables extends AbstractSelectTestCase createColumnHeaderMode(CATEGORY_FEATURES); createAddGeneratedColumnAction(CATEGORY_FEATURES); + createCellStyleAction(CATEGORY_FEATURES); createBooleanAction("Sort enabled", CATEGORY_FEATURES, true, setSortEnabledCommand); @@ -350,6 +390,17 @@ public class Tables extends AbstractSelectTestCase "", false)); } + private void createCellStyleAction(String categoryFeatures) { + LinkedHashMap options = new LinkedHashMap(); + options.put("None", null); + options.put("Red row", new CellStyleInfo( + "tables-test-cell-style-red-row", "Item 2", null)); + options.put("Red cell", new CellStyleInfo( + "tables-test-cell-style-red-row", "Item 2", "Property 2")); + createSelectAction("Cell style generator", categoryFeatures, options, + "None", cellStyleCommand, true); + } + private void createColumnHeaderMode(String category) { LinkedHashMap columnHeaderModeOptions = new LinkedHashMap(); columnHeaderModeOptions.put("Hidden", Table.COLUMN_HEADER_MODE_HIDDEN); @@ -600,7 +651,6 @@ public class Tables extends AbstractSelectTestCase // TODO: // setCurrentPageFirstItemIndex() - // Cell style generator // Editable // Cache rate // CurrentPageFirstItemId 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 index 0000000000..ca1c7aeb9c --- /dev/null +++ b/tests/src/com/vaadin/tests/components/treetable/ExpandAnimationsInChameleon.java @@ -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 index 0000000000..70e9990d39 --- /dev/null +++ b/tests/src/com/vaadin/tests/components/treetable/TreeTableExpandWithRowStyle @@ -0,0 +1,62 @@ + + + + + + +New Test + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
New Test
open/run/com.vaadin.tests.components.treetable.TreeTableTest?restartApplication
mouseClickvaadin=runcomvaadintestscomponentstreetableTreeTableTest::PID_Smenu#item027,4
mouseClickvaadin=runcomvaadintestscomponentstreetableTreeTableTest::Root/VOverlay[0]/VMenuBar[0]#item849,10
mouseClickvaadin=runcomvaadintestscomponentstreetableTreeTableTest::Root/VOverlay[1]/VMenuBar[0]#item5104,7
mouseClickvaadin=runcomvaadintestscomponentstreetableTreeTableTest::Root/VOverlay[2]/VMenuBar[0]#item140,10
screenCaptureredrow
mouseClickvaadin=runcomvaadintestscomponentstreetableTreeTableTest::PID_StestComponent/domChild[1]/domChild[0]/domChild[1]/domChild[0]/domChild[1]/domChild[0]/domChild[0]/domChild[0]10,4
pause200
screenCaptureredrowexpanded
+ + diff --git a/tests/src/com/vaadin/tests/components/treetable/TreeTableTest.java b/tests/src/com/vaadin/tests/components/treetable/TreeTableTest.java index 7c63f7ba4b..010c735047 100644 --- a/tests/src/com/vaadin/tests/components/treetable/TreeTableTest.java +++ b/tests/src/com/vaadin/tests/components/treetable/TreeTableTest.java @@ -98,6 +98,9 @@ public class TreeTableTest extends Tables implements createListeners(CATEGORY_LISTENERS); // createItemStyleGenerator(CATEGORY_FEATURES); + createBooleanAction("Animate collapse/expand", CATEGORY_STATE, false, + animationCommand); + // TODO: DropHandler // TODO: DragMode // TODO: ActionHandler @@ -280,6 +283,13 @@ public class TreeTableTest extends Tables implements } }; + protected Command animationCommand = new Command() { + + public void execute(TreeTable c, Boolean enabled, Object data) { + c.setAnimationsEnabled(enabled); + } + }; + public void nodeCollapse(CollapseEvent event) { log(event.getClass().getSimpleName() + ": " + event.getItemId()); } -- 2.39.5