From 0ca0240a30a9701cdf9fb071594a9e24ee810ad9 Mon Sep 17 00:00:00 2001 From: Jouni Koivuviita Date: Fri, 27 Mar 2015 15:09:41 +0200 Subject: [PATCH] Adds theme to details in Grid (#16644) Change-Id: I84628ee5840b71f2ff889037a525d43f9e86af46 --- WebContent/VAADIN/themes/base/grid/grid.scss | 48 +++- .../VAADIN/themes/valo/components/_grid.scss | 13 +- .../com/vaadin/client/widgets/Escalator.java | 1 - .../src/com/vaadin/client/widgets/Grid.java | 205 ++++++++++++++---- .../grid/basicfeatures/GridBasicFeatures.java | 3 + .../client/GridDetailsClientTest.java | 18 +- .../element/CustomGridElement.java | 10 +- 7 files changed, 250 insertions(+), 48 deletions(-) diff --git a/WebContent/VAADIN/themes/base/grid/grid.scss b/WebContent/VAADIN/themes/base/grid/grid.scss index ccb7043c50..7dc877dca5 100644 --- a/WebContent/VAADIN/themes/base/grid/grid.scss +++ b/WebContent/VAADIN/themes/base/grid/grid.scss @@ -24,6 +24,12 @@ $v-grid-cell-padding-horizontal: 5px !default; $v-grid-editor-background-color: $v-grid-row-background-color !default; +$v-grid-details-marker-width: 2px !default; +$v-grid-details-marker-color: $v-grid-row-selected-background-color !default; +$v-grid-details-border-top: $v-grid-cell-horizontal-border !default; +$v-grid-details-border-top-stripe: $v-grid-cell-horizontal-border !default; +$v-grid-details-border-bottom: 1px solid darken($v-grid-row-stripe-background-color, 10%) !default; +$v-grid-details-border-bottom-stripe: 1px solid darken($v-grid-row-background-color, 10%) !default; @import "../escalator/escalator"; @@ -392,9 +398,45 @@ $v-grid-editor-background-color: $v-grid-row-background-color !default; .#{$primaryStyleName}-editor-save { margin-right: 4px; } - - .#{$primaryStyleName}-spacer { - border: $v-grid-border; + + .#{$primaryStyleName}-spacer > td { + display: block; + padding: 0; + + background-color: $v-grid-row-background-color; + border-top: $v-grid-details-border-top; + border-bottom: $v-grid-details-border-bottom; + } + + .#{$primaryStyleName}-spacer.stripe > td { + background-color: $v-grid-row-stripe-background-color; + border-top: $v-grid-details-border-top-stripe; + border-bottom: $v-grid-details-border-bottom-stripe; + } + + .#{$primaryStyleName}-spacer .deco { + top: 0; // this will be overridden by code, but it's a good default. + left: 0; + width: $v-grid-details-marker-width; + background-color: $v-grid-details-marker-color; + position: absolute; + height: 100%; // this will be overridden by code, but it's a good default. + pointer-events: none; + + // IE 8-10 apply "pointer-events" only to SVG elements. + // Using an empty SVG instead of an empty text node makes IE + // obey the "pointer-events: none" and forwards click events + // to the underlying element. The data decodes to: + // + .ie8 &:before, + .ie9 &:before, + .ie10 &:before { + content: url(data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjwvc3ZnPg==); + } + } + + .#{$primaryStyleName}-spacer .content { + padding-left: $v-grid-details-marker-width; } // Renderers diff --git a/WebContent/VAADIN/themes/valo/components/_grid.scss b/WebContent/VAADIN/themes/valo/components/_grid.scss index 0d6d2ff0a6..0adfdd27ab 100644 --- a/WebContent/VAADIN/themes/valo/components/_grid.scss +++ b/WebContent/VAADIN/themes/valo/components/_grid.scss @@ -3,7 +3,8 @@ $v-grid-row-background-color: valo-table-background-color() !default; $v-grid-row-stripe-background-color: scale-color($v-grid-row-background-color, $lightness: if(color-luminance($v-grid-row-background-color) < 10, 4%, -4%)) !default; -$v-grid-border: flatten-list(valo-border($color: $v-grid-row-background-color, $strength: 0.8)) !default; +$v-grid-border-color-source: $v-grid-row-background-color !default; +$v-grid-border: flatten-list(valo-border($color: $v-grid-border-color-source, $strength: 0.8)) !default; $v-grid-cell-focused-border: max(2px, first-number($v-border)) solid $v-selection-color !default; $v-grid-row-height: $v-table-row-height !default; @@ -16,6 +17,12 @@ $v-grid-cell-padding-horizontal: $v-table-cell-padding-horizontal !default; $v-grid-animations-enabled: $v-animations-enabled !default; +$v-grid-details-marker-width: first-number($v-grid-border) * 2 !default; +$v-grid-details-marker-color: $v-selection-color !default; +$v-grid-details-border-top: valo-border($color: $v-grid-border-color-source, $strength: 0.3) !default; +$v-grid-details-border-top-stripe: valo-border($color: $v-grid-row-stripe-background-color, $strength: 0.3) !default; +$v-grid-details-border-bottom: $v-grid-cell-horizontal-border !default; +$v-grid-details-border-bottom-stripe: $v-grid-cell-horizontal-border !default; @import "../../base/grid/grid"; @@ -177,6 +184,10 @@ $v-grid-animations-enabled: $v-animations-enabled !default; outline: none; } + .#{$primary-stylename}-spacer { + margin-top: first-number($v-grid-border) * -1; + } + // Customize scrollbars .#{$primary-stylename}-scroller { &::-webkit-scrollbar { diff --git a/client/src/com/vaadin/client/widgets/Escalator.java b/client/src/com/vaadin/client/widgets/Escalator.java index 88ed9295e4..0ce5aff74e 100644 --- a/client/src/com/vaadin/client/widgets/Escalator.java +++ b/client/src/com/vaadin/client/widgets/Escalator.java @@ -4597,7 +4597,6 @@ public class Escalator extends Widget implements RequiresResize, getRootElement().getStyle().setWidth(getInnerWidth(), Unit.PX); setHeight(height); - spacerElement.getStyle().setWidth(100, Unit.PCT); spacerElement.setColSpan(getColumnConfiguration() .getColumnCount()); diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index 73dcccfd1e..ed7c0d1800 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -2843,15 +2843,21 @@ public class Grid extends ResizeComposite implements private class GridSpacerUpdater implements SpacerUpdater { + private static final String DECO_CLASSNAME = "deco"; + private static final String CONTENT_CLASSNAME = "content"; + private static final String STRIPE_CLASSNAME = "stripe"; + private final Map elementToWidgetMap = new HashMap(); @Override public void init(Spacer spacer) { + initStructure(spacer); + Element root = getDetailsRoot(spacer); - assert spacer.getElement().getFirstChild() == null : "The spacer's" + assert root.getFirstChild() == null : "The spacer's" + " element should be empty at this point. (row: " - + spacer.getRow() + ", child: " - + spacer.getElement().getFirstChild() + ")"; + + spacer.getRow() + ", child: " + root.getFirstChild() + + ")"; int rowIndex = spacer.getRow(); @@ -2865,43 +2871,45 @@ public class Grid extends ResizeComposite implements + rowIndex, e); } + final double spacerHeight; if (detailsWidget == null) { - spacer.getElement().removeAllChildren(); - escalator.getBody().setSpacer(rowIndex, - DETAILS_ROW_INITIAL_HEIGHT); - return; - } + root.removeAllChildren(); + spacerHeight = DETAILS_ROW_INITIAL_HEIGHT; + } else { + Element element = detailsWidget.getElement(); + root.appendChild(element); + setParent(detailsWidget, Grid.this); + Widget previousWidget = elementToWidgetMap.put(element, + detailsWidget); - Element element = detailsWidget.getElement(); - spacer.getElement().appendChild(element); - setParent(detailsWidget, Grid.this); - Widget previousWidget = elementToWidgetMap.put(element, - detailsWidget); + assert previousWidget == null : "Overwrote a pre-existing widget on row " + + rowIndex + " without proper removal first."; - assert previousWidget == null : "Overwrote a pre-existing widget on row " - + rowIndex + " without proper removal first."; + /* + * Once we have the content properly inside the DOM, we should + * re-measure it to make sure that it's the correct height. + */ + double measuredHeight = WidgetUtil + .getRequiredHeightBoundingClientRectDouble(root); + assert getElement().isOrHasChild(root) : "The spacer element wasn't in the DOM during measurement, but was assumed to be."; + spacerHeight = measuredHeight; + } - /* - * Once we have the content properly inside the DOM, we should - * re-measure it to make sure that it's the correct height. - */ - double measuredHeight = WidgetUtil - .getRequiredHeightBoundingClientRectDouble(spacer - .getElement()); - assert getElement().isOrHasChild(spacer.getElement()) : "The spacer element wasn't in the DOM during measurement, but was assumed to be."; - escalator.getBody().setSpacer(rowIndex, measuredHeight); + escalator.getBody().setSpacer(rowIndex, spacerHeight); + updateDecoratorGeometry(spacerHeight, spacer); } @Override public void destroy(Spacer spacer) { + Element root = getDetailsRoot(spacer); - assert getElement().isOrHasChild(spacer.getElement()) : "Trying " + assert getElement().isOrHasChild(root) : "Trying " + "to destroy a spacer that is not connected to this " + "Grid's DOM. (row: " + spacer.getRow() + ", element: " - + spacer.getElement() + ")"; + + root + ")"; - Widget detailsWidget = elementToWidgetMap.remove(spacer - .getElement().getFirstChildElement()); + Widget detailsWidget = elementToWidgetMap.remove(root + .getFirstChildElement()); if (detailsWidget != null) { /* @@ -2909,16 +2917,107 @@ public class Grid extends ResizeComposite implements * returned a null widget. */ - assert spacer.getElement().getFirstChild() != null : "The " + assert root.getFirstChild() != null : "The " + "details row to destroy did not contain a widget - " + "probably removed by something else without " + "permission? (row: " + spacer.getRow() - + ", element: " + spacer.getElement() + ")"; + + ", element: " + root + ")"; setParent(detailsWidget, null); - spacer.getElement().removeAllChildren(); + root.removeAllChildren(); + } + } + + /** + * Initializes the spacer element into a details structure, containing a + * decorator and a slot for the details widget. + */ + private void initStructure(Spacer spacer) { + Element spacerRoot = spacer.getElement(); + + if (spacerRoot.getChildCount() == 0) { + Element deco = DOM.createDiv(); + deco.setClassName(DECO_CLASSNAME); + + Element detailsContent = DOM.createDiv(); + detailsContent.setClassName(CONTENT_CLASSNAME); + + if (spacer.getRow() % 2 == 1) { + spacerRoot.getParentElement() + .addClassName(STRIPE_CLASSNAME); + } + + spacerRoot.appendChild(deco); + spacerRoot.appendChild(detailsContent); + } + + else { + if (spacer.getRow() % 2 == 1) { + spacerRoot.getParentElement() + .addClassName(STRIPE_CLASSNAME); + } else { + spacerRoot.getParentElement().removeClassName( + STRIPE_CLASSNAME); + } + + /* + * The only case when we get into this else branch is when the + * previous generated details element was a null Widget. In + * those situations, we don't call destroy on the content, but + * simply reuse it as-is. + */ + assert getDetailsRoot(spacer).getChildCount() == 0 : "This " + + "code should never be triggered unless the details " + + "root already was empty"; } } + + /** Gets the decorator element from the DOM structure. */ + private Element getDecorator(Spacer spacer) { + TableCellElement td = TableCellElement.as(spacer.getElement()); + Element decorator = td.getFirstChildElement(); + return decorator; + } + + /** Gets the element for the details widget from the DOM structure. */ + private Element getDetailsRoot(Spacer spacer) { + Element detailsRoot = getDecorator(spacer).getNextSiblingElement(); + return detailsRoot; + } + + /** Resizes and places the decorator. */ + private void updateDecoratorGeometry(double detailsHeight, Spacer spacer) { + Element decorator = getDecorator(spacer); + Style style = decorator.getStyle(); + double rowHeight = escalator.getBody().getDefaultRowHeight(); + double borderHeight = getBorderHeight(spacer); + + style.setTop(-(rowHeight - borderHeight), Unit.PX); + style.setHeight(detailsHeight + rowHeight, Unit.PX); + } + + private native double getBorderHeight(Spacer spacer) + /*-{ + var spacerCell = spacer.@com.vaadin.client.widget.escalator.Spacer::getElement()(); + if (typeof $wnd.getComputedStyle === 'function') { + var computedStyle = $wnd.getComputedStyle(spacerCell); + var borderTopWidth = computedStyle['borderTopWidth']; + var width = parseFloat(borderTopWidth); + return width; + } else { + var spacerRow = spacerCell.offsetParent; + var cloneCell = spacerCell.cloneNode(false); + spacerRow.appendChild(cloneCell); + cloneCell.style.height = "10px"; // IE8 wants the height to be set to something... + var heightWithBorder = cloneCell.offsetHeight; + cloneCell.style.borderTopWidth = "0"; + var heightWithoutBorder = cloneCell.offsetHeight; + spacerRow.removeChild(cloneCell); + + console.log(heightWithBorder+" - "+heightWithoutBorder); + return heightWithBorder - heightWithoutBorder; + } + }-*/; } /** @@ -4829,6 +4928,8 @@ public class Grid extends ResizeComposite implements setSelectionMode(SelectionMode.SINGLE); + escalator.getBody().setSpacerUpdater(gridSpacerUpdater); + escalator.addScrollHandler(new ScrollHandler() { @Override public void onScroll(ScrollEvent event) { @@ -6053,10 +6154,12 @@ public class Grid extends ResizeComposite implements private boolean isOrContainsInSpacer(Node node) { Node n = node; while (n != null && n != getElement()) { - if (Element.is(n) - && Element.as(n).getClassName() - .equals(getStylePrimaryName() + "-spacer")) { - return true; + boolean isElement = Element.is(n); + if (isElement) { + String className = Element.as(n).getClassName(); + if (className.contains(getStylePrimaryName() + "-spacer")) { + return true; + } } n = n.getParentNode(); } @@ -6318,10 +6421,19 @@ public class Grid extends ResizeComposite implements @SuppressWarnings("deprecation") public com.google.gwt.user.client.Element getSubPartElement(String subPart) { - Element subPartElement = escalator.getSubPartElement(subPart + /* + * gandles details[] (translated to spacer[] for Escalator), cell[], + * header[] and footer[] + */ + Element escalatorElement = escalator.getSubPartElement(subPart .replaceFirst("^details\\[", "spacer[")); - if (subPartElement != null) { - return DOM.asOld(subPartElement); + + if (escalatorElement != null) { + if (subPart.startsWith("details[")) { + return DOM.asOld(parseDetails(escalatorElement)); + } else { + return DOM.asOld(escalatorElement); + } } SubPartArguments args = Escalator.parseSubPartArguments(subPart); @@ -6334,6 +6446,23 @@ public class Grid extends ResizeComposite implements return null; } + @SuppressWarnings("static-method") + private Element parseDetails(Element spacer) { + assert spacer.getChildCount() == 2 : "Unexpected structure for details "; + + Element decorator = spacer.getFirstChildElement(); + assert decorator != null : "unexpected spacer DOM structure"; + assert decorator.getClassName() + .equals(GridSpacerUpdater.DECO_CLASSNAME) : "unexpected first details element"; + + Element spacerRoot = decorator.getNextSiblingElement(); + assert spacerRoot != null : "unexpected spacer DOM structure"; + assert spacerRoot.getClassName().equals( + GridSpacerUpdater.CONTENT_CLASSNAME) : "unexpected second details element"; + + return DOM.asOld(spacerRoot); + } + private Element getSubPartElementEditor(SubPartArguments args) { if (!args.getType().equalsIgnoreCase("editor") diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java index 6f4c7df38c..94620f34bd 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java @@ -1301,6 +1301,9 @@ public class GridBasicFeatures extends AbstractComponentTest { createBooleanAction("Open firstItemId", "Details", false, openOrCloseItemId, ds.firstItemId()); + createBooleanAction("Open 1", "Details", false, openOrCloseItemId, + ds.getIdByIndex(1)); + createBooleanAction("Open 995", "Details", false, openOrCloseItemId, ds.getIdByIndex(995)); } diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridDetailsClientTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridDetailsClientTest.java index 5ab0c238e5..619033226c 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridDetailsClientTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridDetailsClientTest.java @@ -23,6 +23,8 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.util.List; + import org.junit.Before; import org.junit.Test; import org.openqa.selenium.NoSuchElementException; @@ -33,12 +35,9 @@ import com.vaadin.shared.ui.grid.ScrollDestination; import com.vaadin.testbench.By; import com.vaadin.testbench.ElementQuery; import com.vaadin.testbench.TestBenchElement; -import com.vaadin.testbench.annotations.RunLocally; import com.vaadin.testbench.elements.NotificationElement; -import com.vaadin.testbench.parallel.Browser; import com.vaadin.tests.components.grid.basicfeatures.GridBasicClientFeaturesTest; -@RunLocally(Browser.PHANTOMJS) public class GridDetailsClientTest extends GridBasicClientFeaturesTest { private static final String[] SET_GENERATOR = new String[] { "Component", @@ -183,6 +182,19 @@ public class GridDetailsClientTest extends GridBasicClientFeaturesTest { getGridElement().getDetails(1); } + + @Test + public void rowElementClassNames() { + toggleDetailsFor(0); + toggleDetailsFor(1); + + List elements = getGridElement().findElements( + By.className("v-grid-spacer")); + assertEquals("v-grid-spacer", elements.get(0).getAttribute("class")); + assertEquals("v-grid-spacer stripe", + elements.get(1).getAttribute("class")); + } + @Test public void scrollDownToRowWithDetails() { toggleDetailsFor(100); diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/element/CustomGridElement.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/element/CustomGridElement.java index dc9b8722f2..e1934d4f2b 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/element/CustomGridElement.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/element/CustomGridElement.java @@ -15,6 +15,8 @@ */ package com.vaadin.tests.components.grid.basicfeatures.element; +import org.openqa.selenium.NoSuchElementException; + import com.vaadin.testbench.By; import com.vaadin.testbench.TestBenchElement; import com.vaadin.testbench.elements.GridElement; @@ -28,9 +30,13 @@ public class CustomGridElement extends GridElement { * @since * @param rowIndex * the index of the row for the details - * @return the element that contains the details of a row + * @return the element that contains the details of a row. null + * if no widget is defined for the detials row + * @throws NoSuchElementException + * if the given details row is currently not open */ - public TestBenchElement getDetails(int rowIndex) { + public TestBenchElement getDetails(int rowIndex) + throws NoSuchElementException { return getSubPart("#details[" + rowIndex + "]"); } -- 2.39.5