]> source.dussan.org Git - vaadin-framework.git/commitdiff
Grid editor theme redesign (#16593)
authorJouni Koivuviita <jouni@vaadin.com>
Thu, 5 Feb 2015 22:05:21 +0000 (00:05 +0200)
committerVaadin Code Review <review@vaadin.com>
Fri, 6 Feb 2015 07:43:44 +0000 (07:43 +0000)
Editor theme is now more flexible with regards to CSS. There are now
separate elements for containing the edited cells and the “footer”
which contains an optional message area and the save and cancel buttons.

Change-Id: I9addcb6adca792a9251ffada99fbe9b77502c77a

WebContent/VAADIN/themes/base/grid/grid.scss
WebContent/VAADIN/themes/reindeer/grid/grid.scss
WebContent/VAADIN/themes/runo/grid/grid.scss
WebContent/VAADIN/themes/valo/components/_grid.scss
WebContent/VAADIN/themes/valo/shared/_global.scss
client/src/com/vaadin/client/widgets/Grid.java
uitest/src/com/vaadin/testbench/elements/GridElement.java
uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridEditorClientTest.java

index ed068a5efcb6a66f0ea1f9c69283f1162e5b2a03..a79420f7a91ca62ab71b03e4607fb3b80c1ea3af 100644 (file)
@@ -85,7 +85,8 @@ $v-grid-editor-background-color: $v-grid-row-background-color !default;
 
   // Rows
 
-  .#{$primaryStyleName}-row > td {
+  .#{$primaryStyleName}-row > td,
+  .#{$primaryStyleName}-editor-cells > div {
     border-left: $v-grid-cell-vertical-border;
     border-bottom: $v-grid-cell-horizontal-border;
 
@@ -221,22 +222,35 @@ $v-grid-editor-background-color: $v-grid-row-background-color !default;
   // Editor
 
   .#{$primaryStyleName}-editor {
-    // TODO should be fixed in offset calculations
-    margin-top: -1px;
     position: absolute;
-    overflow-y: visible;
-    background: $v-grid-editor-background-color;
-    @include box-shadow(0 0 10px 1px rgba(0,0,0,.3));
+    z-index: 20;
+    overflow: hidden;
+    left: 0;
+    right: 0;
+    border: $v-grid-border;
+    margin-top: nth($v-grid-border, 1) * -1;
+    @include box-shadow(0 0 9px rgba(0,0,0,.2));
+  }
+
+  .#{$primaryStyleName}-editor-cells {
+    position: relative;
+    white-space: nowrap;
 
     > div {
-      position: absolute;
+      display: inline-block;
       @include box-sizing(border-box);
-      border-left: $v-grid-cell-vertical-border;
+      vertical-align: middle;
+      background: $v-grid-editor-background-color;
 
       &:first-child {
         border-left: none;
       }
 
+      > * {
+        vertical-align: middle;
+        display: inline-block;
+      }
+
       .v-textfield,
       .v-datefield,
       .v-filterselect {
@@ -244,24 +258,60 @@ $v-grid-editor-background-color: $v-grid-row-background-color !default;
         max-width: 100%;
         min-height: 100%;
         max-height: 100%;
-        border: none;
-        border-radius: 0;
       }
 
-      .v-textfield-focus,
-      .v-filterselect-focus input {
-        position: relative;
-        z-index: 1;
+      .v-select,
+      .v-select-select {
+        min-width: 100%;
+        max-width: 100%;
       }
     }
   }
 
-  .#{$primaryStyleName}-editor-save,
-  .#{$primaryStyleName}-editor-cancel {
-    position: absolute;
-    // TODO remove the inline size from the widgets
-    width: auto !important;
-    height: auto !important;
+  .#{$primaryStyleName}-editor-footer {
+    display: table;
+    height: $v-grid-row-height;
+    border-top: $v-grid-cell-horizontal-border;
+    margin-top: nth($v-grid-cell-horizontal-border, 1) * -1;
+    background: $v-grid-row-background-color;
+    padding: 0 5px;
+
+    + .#{$primaryStyleName}-editor-cells > div {
+      border-bottom: none;
+      border-top: $v-grid-cell-horizontal-border;
+    }
+
+    &:first-child {
+      border-top: none;
+      margin-top: 0;
+      border-bottom: $v-grid-cell-horizontal-border;
+      margin-bottom: nth($v-grid-cell-horizontal-border, 1) * -1;
+    }
+  }
+
+  .#{$primaryStyleName}-editor-message,
+  .#{$primaryStyleName}-editor-buttons {
+    display: table-cell;
+    white-space: nowrap;
+    vertical-align: middle;
+  }
+
+  .#{$primaryStyleName}-editor-message {
+    width: 100%;
+    position: relative;
+
+    > div {
+      position: absolute;
+      width: 100%;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      line-height: $v-grid-row-height;
+      top: 0;
+    }
+  }
+
+  .#{$primaryStyleName}-editor-save {
+    margin-right: 4px;
   }
 
   // Renderers
index 8dacb3ccce1108073c661716543d866d46ab324e..28c5977fda4b74962986eff05fcf3346322c96ff 100644 (file)
     background-image: url(img/desc-light.png);
   }
 
+  .#{$primaryStyleName}-editor-footer {
+    background: #dfe1e3;
+  }
+
+  .#{$primaryStyleName}-editor-cells > div {
+    .v-textfield,
+    .v-textfield-focus,
+    .v-datefield,
+    .v-datefield .v-textfield-focus,
+    .v-filterselect,
+    .v-filterselect-input,
+    .v-filterselect-input:focus {
+      border: none;
+      border-radius: 0;
+      background: transparent;
+    }
+
+    .v-filterselect {
+      overflow: hidden;
+    }
+
+    .v-filterselect-input {
+      height: 100%;
+    }
+
+    .v-filterselect-button {
+      // 24px is the height of v-filterselect
+      margin-top: round((24px - $v-grid-row-height) / -2)
+    }
+  }
+
 }
index a1081878ccda71618045a28fe9f3134f71453876..06a04ec62663d811c5128727cd80bc6d182baaff 100644 (file)
@@ -50,4 +50,8 @@
     background-image: url(img/sort-desc.png);
   }
 
+  .#{$primaryStyleName}-editor-footer {
+    background: #e7e9ea;
+  }
+
 }
index 50c71692256e6f6ed315f2e08a51999c9cf315f3..c481f127a85533f3b64cb861825e7be55b0e9850 100644 (file)
@@ -3,7 +3,7 @@
 $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: valo-border($color: $v-grid-row-background-color, $strength: 0.8) !default;
+$v-grid-border: flatten-list(valo-border($color: $v-grid-row-background-color, $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;
@@ -14,6 +14,8 @@ $v-grid-header-background-color: $v-background-color !default;
 
 $v-grid-cell-padding-horizontal: $v-table-cell-padding-horizontal !default;
 
+$v-grid-animations-enabled: $v-animations-enabled !default;
+
 
 @import "../../base/grid/grid";
 
@@ -68,10 +70,91 @@ $v-grid-cell-padding-horizontal: $v-table-cell-padding-horizontal !default;
     }
   }
 
+  .#{$primary-stylename}-editor {
+    @include valo-focus-style;
+    border-color: $v-focus-color;
+  }
+
+  .#{$primary-stylename}-editor-footer {
+    font-size: $v-font-size--small;
+    padding: 0 round($v-layout-spacing-horizontal / 2);
+    background: $v-app-background-color;
+    @if $v-grid-animations-enabled {
+      @include animation(valo-grid-editor-footer-animate-in 200ms 120ms backwards);
+    }
+  }
+
+  @if $v-grid-animations-enabled {
+    .#{$primary-stylename}-editor-footer:first-child {
+      @include animation(valo-grid-editor-footer-animate-in-alt 200ms 120ms backwards);
+    }
+  }
+
+  .#{$primary-stylename}-editor-cells {
+    z-index: 1;
+  }
+
+  .#{$primary-stylename}-editor-cells > div {
+    // Vertical centering for widgets
+    &:before {
+      content: "";
+      display: inline-block;
+      height: 100%;
+      vertical-align: middle;
+    }
+
+    .v-textfield,
+    .v-textfield-focus,
+    .v-datefield,
+    .v-datefield .v-textfield-focus,
+    .v-filterselect-input,
+    .v-filterselect-input:focus {
+      border: none;
+      border-radius: 0;
+      background: transparent;
+
+      @if $v-textfield-bevel {
+        @include box-shadow(valo-bevel-and-shadow($bevel: $v-textfield-bevel));
+      } @else {
+        @include box-shadow(none);
+      }
+    }
+
+    .v-textfield-focus,
+    .v-datefield .v-textfield-focus,
+    .v-filterselect-input:focus {
+      position: relative;
+    }
+
+    .v-select {
+      padding-left: round($v-grid-cell-padding-horizontal / 2);
+      padding-right: round($v-grid-cell-padding-horizontal / 2);
+    }
+
+    .v-checkbox {
+      margin: 0 round($v-grid-cell-padding-horizontal / 2);
+
+      label {
+        white-space: nowrap;
+      }
+    }
+  }
+
+  .#{$primary-stylename}-editor-message > div:before {
+    display: inline-block;
+    @include valo-error-indicator-style($is-pseudo-element: true);
+  }
+
   .#{$primary-stylename}-editor-save,
   .#{$primary-stylename}-editor-cancel {
-    @include valo-button-static-style;
-    @include valo-button-style($unit-size: $v-unit-size--small, $font-size: $v-font-size--small);
+    @include valo-link-style;
+    font-weight: $v-font-weight + 100;
+    text-decoration: none;
+    border: none;
+    background: transparent;
+    padding: round($v-layout-spacing-vertical / 2) round($v-layout-spacing-horizontal / 2);
+    margin: 0;
+    outline: none;
   }
 
   // Customize scrollbars
@@ -98,3 +181,19 @@ $v-grid-cell-padding-horizontal: $v-table-cell-padding-horizontal !default;
   }
 
 }
+
+
+@include keyframes(valo-grid-editor-footer-animate-in) {
+  0% {
+    margin-top: -$v-grid-row-height;
+  }
+}
+
+@include keyframes(valo-grid-editor-footer-animate-in-alt) {
+  0% {
+    margin-bottom: -$v-grid-row-height - first-number($v-grid-cell-horizontal-border);
+  }
+  100% {
+    margin-bottom: first-number($v-grid-cell-horizontal-border) * -1;
+  }
+}
index 4ce294b06adee2233d2a444aefa5de40b8e89e90..b4e85641195b49a43f21fad67ec7c2fb47871feb 100644 (file)
@@ -394,16 +394,22 @@ $valo-shared-pathPrefix: null;
 /**
  * Error indicator styles. The error indicator is by default a font character which you can style freely.
  *
+ * @param {boolean} $is-pseudo-element (false) - is the selector including this mixin targeting a pseudo element
+ *
  * @requires {mixin} valo-error-indicator-icon-style by default
  */
-@mixin valo-error-indicator-style {
+@mixin valo-error-indicator-style ($is-pseudo-element: false) {
   color: $v-error-indicator-color;
   font-weight: 600;
   width: ceil($v-unit-size/2);
   text-align: center;
 
-  &:before {
+  @if $is-pseudo-element {
     @include valo-error-indicator-icon-style;
+  } @else {
+    &:before {
+      @include valo-error-indicator-icon-style;
+    }
   }
 }
 
index 303b94763d5d0463bab6acb6c218e73deeabd468..fb7ec3abb5a6db79f9096241b952c04166372317 100644 (file)
@@ -942,13 +942,6 @@ public class Grid<T> extends ResizeComposite implements
      */
     protected static class Editor<T> {
 
-        private static final double BUTTON_HEIGHT = 25;
-        private static final double BUTTON_WIDTH = 50;
-        private static final double BUTTON_MARGIN = 5;
-        private static final double SAVE_BUTTON_LEFT_MARGIN_PX = 0;
-        private static final double CANCEL_BUTTON_LEFT_MARGIN_PX = SAVE_BUTTON_LEFT_MARGIN_PX
-                + BUTTON_WIDTH + BUTTON_MARGIN;
-
         public static final int KEYCODE_SHOW = KeyCodes.KEY_ENTER;
         public static final int KEYCODE_HIDE = KeyCodes.KEY_ESCAPE;
 
@@ -960,6 +953,16 @@ public class Grid<T> extends ResizeComposite implements
         private EditorHandler<T> handler;
 
         private DivElement editorOverlay = DivElement.as(DOM.createDiv());
+        private DivElement cellWrapper = DivElement.as(DOM.createDiv());
+        private DivElement messageAndButtonsWrapper = DivElement.as(DOM
+                .createDiv());
+
+        private DivElement messageWrapper = DivElement.as(DOM.createDiv());
+        private DivElement buttonsWrapper = DivElement.as(DOM.createDiv());
+
+        // Element which contains the error message for the editor
+        // Should only be added to the DOM when there's a message to show
+        private DivElement message = DivElement.as(DOM.createDiv());
 
         private Map<Column<?, T>, Widget> columnToWidget = new HashMap<Column<?, T>, Widget>();
 
@@ -1055,7 +1058,6 @@ public class Grid<T> extends ResizeComposite implements
         public Editor() {
             saveButton = new Button();
             saveButton.setText(GridConstants.DEFAULT_SAVE_CAPTION);
-            saveButton.setStylePrimaryName("v-nativebutton");
             saveButton.addClickHandler(new ClickHandler() {
                 @Override
                 public void onClick(ClickEvent event) {
@@ -1065,7 +1067,6 @@ public class Grid<T> extends ResizeComposite implements
 
             cancelButton = new Button();
             cancelButton.setText(GridConstants.DEFAULT_CANCEL_CAPTION);
-            cancelButton.setStylePrimaryName("v-nativebutton");
             cancelButton.addClickHandler(new ClickHandler() {
                 @Override
                 public void onClick(ClickEvent event) {
@@ -1274,23 +1275,7 @@ public class Grid<T> extends ResizeComposite implements
          */
         protected void showOverlay(TableRowElement tr) {
 
-            DivElement tableWrapper = DivElement.as(tr.getParentElement()
-                    .getParentElement().getParentElement());
-
-            AbstractRowContainer body = (AbstractRowContainer) grid
-                    .getEscalator().getBody();
-
-            double rowTop = body.getRowTop(tr);
-            int bodyTop = body.getElement().getAbsoluteTop();
-            int wrapperTop = tableWrapper.getAbsoluteTop();
-
-            double width = WidgetUtil
-                    .getRequiredWidthBoundingClientRectDouble(tr);
-            double height = WidgetUtil
-                    .getRequiredHeightBoundingClientRectDouble(tr);
-            double overlayTop = rowTop + bodyTop - wrapperTop;
-            setBounds(editorOverlay, tr.getOffsetLeft(), overlayTop, width,
-                    height);
+            DivElement gridElement = DivElement.as(grid.getElement());
 
             scrollHandler = grid.addScrollHandler(new ScrollHandler() {
                 @Override
@@ -1299,12 +1284,14 @@ public class Grid<T> extends ResizeComposite implements
                 }
             });
 
-            tableWrapper.appendChild(editorOverlay);
+            gridElement.appendChild(editorOverlay);
+            editorOverlay.appendChild(cellWrapper);
+            editorOverlay.appendChild(messageAndButtonsWrapper);
 
             for (int i = 0; i < tr.getCells().getLength(); i++) {
                 Element cell = createCell(tr.getCells().getItem(i));
 
-                editorOverlay.appendChild(cell);
+                cellWrapper.appendChild(cell);
 
                 Column<?, T> column = grid.getColumn(i);
                 if (column.isEditable()) {
@@ -1316,43 +1303,58 @@ public class Grid<T> extends ResizeComposite implements
                 }
             }
 
-            attachWidget(saveButton, editorOverlay);
-            attachWidget(cancelButton, editorOverlay);
+            // Only add these elements once
+            if (!messageAndButtonsWrapper.isOrHasChild(messageWrapper)) {
+                messageAndButtonsWrapper.appendChild(messageWrapper);
+                messageAndButtonsWrapper.appendChild(buttonsWrapper);
+            }
 
-            /*
-             * We can't use BUTTON_HEIGHT here, becuase it's ignored by at least
-             * Chrome and Firefox. So we measure it.
-             */
-            double buttonTop = getButtonTop(tr, saveButton.getOffsetHeight());
-            setBounds(saveButton.getElement(), SAVE_BUTTON_LEFT_MARGIN_PX,
-                    buttonTop, BUTTON_WIDTH, BUTTON_HEIGHT);
-            setBounds(cancelButton.getElement(), CANCEL_BUTTON_LEFT_MARGIN_PX,
-                    buttonTop, BUTTON_WIDTH, BUTTON_HEIGHT);
+            attachWidget(saveButton, buttonsWrapper);
+            attachWidget(cancelButton, buttonsWrapper);
 
             updateHorizontalScrollPosition();
-        }
 
-        private double getButtonTop(TableRowElement tr, int buttonHeight) {
-            boolean buttonsShouldBeRenderedBelow = buttonsShouldBeRenderedBelow(
-                    tr, buttonHeight);
-            final double buttonTop;
-            if (buttonsShouldBeRenderedBelow) {
-                buttonTop = tr.getOffsetHeight() + BUTTON_MARGIN;
+            AbstractRowContainer body = (AbstractRowContainer) grid
+                    .getEscalator().getBody();
+            double rowTop = body.getRowTop(tr);
+
+            int bodyTop = body.getElement().getAbsoluteTop();
+            int gridTop = gridElement.getAbsoluteTop();
+            double overlayTop = rowTop + bodyTop - gridTop;
+
+            if (buttonsShouldBeRenderedBelow(tr)) {
+                // Default case, editor buttons are below the edited row
+                editorOverlay.getStyle().setTop(overlayTop, Unit.PX);
+                editorOverlay.getStyle().clearBottom();
             } else {
-                buttonTop = -(buttonHeight + BUTTON_MARGIN);
+                // Move message and buttons wrapper on top of cell wrapper if
+                // there is not enough space visible space under and fix the
+                // overlay from the bottom
+                editorOverlay.appendChild(cellWrapper);
+                int gridHeight = grid.getElement().getOffsetHeight();
+                editorOverlay.getStyle()
+                        .setBottom(
+                                gridHeight - overlayTop - tr.getOffsetHeight(),
+                                Unit.PX);
+                editorOverlay.getStyle().clearTop();
+            }
+
+            // Do not render over the vertical scrollbar
+            int nativeScrollbarSize = WidgetUtil.getNativeScrollbarSize();
+            if (nativeScrollbarSize > 0) {
+                editorOverlay.getStyle().setRight(nativeScrollbarSize, Unit.PX);
             }
-            return buttonTop;
         }
 
-        private boolean buttonsShouldBeRenderedBelow(TableRowElement tr,
-                int buttonHeight) {
+        private boolean buttonsShouldBeRenderedBelow(TableRowElement tr) {
             TableSectionElement tfoot = grid.escalator.getFooter().getElement();
             double tfootPageTop = WidgetUtil.getBoundingClientRect(tfoot)
                     .getTop();
             double trPageBottom = WidgetUtil.getBoundingClientRect(tr)
                     .getBottom();
-            double bottomOfButtons = trPageBottom + buttonHeight
-                    + BUTTON_MARGIN;
+            int messageAndButtonsHeight = messageAndButtonsWrapper
+                    .getOffsetHeight();
+            double bottomOfButtons = trPageBottom + messageAndButtonsHeight;
             return bottomOfButtons < tfootPageTop;
         }
 
@@ -1366,6 +1368,7 @@ public class Grid<T> extends ResizeComposite implements
             detachWidget(cancelButton);
 
             editorOverlay.removeAllChildren();
+            cellWrapper.removeAllChildren();
             editorOverlay.removeFromParent();
 
             scrollHandler.removeHandler();
@@ -1374,14 +1377,27 @@ public class Grid<T> extends ResizeComposite implements
         protected void setStylePrimaryName(String primaryName) {
             if (styleName != null) {
                 editorOverlay.removeClassName(styleName);
+
+                cellWrapper.removeClassName(styleName + "-cells");
+                messageAndButtonsWrapper.removeClassName(styleName + "-footer");
+
+                messageWrapper.removeClassName(styleName + "-message");
+                buttonsWrapper.removeClassName(styleName + "-buttons");
+
                 saveButton.removeStyleName(styleName + "-save");
                 cancelButton.removeStyleName(styleName + "-cancel");
             }
             styleName = primaryName + "-editor";
-            editorOverlay.addClassName(styleName);
+            editorOverlay.setClassName(styleName);
 
-            saveButton.addStyleName(styleName + "-save");
-            cancelButton.addStyleName(styleName + "-cancel");
+            cellWrapper.setClassName(styleName + "-cells");
+            messageAndButtonsWrapper.setClassName(styleName + "-footer");
+
+            messageWrapper.setClassName(styleName + "-message");
+            buttonsWrapper.setClassName(styleName + "-buttons");
+
+            saveButton.setStyleName(styleName + "-save");
+            cancelButton.setStyleName(styleName + "-cancel");
         }
 
         /**
@@ -1425,13 +1441,7 @@ public class Grid<T> extends ResizeComposite implements
 
         private void updateHorizontalScrollPosition() {
             double scrollLeft = grid.getScrollLeft();
-
-            editorOverlay.getStyle().setLeft(-scrollLeft, Unit.PX);
-
-            double saveLeftPx = scrollLeft + SAVE_BUTTON_LEFT_MARGIN_PX;
-            double cancelLeftPx = scrollLeft + CANCEL_BUTTON_LEFT_MARGIN_PX;
-            saveButton.getElement().getStyle().setLeft(saveLeftPx, Unit.PX);
-            cancelButton.getElement().getStyle().setLeft(cancelLeftPx, Unit.PX);
+            cellWrapper.getStyle().setLeft(-scrollLeft, Unit.PX);
         }
 
         protected void setGridEnabled(boolean enabled) {
index 5a27bc14443034d0e853fec75367f8d620e0d8c5..6b1279d2c22b2264a8e5aaf5f5cbb118d301730b 100644 (file)
@@ -106,8 +106,7 @@ public class GridElement extends AbstractComponentElement {
          * useless.
          */
         public void save() {
-            List<WebElement> buttons = findElements(By.xpath("./button"));
-            buttons.get(0).click();
+            findElement(By.className("v-grid-editor-save")).click();
         }
 
         /**
@@ -117,8 +116,7 @@ public class GridElement extends AbstractComponentElement {
          * useless.
          */
         public void cancel() {
-            List<WebElement> buttons = findElements(By.xpath("./button"));
-            buttons.get(1).click();
+            findElement(By.className("v-grid-editor-cancel")).click();
         }
     }
 
index f6f2fac074bd1771e242b15853542b2f08dfcc1e..30481ebc651ea126df6d0ebd8f6e1da6ac9922e5 100644 (file)
@@ -117,8 +117,9 @@ public class GridEditorClientTest extends GridBasicClientFeaturesTest {
         selectMenuPath("Component", "State", "Selection mode", "multi");
         selectMenuPath(EDIT_ROW_5);
 
-        WebElement editor = getEditor();
-        List<WebElement> selectorDivs = editor.findElements(By
+        WebElement editorCells = findElement(By
+                .className("v-grid-editor-cells"));
+        List<WebElement> selectorDivs = editorCells.findElements(By
                 .cssSelector("div"));
 
         assertTrue("selector column cell should've been empty", selectorDivs