]> source.dussan.org Git - vaadin-framework.git/commitdiff
Adds theme to details in Grid (#16644)
authorJouni Koivuviita <jouni@vaadin.com>
Fri, 27 Mar 2015 13:09:41 +0000 (15:09 +0200)
committerHenrik Paul <henrik@vaadin.com>
Wed, 1 Apr 2015 07:50:18 +0000 (07:50 +0000)
Change-Id: I84628ee5840b71f2ff889037a525d43f9e86af46

WebContent/VAADIN/themes/base/grid/grid.scss
WebContent/VAADIN/themes/valo/components/_grid.scss
client/src/com/vaadin/client/widgets/Escalator.java
client/src/com/vaadin/client/widgets/Grid.java
uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java
uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridDetailsClientTest.java
uitest/src/com/vaadin/tests/components/grid/basicfeatures/element/CustomGridElement.java

index ccb7043c502cc31d0c1235b402d43824661a2e11..7dc877dca50dd08e5d2333b4eca1541cbe5433e1 100644 (file)
@@ -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:
+    // <svg xmlns="http://www.w3.org/2000/svg"></svg>
+    .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
index 0d6d2ff0a6401f10fd1d98df0bcd5d3935328b54..0adfdd27abfeb76d421384a0b1d5490e0a8a0193 100644 (file)
@@ -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 {
index 88ed9295e49bc4673d1c8b6a1bb31559e7dd6e67..0ce5aff74e11d539315984b97f29a147b792c101 100644 (file)
@@ -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());
 
index 73dcccfd1e8164d2b6a955d32041d59f594fb888..ed7c0d180094fb867fbfae9bd5effea411511563 100644 (file)
@@ -2843,15 +2843,21 @@ public class Grid<T> 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<Element, Widget> elementToWidgetMap = new HashMap<Element, Widget>();
 
         @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<T> 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<T> 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<T> 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<T> 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<T> 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<T> 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")
index 6f4c7df38c6244bd6cbff328925a7ba89e23b309..94620f34bd2a186698c5e690c3ec6657d5dfe956 100644 (file)
@@ -1301,6 +1301,9 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> {
         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));
     }
index 5ab0c238e5be3ba1188356214bd81d7997ea91bf..619033226cd2f11f342e0f631771d3204b4058f9 100644 (file)
@@ -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<WebElement> 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);
index dc9b8722f2e4c71ccb61506bbce36c3e5bbf63a3..e1934d4f2b74dcbb9d15503c4307f70557a644c8 100644 (file)
@@ -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. <code>null</code>
+     *         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 + "]");
     }