From 7c6f4ef6e12836508f63cdee79c11aecdfa93291 Mon Sep 17 00:00:00 2001 From: Pekka Hyvönen Date: Thu, 12 Feb 2015 11:39:44 +0200 Subject: Drag and drop column reorder for default header row on client side Grid. #16643 Change-Id: I94558915859aaa91bdd041da12094bc0dcc53e62 --- WebContent/VAADIN/themes/base/grid/grid.scss | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) (limited to 'WebContent/VAADIN/themes') diff --git a/WebContent/VAADIN/themes/base/grid/grid.scss b/WebContent/VAADIN/themes/base/grid/grid.scss index e4a4a1d920..d0bae911db 100644 --- a/WebContent/VAADIN/themes/base/grid/grid.scss +++ b/WebContent/VAADIN/themes/base/grid/grid.scss @@ -52,6 +52,27 @@ $v-grid-editor-background-color: $v-grid-row-background-color !default; border: $v-grid-border; } + .#{$primaryStyleName} .header-drag-table { + border-spacing: 0; + table-layout: fixed; + width: inherit; // a decent default fallback + + .#{$primaryStyleName}-header { + + > .#{$primaryStyleName}-cell { + border: $v-grid-border; + opacity: 0.9; + filter: alpha(opacity=90); // IE8 + } + + > .#{$primaryStyleName}-drop-marker { + background-color: #197de1; + position: absolute; + width: 3px; + } + } + } + // Common cell styles .#{$primaryStyleName}-cell { -- cgit v1.2.3 From 556c1aa06c547933bc36f347693c4b5b85bac149 Mon Sep 17 00:00:00 2001 From: Henrik Paul Date: Wed, 11 Feb 2015 14:52:02 +0200 Subject: Escalator supports adding spacer elements into DOM. (#16644) This is the first step towards Grid's details rows: Escalator puts spacer elements in the DOM, and is able to scroll around with them. The spacers are put in their correct locations, but they will not affect the normal row elements in any way at this time. Change-Id: Id20090c4de117e07e332dcc81e9964360f778258 --- .../VAADIN/themes/base/escalator/escalator.scss | 13 + .../client/widget/escalator/RowContainer.java | 39 +- .../src/com/vaadin/client/widgets/Escalator.java | 451 ++++++++++++--------- .../EscalatorBasicClientFeaturesTest.java | 26 ++ .../escalator/EscalatorSpacerTest.java | 48 +++ .../grid/EscalatorBasicClientFeaturesWidget.java | 29 ++ .../widgetset/client/grid/EscalatorProxy.java | 23 +- 7 files changed, 441 insertions(+), 188 deletions(-) create mode 100644 uitest/src/com/vaadin/tests/components/grid/basicfeatures/escalator/EscalatorSpacerTest.java (limited to 'WebContent/VAADIN/themes') diff --git a/WebContent/VAADIN/themes/base/escalator/escalator.scss b/WebContent/VAADIN/themes/base/escalator/escalator.scss index 606dc6a7dd..6d146f3a74 100644 --- a/WebContent/VAADIN/themes/base/escalator/escalator.scss +++ b/WebContent/VAADIN/themes/base/escalator/escalator.scss @@ -133,4 +133,17 @@ z-index: 1; } + .#{$primaryStyleName}-spacer { + position: absolute; + display: block; + + // debug + background-color: rgba(0,0,0,0.6); + color: white; + + > td { + width: 100%; + height: 100%; + } + } } diff --git a/client/src/com/vaadin/client/widget/escalator/RowContainer.java b/client/src/com/vaadin/client/widget/escalator/RowContainer.java index 397336450e..5cc58cb47e 100644 --- a/client/src/com/vaadin/client/widget/escalator/RowContainer.java +++ b/client/src/com/vaadin/client/widget/escalator/RowContainer.java @@ -22,16 +22,47 @@ import com.google.gwt.dom.client.TableSectionElement; /** * A representation of the rows in each of the sections (header, body and - * footer) in an {@link Escalator}. + * footer) in an {@link com.vaadin.client.widgets.Escalator}. * * @since 7.4 * @author Vaadin Ltd - * @see Escalator#getHeader() - * @see Escalator#getBody() - * @see Escalator#getFooter() + * @see com.vaadin.client.widgets.Escalator#getHeader() + * @see com.vaadin.client.widgets.Escalator#getBody() + * @see com.vaadin.client.widgets.Escalator#getFooter() + * @see SpacerContainer */ public interface RowContainer { + /** + * The row container for the body section in an + * {@link com.vaadin.client.widgets.Escalator}. + *

+ * The body section can contain both rows and spacers. + * + * @since + * @author Vaadin Ltd + * @see com.vaadin.client.widgets.Escalator#getBody() + */ + public interface BodyRowContainer extends RowContainer { + /** + * Marks a spacer and its height. + *

+ * If a spacer is already registered with the given row index, that + * spacer will be updated with the given height. + * + * @param rowIndex + * the row index for the spacer to modify. The affected + * spacer is underneath the given index + * @param height + * the pixel height of the spacer. If {@code height} is + * negative, the affected spacer (if exists) will be removed + * @throws IllegalArgumentException + * if {@code rowIndex} is not a valid row index + */ + void setSpacer(int rowIndex, double height) + throws IllegalArgumentException; + } + /** * An arbitrary pixel height of a row, before any autodetection for the row * height has been made. diff --git a/client/src/com/vaadin/client/widgets/Escalator.java b/client/src/com/vaadin/client/widgets/Escalator.java index ca54b97ca5..2536356c94 100644 --- a/client/src/com/vaadin/client/widgets/Escalator.java +++ b/client/src/com/vaadin/client/widgets/Escalator.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Map.Entry; +import java.util.TreeMap; import java.util.logging.Level; import java.util.logging.Logger; @@ -67,6 +68,7 @@ import com.vaadin.client.widget.escalator.PositionFunction.Translate3DPosition; import com.vaadin.client.widget.escalator.PositionFunction.TranslatePosition; import com.vaadin.client.widget.escalator.PositionFunction.WebkitTranslate3DPosition; import com.vaadin.client.widget.escalator.RowContainer; +import com.vaadin.client.widget.escalator.RowContainer.BodyRowContainer; import com.vaadin.client.widget.escalator.RowVisibilityChangeEvent; import com.vaadin.client.widget.escalator.RowVisibilityChangeHandler; import com.vaadin.client.widget.escalator.ScrollbarBundle; @@ -93,7 +95,7 @@ import com.vaadin.shared.util.SharedUtil; |-- AbstractStaticRowContainer | |-- HeaderRowContainer | `-- FooterContainer - `---- BodyRowContainer + `---- BodyRowContainerImpl AbstractRowContainer is intended to contain all common logic between RowContainers. It manages the bookkeeping of row @@ -106,7 +108,7 @@ import com.vaadin.shared.util.SharedUtil; are pretty thin special cases of a StaticRowContainer (mostly relating to positioning of the root element). - BodyRowContainer could also be split into an additional + BodyRowContainerImpl could also be split into an additional "AbstractScrollingRowContainer", but I felt that no more inner classes were needed. So it contains both logic required for making things scroll about, and equivalent @@ -118,8 +120,8 @@ import com.vaadin.shared.util.SharedUtil; Each RowContainer can be thought to have three levels of indices for any given displayed row (but the distinction - matters primarily for the BodyRowContainer, because of the - way it scrolls through data): + matters primarily for the BodyRowContainerImpl, because of + the way it scrolls through data): - Logical index - Physical (or DOM) index @@ -137,9 +139,9 @@ import com.vaadin.shared.util.SharedUtil; (because of 0-based indices). In Header and FooterRowContainers, you are safe to assume that the logical index is the same as the physical index. But because the - BodyRowContainer never displays large data sources entirely - in the DOM, a physical index usually has no apparent direct - relationship with its logical index. + BodyRowContainerImpl never displays large data sources + entirely in the DOM, a physical index usually has no + apparent direct relationship with its logical index. VISUAL INDEX is the index relating to the order that you see a row in, in the browser, as it is rendered. The @@ -147,20 +149,20 @@ import com.vaadin.shared.util.SharedUtil; index is similar to the physical index in the sense that Header and FooterRowContainers can assume a 1:1 relationship between visual index and logical index. And - again, BodyRowContainer has no such relationship. The + again, BodyRowContainerImpl has no such relationship. The body's visual index has additionally no apparent relationship with its physical index. Because the tags are reused in the body and visually repositioned with CSS as the user scrolls, the relationship between physical index and visual index is quickly broken. You can get an element's visual index via the field - BodyRowContainer.visualRowOrder. + BodyRowContainerImpl.visualRowOrder. Currently, the physical and visual indices are kept in sync _most of the time_ by a deferred rearrangement of rows. They become desynced when scrolling. This is to help screen readers to read the contents from the DOM in a natural - order. See BodyRowContainer.DeferredDomSorter for more + order. See BodyRowContainerImpl.DeferredDomSorter for more about that. */ @@ -271,12 +273,6 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker * that it _might_ perform better (rememeber to measure, implement, * re-measure) */ - /* - * [[rowheight]]: This code will require alterations that are relevant for - * being able to support variable row heights. NOTE: these bits can most - * often also be identified by searching for code reading the ROW_HEIGHT_PX - * constant. - */ /* * [[mpixscroll]]: This code will require alterations that are relevant for * supporting the scrolling through more pixels than some browsers normally @@ -284,6 +280,9 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker * escalator DOM). NOTE: these bits can most often also be identified by * searching for code that call scrollElem.getScrollTop();. */ + /* + * [[spacer]]: Code that is important to make spacers work. + */ /** * A utility class that contains utility methods that are usually called @@ -1139,10 +1138,6 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker public void scrollToRow(final int rowIndex, final ScrollDestination destination, final double padding) { - /* - * FIXME [[rowheight]]: coded to work only with default row heights - * - will not work with variable row heights - */ final double targetStartPx = body.getDefaultRowHeight() * rowIndex; final double targetEndPx = targetStartPx + body.getDefaultRowHeight(); @@ -1183,19 +1178,6 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker */ private String primaryStyleName = null; - /** - * A map containing cached values of an element's current top position. - *

- * Don't use this field directly, because it will not take proper care - * of all the bookkeeping required. - * - * @deprecated Use {@link #setRowPosition(Element, int, int)}, - * {@link #getRowTop(Element)} and - * {@link #removeRowPosition(Element)} instead. - */ - @Deprecated - private final Map rowTopPositionMap = new HashMap(); - private boolean defaultRowHeightShouldBeAutodetected = true; private double defaultRowHeight = INITIAL_DEFAULT_ROW_HEIGHT; @@ -1907,20 +1889,17 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker */ } - @SuppressWarnings("boxing") protected void setRowPosition(final TableRowElement tr, final int x, final double y) { - position.set(tr, x, y); - rowTopPositionMap.put(tr, y); + positions.set(tr, x, y); } - @SuppressWarnings("boxing") protected double getRowTop(final TableRowElement tr) { - return rowTopPositionMap.get(tr); + return positions.getTop(tr); } protected void removeRowPosition(TableRowElement tr) { - rowTopPositionMap.remove(tr); + positions.remove(tr); } public void autodetectRowHeightLater() { @@ -2204,18 +2183,7 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker return; } - /* - * TODO [[rowheight]]: even if no rows are evaluated in the current - * viewport, the heights of some unrendered rows might change in a - * refresh. This would cause the scrollbar to be adjusted (in - * scrollHeight and/or scrollTop). Do we want to take this into - * account? - */ if (hasColumnAndRowData()) { - /* - * TODO [[rowheight]]: nudge rows down with - * refreshRowPositions() as needed - */ for (int row = logicalRowRange.getStart(); row < logicalRowRange .getEnd(); row++) { final TableRowElement tr = getTrByVisualIndex(row); @@ -2290,7 +2258,8 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker } } - private class BodyRowContainer extends AbstractRowContainer { + private class BodyRowContainerImpl extends AbstractRowContainer implements + BodyRowContainer { /* * TODO [[optimize]]: check whether a native JsArray might be faster * than LinkedList @@ -2401,7 +2370,9 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker private DeferredDomSorter domSorter = new DeferredDomSorter(); - public BodyRowContainer(final TableSectionElement bodyElement) { + private final SpacerContainer spacerContainer = new SpacerContainer(); + + public BodyRowContainerImpl(final TableSectionElement bodyElement) { super(bodyElement); } @@ -2409,6 +2380,7 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker public void setStylePrimaryName(String primaryStyleName) { super.setStylePrimaryName(primaryStyleName); UIObject.setStylePrimaryName(root, primaryStyleName + "-body"); + spacerContainer.setStylePrimaryName(primaryStyleName); } public void updateEscalatorRowsOnScroll() { @@ -2431,21 +2403,13 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker if (viewportOffset > 0) { // there's empty room on top - /* - * FIXME [[rowheight]]: coded to work only with default row - * heights - will not work with variable row heights - */ int originalRowsToMove = (int) Math.ceil(viewportOffset / getDefaultRowHeight()); int rowsToMove = Math.min(originalRowsToMove, - root.getChildCount()); + visualRowOrder.size()); - final int end = root.getChildCount(); + final int end = visualRowOrder.size(); final int start = end - rowsToMove; - /* - * FIXME [[rowheight]]: coded to work only with default row - * heights - will not work with variable row heights - */ final int logicalRowIndex = (int) (scrollTop / getDefaultRowHeight()); moveAndUpdateEscalatorRows(Range.between(start, end), 0, logicalRowIndex); @@ -2456,11 +2420,6 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker } else if (viewportOffset + getDefaultRowHeight() <= 0) { - /* - * FIXME [[rowheight]]: coded to work only with default row - * heights - will not work with variable row heights - */ - /* * the viewport has been scrolled more than the topmost visual * row. @@ -2469,10 +2428,10 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker int originalRowsToMove = (int) Math.abs(viewportOffset / getDefaultRowHeight()); int rowsToMove = Math.min(originalRowsToMove, - root.getChildCount()); + visualRowOrder.size()); int logicalRowIndex; - if (rowsToMove < root.getChildCount()) { + if (rowsToMove < visualRowOrder.size()) { /* * We scroll so little that we can just keep adding the rows * below the current escalator @@ -2480,10 +2439,6 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker logicalRowIndex = getLogicalRowIndex(visualRowOrder .getLast()) + 1; } else { - /* - * FIXME [[rowheight]]: coded to work only with default row - * heights - will not work with variable row heights - */ /* * Since we're moving all escalator rows, we need to * calculate the first logical row index from the scroll @@ -2498,13 +2453,13 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker * moveAndUpdateEscalatorRows works, this will work out even if * we move all the rows, and try to place them "at the end". */ - final int targetVisualIndex = root.getChildCount(); + final int targetVisualIndex = visualRowOrder.size(); // make sure that we don't move rows over the data boundary boolean aRowWasLeftBehind = false; if (logicalRowIndex + rowsToMove > getRowCount()) { /* - * TODO [[rowheight]]: with constant row heights, there's + * TODO [[spacer]]: with constant row heights, there's * always exactly one row that will be moved beyond the data * source, when viewport is scrolled to the end. This, * however, isn't guaranteed anymore once row heights start @@ -2583,10 +2538,6 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker */ scroller.recalculateScrollbarsForVirtualViewport(); - /* - * FIXME [[rowheight]]: coded to work only with default row heights - * - will not work with variable row heights - */ final boolean addedRowsAboveCurrentViewport = index * getDefaultRowHeight() < getScrollTop(); final boolean addedRowsBelowCurrentViewport = index @@ -2600,10 +2551,6 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker * without re-evaluating any rows. */ - /* - * FIXME [[rowheight]]: coded to work only with default row - * heights - will not work with variable row heights - */ final double yDelta = numberOfRows * getDefaultRowHeight(); adjustScrollPosIgnoreEvents(yDelta); updateTopRowLogicalIndex(numberOfRows); @@ -2637,10 +2584,6 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker moveAndUpdateEscalatorRows(Range.between(start, end), visualTargetIndex, unupdatedLogicalStart); - /* - * FIXME [[rowheight]]: coded to work only with default row - * heights - will not work with variable row heights - */ // move the surrounding rows to their correct places. double rowTop = (unupdatedLogicalStart + (end - start)) * getDefaultRowHeight(); @@ -2649,10 +2592,6 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker while (i.hasNext()) { final TableRowElement tr = i.next(); setRowPosition(tr, 0, rowTop); - /* - * FIXME [[rowheight]]: coded to work only with default row - * heights - will not work with variable row heights - */ rowTop += getDefaultRowHeight(); } @@ -2762,10 +2701,6 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker } { // Reposition the rows that were moved - /* - * FIXME [[rowheight]]: coded to work only with default row - * heights - will not work with variable row heights - */ double newRowTop = logicalTargetIndex * getDefaultRowHeight(); final ListIterator iter = visualRowOrder @@ -2773,10 +2708,6 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker for (int i = 0; i < visualSourceRange.length(); i++) { final TableRowElement tr = iter.next(); setRowPosition(tr, 0, newRowTop); - /* - * FIXME [[rowheight]]: coded to work only with default row - * heights - will not work with variable row heights - */ newRowTop += getDefaultRowHeight(); } } @@ -2802,10 +2733,6 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker verticalScrollbar.setScrollPosByDelta(yDelta); - /* - * FIXME [[rowheight]]: coded to work only with default row heights - * - will not work with variable row heights - */ final double rowTopPos = yDelta - (yDelta % getDefaultRowHeight()); for (final TableRowElement tr : visualRowOrder) { setRowPosition(tr, 0, getRowTop(tr) + rowTopPos); @@ -2847,10 +2774,6 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker * added. */ for (int i = 0; i < addedRows.size(); i++) { - /* - * FIXME [[rowheight]]: coded to work only with default row - * heights - will not work with variable row heights - */ setRowPosition(addedRows.get(i), 0, (index + i) * getDefaultRowHeight()); } @@ -2859,10 +2782,6 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker for (int i = index + addedRows.size(); i < visualRowOrder .size(); i++) { final TableRowElement tr = visualRowOrder.get(i); - /* - * FIXME [[rowheight]]: coded to work only with default row - * heights - will not work with variable row heights - */ setRowPosition(tr, 0, i * getDefaultRowHeight()); } @@ -2873,10 +2792,6 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker } private int getMaxEscalatorRowCapacity() { - /* - * FIXME [[rowheight]]: coded to work only with default row heights - * - will not work with variable row heights - */ final int maxEscalatorRowCapacity = (int) Math .ceil(calculateHeight() / getDefaultRowHeight()) + 1; @@ -2930,10 +2845,6 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker .isEmpty() && removedVisualInside.getStart() == 0; if (!removedAbove.isEmpty() || firstVisualRowIsRemoved) { - /* - * FIXME [[rowheight]]: coded to work only with default row - * heights - will not work with variable row heights - */ final double yDelta = removedAbove.length() * getDefaultRowHeight(); final double firstLogicalRowHeight = getDefaultRowHeight(); @@ -2996,10 +2907,6 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker final int dirtyRowsStart = removedLogicalInside.getStart(); for (int i = dirtyRowsStart; i < escalatorRowCount; i++) { final TableRowElement tr = visualRowOrder.get(i); - /* - * FIXME [[rowheight]]: coded to work only with default - * row heights - will not work with variable row heights - */ setRowPosition(tr, 0, i * getDefaultRowHeight()); } @@ -3039,10 +2946,6 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker * double-refreshing. */ - /* - * FIXME [[rowheight]]: coded to work only with default row - * heights - will not work with variable row heights - */ final double contentBottom = getRowCount() * getDefaultRowHeight(); final double viewportBottom = tBodyScrollTop @@ -3097,7 +3000,7 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker */ /* - * FIXME [[rowheight]]: above if-clause is coded to only + * FIXME [[spacer]]: above if-clause is coded to only * work with default row heights - will not work with * variable row heights */ @@ -3160,12 +3063,6 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker for (int i = removedVisualInside.getStart(); i < escalatorRowCount; i++) { final TableRowElement tr = visualRowOrder.get(i); setRowPosition(tr, 0, (int) newTop); - - /* - * FIXME [[rowheight]]: coded to work only with - * default row heights - will not work with variable - * row heights - */ newTop += getDefaultRowHeight(); } @@ -3214,10 +3111,6 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker * 5 */ - /* - * FIXME [[rowheight]]: coded to work only with default - * row heights - will not work with variable row heights - */ final int rowsScrolled = (int) (Math .ceil((viewportBottom - contentBottom) / getDefaultRowHeight())); @@ -3269,20 +3162,12 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker final ListIterator iterator = visualRowOrder .listIterator(removedVisualInside.getStart()); - /* - * FIXME [[rowheight]]: coded to work only with default row heights - * - will not work with variable row heights - */ double rowTop = (removedLogicalInside.getStart() + logicalOffset) * getDefaultRowHeight(); for (int i = removedVisualInside.getStart(); i < escalatorRowCount - removedVisualInside.length(); i++) { final TableRowElement tr = iterator.next(); setRowPosition(tr, 0, rowTop); - /* - * FIXME [[rowheight]]: coded to work only with default row - * heights - will not work with variable row heights - */ rowTop += getDefaultRowHeight(); } } @@ -3305,19 +3190,11 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker // move the surrounding rows to their correct places. final ListIterator iterator = visualRowOrder .listIterator(removedVisualInside.getEnd()); - /* - * FIXME [[rowheight]]: coded to work only with default row heights - * - will not work with variable row heights - */ double rowTop = removedLogicalInside.getStart() * getDefaultRowHeight(); while (iterator.hasNext()) { final TableRowElement tr = iterator.next(); setRowPosition(tr, 0, rowTop); - /* - * FIXME [[rowheight]]: coded to work only with default row - * heights - will not work with variable row heights - */ rowTop += getDefaultRowHeight(); } } @@ -3364,8 +3241,8 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker } /* - * TODO [[rowheight]]: these assumptions will be totally broken with - * variable row heights. + * TODO [[spacer]]: these assumptions will be totally broken with + * spacers. */ final int maxEscalatorRows = getMaxEscalatorRowCapacity(); final int currentTopRowIndex = getLogicalRowIndex(visualRowOrder @@ -3586,10 +3463,6 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker if (!visualRowOrder.isEmpty()) { final double firstRowTop = getRowTop(visualRowOrder .getFirst()); - /* - * FIXME [[rowheight]]: coded to work only with default row - * heights - will not work with variable row heights - */ final double firstRowMinTop = tBodyScrollTop - getDefaultRowHeight(); if (firstRowTop < firstRowMinTop) { @@ -3614,20 +3487,6 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker return; } - /* - * As an intermediate step between hard-coded row heights to crazily - * varying row heights, Escalator will support the modification of - * the default row height (which is applied to all rows). - * - * This allows us to do some assumptions and simplifications for - * now. This code is intended to be quite short-lived, but gives - * insight into what needs to be done when row heights change in the - * body, in a general sense. - * - * TODO [[rowheight]] remove this comment once row heights may - * genuinely vary. - */ - Profiler.enter("Escalator.BodyRowContainer.reapplyDefaultRowHeights"); /* step 1: resize and reposition rows */ @@ -3661,10 +3520,6 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker /* step 3: make sure we have the correct amount of escalator rows. */ verifyEscalatorCount(); - /* - * TODO [[rowheight]] This simply doesn't work with variable rows - * heights. - */ int logicalLogical = (int) (getRowTop(visualRowOrder.getFirst()) / getDefaultRowHeight()); setTopRowLogicalIndex(logicalLogical); @@ -3775,6 +3630,12 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker return new Cell(getLogicalRowIndex(rowElement), cell.getColumn(), cell.getElement()); } + + @Override + public void setSpacer(int rowIndex, double height) + throws IllegalArgumentException { + spacerContainer.setSpacer(rowIndex, height); + } } private class ColumnConfigurationImpl implements ColumnConfiguration { @@ -4250,6 +4111,226 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker } } + private class SpacerContainer { + + /** This is used mainly for testing purposes */ + private static final String SPACER_LOGICAL_ROW_PROPERTY = "vLogicalRow"; + + /* + * TODO [[optimize]] maybe convert the usage of this class to flyweight + * or object pooling pattern? + */ + private final class Spacer { + private TableCellElement spacerElement; + private TableRowElement root; + + public TableRowElement getRootElement() { + return root; + } + + public void setPosition(double x, double y) { + positions.set(root, x, y); + } + + /** + * Creates a new element structure for the spacer. + *

+ * {@link #createDomStructure()} and + * {@link #setRootElement(Element)} can collectively only be called + * once, otherwise an {@link AssertionError} will be raised (if + * asserts are enabled). + */ + public void createDomStructure(double height) { + assert root == null || root.getParentElement() == null : "this spacer was already attached"; + + root = TableRowElement.as(DOM.createTR()); + spacerElement = TableCellElement.as(DOM.createTD()); + root.appendChild(spacerElement); + spacerElement.setInnerText("IAMA SPACER, AMA"); + initElements(height); + } + + private void initElements(double height) { + setHeight(height); + root.getStyle().setWidth(100, Unit.PCT); + + spacerElement.getStyle().setWidth(100, Unit.PCT); + spacerElement.setColSpan(getColumnConfiguration() + .getColumnCount()); + + setStylePrimaryName(getStylePrimaryName()); + } + + public void setRootElement(TableRowElement tr) { + assert root == null || root.getParentElement() == null : "this spacer was already attached"; + + assert tr != null : "tr may not be null"; + root = tr; + + assert tr.getChildCount() == 1 : "tr must have exactly one child"; + spacerElement = tr.getCells().getItem(0); + assert spacerElement != null : "spacer element somehow was null"; + } + + public void setStylePrimaryName(String style) { + UIObject.setStylePrimaryName(root, style + "-spacer"); + } + + public void setHeight(double newHeight) { + root.getStyle().setHeight(newHeight, Unit.PX); + getLogger().warning( + "spacer's height changed, but pushing rows out of " + + "the way not implemented yet"); + } + } + + private final TreeMap rowIndexToHeight = new TreeMap(); + private final TreeMap rowIndexToSpacerElement = new TreeMap(); + + public void setSpacer(int rowIndex, double height) + throws IllegalArgumentException { + if (rowIndex < 0 || rowIndex >= getBody().getRowCount()) { + throw new IllegalArgumentException("invalid row index: " + + rowIndex + ", while the body only has " + + getBody().getRowCount() + " rows."); + } + + if (height >= 0) { + insertOrUpdateSpacer(rowIndex, height); + } else if (spacerExists(rowIndex)) { + removeSpacer(rowIndex); + } + } + + @SuppressWarnings("boxing") + private void insertOrUpdateSpacer(int rowIndex, double height) { + if (!spacerExists(rowIndex)) { + insertSpacer(rowIndex, height); + } else { + updateSpacer(rowIndex, height); + } + rowIndexToHeight.put(rowIndex, height); + } + + private boolean spacerExists(int rowIndex) { + Integer rowIndexObj = Integer.valueOf(rowIndex); + boolean spacerExists = rowIndexToHeight.containsKey(rowIndexObj); + assert spacerExists == rowIndexToSpacerElement + .containsKey(rowIndexObj) : "Inconsistent bookkeeping detected."; + return spacerExists; + } + + @SuppressWarnings("boxing") + private void insertSpacer(int rowIndex, double height) { + Spacer spacer = createSpacer(height); + spacer.getRootElement().setPropertyInt(SPACER_LOGICAL_ROW_PROPERTY, + rowIndex); + TableRowElement spacerRoot = spacer.getRootElement(); + rowIndexToSpacerElement.put(rowIndex, spacerRoot); + spacer.setPosition(0, getSpacerTop(rowIndex)); + spacerRoot.getStyle().setWidth( + columnConfiguration.calculateRowWidth(), Unit.PX); + body.getElement().appendChild(spacerRoot); + } + + private void updateSpacer(int rowIndex, double newHeight) { + getSpacer(rowIndex).setHeight(newHeight); + } + + @SuppressWarnings("boxing") + private Spacer getSpacer(int rowIndex) { + Spacer spacer = new Spacer(); + spacer.setRootElement(rowIndexToSpacerElement.get(rowIndex)); + return spacer; + } + + private Spacer createSpacer(double height) { + /* + * Optimally, this would be a factory method in SpacerImpl, but + * since it's not a static class, we can't do that directly. We + * could make it static, and pass in references, but that probably + * will become hairy pretty quickly. + */ + + Spacer spacer = new Spacer(); + spacer.createDomStructure(height); + return spacer; + } + + private double getSpacerTop(int rowIndex) { + double spacerHeights = 0; + + /*- + * TODO: uncomment this bit once the spacers start pushing the + * rows downwards, offseting indices. OTOH this entire method + * probably needs to be usable by BodyContainerImpl as well, + * since this same logic can/should be used for calculating the + * top position for rows. + + // Sum all spacer heights that occur before rowIndex. + for (Double spacerHeight : rowIndexToHeight.headMap( + Integer.valueOf(rowIndex), false).values()) { + spacerHeights += spacerHeight.doubleValue(); + } + */ + + double rowHeights = getBody().getDefaultRowHeight() + * (rowIndex + 1); + + return rowHeights + spacerHeights; + } + + @SuppressWarnings("boxing") + private void removeSpacer(int rowIndex) { + Spacer spacer = getSpacer(rowIndex); + + // fix DOM + spacer.setHeight(0); // resets row offsets + spacer.getRootElement().removeFromParent(); + + // fix bookkeeping + rowIndexToHeight.remove(rowIndex); + rowIndexToSpacerElement.remove(rowIndex); + } + + public void setStylePrimaryName(String style) { + for (TableRowElement spacerRoot : rowIndexToSpacerElement.values()) { + Spacer spacer = new Spacer(); + spacer.setRootElement(spacerRoot); + spacer.setStylePrimaryName(style); + } + } + } + + private class ElementPositionBookkeeper { + /** + * A map containing cached values of an element's current top position. + *

+ * Don't use this field directly, because it will not take proper care + * of all the bookkeeping required. + */ + private final Map elementTopPositionMap = new HashMap(); + + public void set(final Element e, final double x, final double y) { + assert e != null : "Element was null"; + position.set(e, x, y); + elementTopPositionMap.put(e, Double.valueOf(y)); + } + + public double getTop(final Element e) { + Double top = elementTopPositionMap.get(e); + if (top == null) { + throw new IllegalArgumentException("Element " + e + + " was not found in the position bookkeeping"); + } + return top.doubleValue(); + } + + public void remove(Element e) { + elementTopPositionMap.remove(e); + } + } + // abs(atan(y/x))*(180/PI) = n deg, x = 1, solve y /** * The solution to @@ -4309,7 +4390,7 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker private final HorizontalScrollbarBundle horizontalScrollbar = new HorizontalScrollbarBundle(); private final HeaderRowContainer header = new HeaderRowContainer(headElem); - private final BodyRowContainer body = new BodyRowContainer(bodyElem); + private final BodyRowContainerImpl body = new BodyRowContainerImpl(bodyElem); private final FooterRowContainer footer = new FooterRowContainer(footElem); private final Scroller scroller = new Scroller(); @@ -4346,6 +4427,8 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker } }; + private final ElementPositionBookkeeper positions = new ElementPositionBookkeeper(); + /** * Creates a new Escalator widget instance. */ @@ -4517,7 +4600,7 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker int index = rowsToRemove - i - 1; TableRowElement tr = bodyElem.getRows().getItem(index); body.paintRemoveRow(tr, index); - body.removeRowPosition(tr); + positions.remove(tr); } body.visualRowOrder.clear(); body.setTopRowLogicalIndex(0); @@ -4595,7 +4678,7 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker * * @return the body. Never null */ - public RowContainer getBody() { + public BodyRowContainer getBody() { return body; } @@ -5190,4 +5273,10 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker columnConfiguration.getColumnWidth(i)); } } + + private Range getViewportPixels() { + int from = (int) Math.floor(verticalScrollbar.getScrollPos()); + int to = (int) Math.ceil(body.heightOfSection); + return Range.between(from, to); + } } diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/EscalatorBasicClientFeaturesTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/EscalatorBasicClientFeaturesTest.java index 92c7f3e6a6..b8bc644948 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/EscalatorBasicClientFeaturesTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/EscalatorBasicClientFeaturesTest.java @@ -19,6 +19,8 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.util.List; + import org.openqa.selenium.By; import org.openqa.selenium.Dimension; import org.openqa.selenium.JavascriptExecutor; @@ -64,6 +66,10 @@ public abstract class EscalatorBasicClientFeaturesTest extends MultiBrowserTest protected static final String COLUMN_SPANNING = "Column spanning"; protected static final String COLSPAN_NORMAL = "Apply normal colspan"; protected static final String COLSPAN_NONE = "Apply no colspan"; + protected static final String SPACERS = "Spacers"; + protected static final String ROW_1 = "Row 1"; + protected static final String SET_100PX = "Set 100px"; + protected static final String REMOVE = "Remove"; @Override protected Class getUIClass() { @@ -259,4 +265,24 @@ public abstract class EscalatorBasicClientFeaturesTest extends MultiBrowserTest protected void populate() { selectMenuPath(GENERAL, POPULATE_COLUMN_ROW); } + + private List getSpacers() { + return getEscalator().findElements(By.className("v-escalator-spacer")); + } + + @SuppressWarnings("boxing") + protected WebElement getSpacer(int logicalRowIndex) { + List spacers = getSpacers(); + System.out.println("size: " + spacers.size()); + for (WebElement spacer : spacers) { + System.out.println(spacer + ", " + logicalRowIndex); + Boolean isInDom = (Boolean) executeScript( + "return arguments[0]['vLogicalRow'] === arguments[1]", + spacer, logicalRowIndex); + if (isInDom) { + return spacer; + } + } + return null; + } } diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/escalator/EscalatorSpacerTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/escalator/EscalatorSpacerTest.java new file mode 100644 index 0000000000..24e474a767 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/escalator/EscalatorSpacerTest.java @@ -0,0 +1,48 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.components.grid.basicfeatures.escalator; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import org.junit.Before; +import org.junit.Test; + +import com.vaadin.tests.components.grid.basicfeatures.EscalatorBasicClientFeaturesTest; + +public class EscalatorSpacerTest extends EscalatorBasicClientFeaturesTest { + + @Before + public void before() { + openTestURL(); + populate(); + } + + @Test + public void openVisibleSpacer() { + assertNull("No spacers should be shown at the start", getSpacer(1)); + selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX); + assertNotNull("Spacer should be shown after setting it", getSpacer(1)); + } + + @Test + public void closeVisibleSpacer() { + selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX); + selectMenuPath(FEATURES, SPACERS, ROW_1, REMOVE); + assertNull("Spacer should not exist after removing it", getSpacer(1)); + } + +} diff --git a/uitest/src/com/vaadin/tests/widgetset/client/grid/EscalatorBasicClientFeaturesWidget.java b/uitest/src/com/vaadin/tests/widgetset/client/grid/EscalatorBasicClientFeaturesWidget.java index 761f32bc9a..0905cde4eb 100644 --- a/uitest/src/com/vaadin/tests/widgetset/client/grid/EscalatorBasicClientFeaturesWidget.java +++ b/uitest/src/com/vaadin/tests/widgetset/client/grid/EscalatorBasicClientFeaturesWidget.java @@ -303,6 +303,7 @@ public class EscalatorBasicClientFeaturesWidget extends createColumnsAndRowsMenu(); createFrozenMenu(); createColspanMenu(); + createSpacerMenu(); } private void createFrozenMenu() { @@ -612,6 +613,34 @@ public class EscalatorBasicClientFeaturesWidget extends }, menupath); } + private void createSpacerMenu() { + String[] menupath = { "Features", "Spacers" }; + createSpacersMenuForRow(1, menupath); + createSpacersMenuForRow(50, menupath); + } + + private void createSpacersMenuForRow(final int rowIndex, String[] menupath) { + menupath = new String[] { menupath[0], menupath[1], "Row " + rowIndex }; + addMenuCommand("Set 100px", new ScheduledCommand() { + @Override + public void execute() { + escalator.getBody().setSpacer(rowIndex, 100); + } + }, menupath); + addMenuCommand("Set 50px", new ScheduledCommand() { + @Override + public void execute() { + escalator.getBody().setSpacer(rowIndex, 50); + } + }, menupath); + addMenuCommand("Remove", new ScheduledCommand() { + @Override + public void execute() { + escalator.getBody().setSpacer(rowIndex, -1); + } + }, menupath); + } + private void insertRows(final RowContainer container, int offset, int number) { if (container == escalator.getBody()) { data.insertRows(offset, number); diff --git a/uitest/src/com/vaadin/tests/widgetset/client/grid/EscalatorProxy.java b/uitest/src/com/vaadin/tests/widgetset/client/grid/EscalatorProxy.java index 7f813b9d0f..63286af86f 100644 --- a/uitest/src/com/vaadin/tests/widgetset/client/grid/EscalatorProxy.java +++ b/uitest/src/com/vaadin/tests/widgetset/client/grid/EscalatorProxy.java @@ -24,6 +24,7 @@ import com.vaadin.client.widget.escalator.Cell; import com.vaadin.client.widget.escalator.ColumnConfiguration; import com.vaadin.client.widget.escalator.EscalatorUpdater; import com.vaadin.client.widget.escalator.RowContainer; +import com.vaadin.client.widget.escalator.RowContainer.BodyRowContainer; import com.vaadin.client.widgets.Escalator; import com.vaadin.tests.widgetset.client.grid.EscalatorBasicClientFeaturesWidget.LogWidget; @@ -97,6 +98,22 @@ public class EscalatorProxy extends Escalator { } } + private class BodyRowContainerProxy extends RowContainerProxy implements + BodyRowContainer { + private BodyRowContainer rowContainer; + + public BodyRowContainerProxy(BodyRowContainer rowContainer) { + super(rowContainer); + this.rowContainer = rowContainer; + } + + @Override + public void setSpacer(int rowIndex, double height) + throws IllegalArgumentException { + rowContainer.setSpacer(rowIndex, height); + } + } + private class RowContainerProxy implements RowContainer { private final RowContainer rowContainer; @@ -176,7 +193,7 @@ public class EscalatorProxy extends Escalator { } private RowContainer headerProxy = null; - private RowContainer bodyProxy = null; + private BodyRowContainer bodyProxy = null; private RowContainer footerProxy = null; private ColumnConfiguration columnProxy = null; private LogWidget logWidget; @@ -198,9 +215,9 @@ public class EscalatorProxy extends Escalator { } @Override - public RowContainer getBody() { + public BodyRowContainer getBody() { if (bodyProxy == null) { - bodyProxy = new RowContainerProxy(super.getBody()); + bodyProxy = new BodyRowContainerProxy(super.getBody()); } return bodyProxy; } -- cgit v1.2.3 From f5f699e61cd42f5c4ff1bc9b63ec2db8d446e316 Mon Sep 17 00:00:00 2001 From: Henrik Paul Date: Mon, 16 Feb 2015 17:05:40 +0200 Subject: Adds initial details row support to Grid (#16644) For now, the details row only supports text content and index based row references Change-Id: Id12557d83732475b9a5133b3ee32ae31ba4fcdd7 --- .../VAADIN/themes/base/escalator/escalator.scss | 4 +- WebContent/VAADIN/themes/base/grid/grid.scss | 4 + .../client/widget/grid/DetailsGenerator.java | 45 ++++++++++ client/src/com/vaadin/client/widgets/Grid.java | 100 ++++++++++++++++++++- .../client/grid/GridBasicClientFeaturesWidget.java | 40 +++++++++ 5 files changed, 189 insertions(+), 4 deletions(-) create mode 100644 client/src/com/vaadin/client/widget/grid/DetailsGenerator.java (limited to 'WebContent/VAADIN/themes') diff --git a/WebContent/VAADIN/themes/base/escalator/escalator.scss b/WebContent/VAADIN/themes/base/escalator/escalator.scss index 6d146f3a74..7949b52882 100644 --- a/WebContent/VAADIN/themes/base/escalator/escalator.scss +++ b/WebContent/VAADIN/themes/base/escalator/escalator.scss @@ -137,9 +137,7 @@ position: absolute; display: block; - // debug - background-color: rgba(0,0,0,0.6); - color: white; + background-color: $background-color; > td { width: 100%; diff --git a/WebContent/VAADIN/themes/base/grid/grid.scss b/WebContent/VAADIN/themes/base/grid/grid.scss index e4a4a1d920..730bee8a6b 100644 --- a/WebContent/VAADIN/themes/base/grid/grid.scss +++ b/WebContent/VAADIN/themes/base/grid/grid.scss @@ -328,6 +328,10 @@ $v-grid-editor-background-color: $v-grid-row-background-color !default; .#{$primaryStyleName}-editor-save { margin-right: 4px; } + + .#{$primaryStyleName}-spacer { + border: $v-grid-border; + } // Renderers diff --git a/client/src/com/vaadin/client/widget/grid/DetailsGenerator.java b/client/src/com/vaadin/client/widget/grid/DetailsGenerator.java new file mode 100644 index 0000000000..665362ca8e --- /dev/null +++ b/client/src/com/vaadin/client/widget/grid/DetailsGenerator.java @@ -0,0 +1,45 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid; + +/** + * A callback interface for generating details for a particular row in Grid. + * + * @since + * @author Vaadin Ltd + */ +public interface DetailsGenerator { + + public static final DetailsGenerator NULL = new DetailsGenerator() { + @Override + public String getDetails(int rowIndex) { + return null; + } + }; + + /** + * This method is called for whenever a new details row needs to be + * generated. + * + * @param rowIndex + * the index of the row for which to generate details + * @return the details for the given row, or null to leave the + * details empty. + */ + // TODO: provide a row object instead of index (maybe, needs discussion?) + // TODO: return a Widget instead of a String + String getDetails(int rowIndex); +} diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index 71962d6953..31984f7d5b 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -36,6 +36,7 @@ import com.google.gwt.dom.client.BrowserEvents; import com.google.gwt.dom.client.DivElement; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.EventTarget; +import com.google.gwt.dom.client.Node; import com.google.gwt.dom.client.Style; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.dom.client.TableCellElement; @@ -77,10 +78,13 @@ import com.vaadin.client.widget.escalator.RowContainer; import com.vaadin.client.widget.escalator.RowVisibilityChangeEvent; import com.vaadin.client.widget.escalator.RowVisibilityChangeHandler; import com.vaadin.client.widget.escalator.ScrollbarBundle.Direction; +import com.vaadin.client.widget.escalator.Spacer; +import com.vaadin.client.widget.escalator.SpacerUpdater; import com.vaadin.client.widget.grid.CellReference; import com.vaadin.client.widget.grid.CellStyleGenerator; import com.vaadin.client.widget.grid.DataAvailableEvent; import com.vaadin.client.widget.grid.DataAvailableHandler; +import com.vaadin.client.widget.grid.DetailsGenerator; import com.vaadin.client.widget.grid.EditorHandler; import com.vaadin.client.widget.grid.EditorHandler.EditorRequest; import com.vaadin.client.widget.grid.EventCellReference; @@ -1776,6 +1780,8 @@ public class Grid extends ResizeComposite implements private static final String CUSTOM_STYLE_PROPERTY_NAME = "customStyle"; + private static final double DETAILS_ROW_INITIAL_HEIGHT = 50; + private EventCellReference eventCell = new EventCellReference(this); private GridKeyDownEvent keyDown = new GridKeyDownEvent(this, eventCell); private GridKeyUpEvent keyUp = new GridKeyUpEvent(this, eventCell); @@ -2768,6 +2774,38 @@ public class Grid extends ResizeComposite implements } } + private class GridSpacerUpdater implements SpacerUpdater { + @Override + public void init(Spacer spacer) { + int rowIndex = spacer.getRow(); + + String string = detailsGenerator.getDetails(rowIndex); + if (string == null) { + destroy(spacer); + escalator.getBody().setSpacer(rowIndex, + DETAILS_ROW_INITIAL_HEIGHT); + return; + } + + spacer.getElement().setInnerText(string); + + /* + * 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); + } + + @Override + public void destroy(Spacer spacer) { + spacer.getElement().setInnerText(""); + } + } + /** * Escalator used internally by grid to render the rows */ @@ -2853,6 +2891,10 @@ public class Grid extends ResizeComposite implements private boolean enabled = true; + private DetailsGenerator detailsGenerator = DetailsGenerator.NULL; + + private GridSpacerUpdater gridSpacerUpdater = new GridSpacerUpdater(); + /** * Enumeration for easy setting of selection mode. */ @@ -4902,7 +4944,7 @@ public class Grid extends ResizeComposite implements EventTarget target = event.getEventTarget(); - if (!Element.is(target)) { + if (!Element.is(target) || isOrContainsInSpacer(Element.as(target))) { return; } @@ -4968,6 +5010,19 @@ 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; + } + n = n.getParentNode(); + } + return false; + } + private boolean isElementInChildWidget(Element e) { Widget w = WidgetUtil.findWidget(e, null); @@ -6279,4 +6334,47 @@ public class Grid extends ResizeComposite implements public void resetSizesFromDom() { getEscalator().resetSizesFromDom(); } + + /** + * Sets a new details generator for row details. + *

+ * The currently opened row details will be re-rendered. + * + * @since + * @param detailsGenerator + * the details generator to set + */ + public void setDetailsGenerator(DetailsGenerator detailsGenerator) + throws IllegalArgumentException { + + this.detailsGenerator = detailsGenerator; + + // this will refresh all visible spacers + escalator.getBody().setSpacerUpdater(gridSpacerUpdater); + } + + /** + * Gets the current details generator for row details. + * + * @since + * @return the detailsGenerator the current details generator + */ + public DetailsGenerator getDetailsGenerator() { + return detailsGenerator; + } + + /** + * Shows or hides the details for a specific row. + * + * @since + * @param row + * the index of the affected row + * @param visible + * true to show the details, or false + * to hide them + */ + public void setDetailsVisible(int rowIndex, boolean visible) { + double height = visible ? DETAILS_ROW_INITIAL_HEIGHT : -1; + escalator.getBody().setSpacer(rowIndex, height); + } } diff --git a/uitest/src/com/vaadin/tests/widgetset/client/grid/GridBasicClientFeaturesWidget.java b/uitest/src/com/vaadin/tests/widgetset/client/grid/GridBasicClientFeaturesWidget.java index 0452aa65d1..7c2ca3eedb 100644 --- a/uitest/src/com/vaadin/tests/widgetset/client/grid/GridBasicClientFeaturesWidget.java +++ b/uitest/src/com/vaadin/tests/widgetset/client/grid/GridBasicClientFeaturesWidget.java @@ -46,6 +46,7 @@ import com.vaadin.client.renderers.TextRenderer; import com.vaadin.client.ui.VLabel; import com.vaadin.client.widget.grid.CellReference; import com.vaadin.client.widget.grid.CellStyleGenerator; +import com.vaadin.client.widget.grid.DetailsGenerator; import com.vaadin.client.widget.grid.EditorHandler; import com.vaadin.client.widget.grid.RendererCellReference; import com.vaadin.client.widget.grid.RowReference; @@ -400,6 +401,7 @@ public class GridBasicClientFeaturesWidget extends createEditorMenu(); createInternalsMenu(); createDataSourceMenu(); + createDetailsMenu(); grid.getElement().getStyle().setZIndex(0); @@ -1214,4 +1216,42 @@ public class GridBasicClientFeaturesWidget extends String coords = "(" + object + ", " + column + ")"; label.setText(coords + " " + output); } + + private void createDetailsMenu() { + String[] menupath = new String[] { "Component", "Row details" }; + addMenuCommand("Set generator", new ScheduledCommand() { + @Override + public void execute() { + grid.setDetailsGenerator(new DetailsGenerator() { + @Override + public String getDetails(int rowIndex) { + return "Row: " + rowIndex + ". Lorem ipsum " + + "dolor sit amet, consectetur adipiscing " + + "elit. Morbi congue massa non augue " + + "pulvinar, nec consectetur justo efficitur. " + + "In nec arcu sit amet lorem hendrerit " + + "mollis."; + } + }); + } + }, menupath); + addMenuCommand("Toggle details for row 1", new ScheduledCommand() { + boolean visible = false; + + @Override + public void execute() { + visible = !visible; + grid.setDetailsVisible(1, visible); + } + }, menupath); + addMenuCommand("Toggle details for row 100", new ScheduledCommand() { + boolean visible = false; + + @Override + public void execute() { + visible = !visible; + grid.setDetailsVisible(100, visible); + } + }, menupath); + } } -- cgit v1.2.3 From bff2a3f558ea0416ae48b5595b59057a3475d6b6 Mon Sep 17 00:00:00 2001 From: Pekka Hyvönen Date: Thu, 19 Feb 2015 17:21:22 +0200 Subject: Theming and UX for Grid column reordering. (#16643) - Show sorting and focus on the dragged header. - Keep the focused header/cell the same after drag. - Make the drop marker get the same color as the grid selection. - Make dragged header and the drag element theme customizable - Valo related theming: little opacity, proper positioning Change-Id: Ia1c6f72ef2c7b4333e64ac410e50ef3688461749 --- WebContent/VAADIN/themes/base/grid/grid.scss | 5 +- .../VAADIN/themes/valo/components/_grid.scss | 9 + client/src/com/vaadin/client/widgets/Grid.java | 45 +++- .../grid/basicfeatures/GridBasicFeaturesTest.java | 44 ++++ .../basicfeatures/GridColumnReorderEventTest.java | 74 ------- .../grid/basicfeatures/GridColumnReorderTest.java | 233 +++++++++++++++++++++ .../server/GridColumnReorderTest.java | 32 --- 7 files changed, 326 insertions(+), 116 deletions(-) delete mode 100644 uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderEventTest.java create mode 100644 uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderTest.java (limited to 'WebContent/VAADIN/themes') diff --git a/WebContent/VAADIN/themes/base/grid/grid.scss b/WebContent/VAADIN/themes/base/grid/grid.scss index d0bae911db..35024e27c0 100644 --- a/WebContent/VAADIN/themes/base/grid/grid.scss +++ b/WebContent/VAADIN/themes/base/grid/grid.scss @@ -14,6 +14,7 @@ $v-grid-row-focused-background-color: null !default; $v-grid-header-row-height: null !default; $v-grid-header-font-size: $v-font-size !default; $v-grid-header-background-color: $v-grid-row-background-color !default; +$v-grid-header-drag-marked-color: $v-grid-row-selected-background-color !default; $v-grid-footer-row-height: $v-grid-header-row-height !default; $v-grid-footer-font-size: $v-grid-header-font-size !default; @@ -61,12 +62,14 @@ $v-grid-editor-background-color: $v-grid-row-background-color !default; > .#{$primaryStyleName}-cell { border: $v-grid-border; + margin-top: -10px; opacity: 0.9; filter: alpha(opacity=90); // IE8 + z-index: 30000; } > .#{$primaryStyleName}-drop-marker { - background-color: #197de1; + background-color: $v-grid-header-drag-marked-color; position: absolute; width: 3px; } diff --git a/WebContent/VAADIN/themes/valo/components/_grid.scss b/WebContent/VAADIN/themes/valo/components/_grid.scss index 4cac9c5e43..0d6d2ff0a6 100644 --- a/WebContent/VAADIN/themes/valo/components/_grid.scss +++ b/WebContent/VAADIN/themes/valo/components/_grid.scss @@ -40,6 +40,15 @@ $v-grid-animations-enabled: $v-animations-enabled !default; text-shadow: valo-text-shadow($font-color: valo-font-color($v-grid-header-background-color), $background-color: $v-grid-header-background-color); } + .#{$primary-stylename}-header .#{$primary-stylename}-cell.dragged { + @include opacity(0.5, false); + @include transition (opacity .3s ease-in-out); + } + + .#{$primary-stylename}-header .#{$primary-stylename}-cell.dragged-column-header { + margin-top: round($v-grid-row-height/-2); + } + .#{$primary-stylename}-footer .#{$primary-stylename}-cell { @include valo-gradient($v-grid-footer-background-color); text-shadow: valo-text-shadow($font-color: valo-font-color($v-grid-footer-background-color), $background-color: $v-grid-footer-background-color); diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index 643f223068..d54c023e3d 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -2932,10 +2932,6 @@ public class Grid extends ResizeComposite implements dropMarker.getStyle().setLeft(dropMarkerLeft, Unit.PX); } - private void resolveDragElementVerticalPosition() { - dragElement.getStyle().setTop(-10, Unit.PX); - } - private void resolveDragElementHorizontalPosition(final int clientX) { int left = clientX - table.getAbsoluteLeft(); left = Math.max(0, Math.min(left, table.getClientWidth())); @@ -2946,22 +2942,23 @@ public class Grid extends ResizeComposite implements @Override public void showDragElement() { initHeaderDragElementDOM(); - // TODO this clones also some unwanted style names, should confirm - // with UX what we want to show (focus/sort indicator) + // needs to clone focus and sorting indicators too (UX) dragElement = DOM.clone(eventCell.getElement(), true); dragElement.getStyle().clearWidth(); dropMarker.getStyle().setProperty("height", dragElement.getStyle().getHeight()); tableHeader.appendChild(dragElement); - // might need to change this on fly once sorting with multiple - // header rows is possible - resolveDragElementVerticalPosition(); + // mark the column being dragged for styling + eventCell.getElement().addClassName("dragged"); + // mark the floating cell, for styling & testing + dragElement.addClassName("dragged-column-header"); } @Override public void removeDragElement() { table.removeFromParent(); dragElement.removeFromParent(); + eventCell.getElement().removeClassName("dragged"); } @Override @@ -2980,10 +2977,40 @@ public class Grid extends ResizeComposite implements @SuppressWarnings("unchecked") Column[] array = reordered.toArray(new Column[reordered .size()]); + transferCellFocusOnDrop(); setColumnOrder(array); } // else no reordering } + private void transferCellFocusOnDrop() { + final Cell focusedCell = cellFocusHandler.getFocusedCell(); + if (focusedCell != null) { + final int focusedCellColumnIndex = focusedCell.getColumn(); + final int focusedRowIndex = focusedCell.getRow(); + final int draggedColumnIndex = eventCell.getColumnIndex(); + // transfer focus if it was effected by the new column order + // FIXME if the dragged column is partly outside of the view + // port and the focused cell is +-1 of the dragged column, the + // grid scrolls to the right end. maybe fixed when the automatic + // scroll handling is implemented? + final RowContainer rowContainer = escalator + .findRowContainer(focusedCell.getElement()); + if (focusedCellColumnIndex == draggedColumnIndex) { + // move with the dragged column + cellFocusHandler.setCellFocus(focusedRowIndex, + latestColumnDropIndex, rowContainer); + } else if (latestColumnDropIndex <= focusedCellColumnIndex + && draggedColumnIndex > focusedCellColumnIndex) { + cellFocusHandler.setCellFocus(focusedRowIndex, + focusedCellColumnIndex + 1, rowContainer); + } else if (latestColumnDropIndex >= focusedCellColumnIndex + && draggedColumnIndex < focusedCellColumnIndex) { + cellFocusHandler.setCellFocus(focusedRowIndex, + focusedCellColumnIndex - 1, rowContainer); + } + } + } + @Override public void onDragCancel() { // cancel next click so that we may prevent column sorting if diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeaturesTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeaturesTest.java index 0e339ec0ae..e7fbc14d89 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeaturesTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeaturesTest.java @@ -15,6 +15,9 @@ */ package com.vaadin.tests.components.grid.basicfeatures; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + import java.util.ArrayList; import java.util.List; @@ -132,4 +135,45 @@ public abstract class GridBasicFeaturesTest extends MultiBrowserTest { testUrl = testUrl.replace("?&", "?"); driver.get(testUrl); } + + protected void focusCell(int row, int column) { + getGridElement().getCell(row, column).click(); + } + + protected void assertColumnHeaderOrder(int... indices) { + List headers = getGridHeaderRowCells(); + for (int i = 0; i < indices.length; i++) { + assertColumnHeader("Column " + indices[i], headers.get(i)); + } + } + + protected void assertColumnHeader(String expectedHeaderCaption, + TestBenchElement testBenchElement) { + assertEquals(expectedHeaderCaption.toLowerCase(), testBenchElement + .getText().toLowerCase()); + } + + protected WebElement getDefaultColumnHeader(int index) { + List headerRowCells = getGridHeaderRowCells(); + return headerRowCells.get(index); + } + + protected void dragDefaultColumnHeader(int draggedColumnHeaderIndex, + int onTopOfColumnHeaderIndex, int xOffsetFromColumnTopLeftCorner) { + new Actions(getDriver()) + .clickAndHold(getDefaultColumnHeader(draggedColumnHeaderIndex)) + .moveToElement( + getDefaultColumnHeader(onTopOfColumnHeaderIndex), + xOffsetFromColumnTopLeftCorner, 0).release().perform(); + } + + protected void assertColumnIsSorted(int index) { + WebElement columnHeader = getDefaultColumnHeader(index); + assertTrue(columnHeader.getAttribute("class").contains("sort")); + } + + protected void assertFocusedCell(int row, int column) { + assertTrue(getGridElement().getCell(row, column).getAttribute("class") + .contains("focused")); + } } diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderEventTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderEventTest.java deleted file mode 100644 index eda064284c..0000000000 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderEventTest.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.tests.components.grid.basicfeatures; - -import static org.junit.Assert.assertEquals; - -import org.junit.Before; -import org.junit.Test; -import org.openqa.selenium.By; -import org.openqa.selenium.WebElement; - -import com.vaadin.testbench.elements.GridElement.GridCellElement; -import com.vaadin.tests.annotations.TestCategory; - -/** - * - * @since - * @author Vaadin Ltd - */ -@TestCategory("grid") -public class GridColumnReorderEventTest extends GridBasicClientFeaturesTest { - - @Before - public void before() { - openTestURL(); - } - - @Test - public void columnReorderEventTriggered() { - final int firstIndex = 3; - final int secondIndex = 4; - final String firstHeaderText = getGridElement().getHeaderCell(0, - firstIndex).getText(); - final String secondHeaderText = getGridElement().getHeaderCell(0, - secondIndex).getText(); - selectMenuPath("Component", "Internals", "Listeners", - "Add ColumnReorder listener"); - selectMenuPath("Component", "Columns", "Column " + secondIndex, - "Move column left"); - // columns 3 and 4 should have swapped to 4 and 3 - GridCellElement headerCell = getGridElement().getHeaderCell(0, - firstIndex); - assertEquals(secondHeaderText, headerCell.getText()); - headerCell = getGridElement().getHeaderCell(0, secondIndex); - assertEquals(firstHeaderText, headerCell.getText()); - - // the reorder event should have typed the order to this label - WebElement columnReorderElement = findElement(By.id("columnreorder")); - int eventIndex = Integer.parseInt(columnReorderElement - .getAttribute("columns")); - assertEquals(1, eventIndex); - - // trigger another event - selectMenuPath("Component", "Columns", "Column " + secondIndex, - "Move column left"); - columnReorderElement = findElement(By.id("columnreorder")); - eventIndex = Integer.parseInt(columnReorderElement - .getAttribute("columns")); - assertEquals(2, eventIndex); - } -} diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderTest.java new file mode 100644 index 0000000000..9f43c39b1e --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderTest.java @@ -0,0 +1,233 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.components.grid.basicfeatures; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Before; +import org.junit.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.interactions.Actions; + +import com.vaadin.testbench.elements.GridElement.GridCellElement; +import com.vaadin.tests.annotations.TestCategory; + +/** + * + * @since + * @author Vaadin Ltd + */ +@TestCategory("grid") +public class GridColumnReorderTest extends GridBasicClientFeaturesTest { + + @Before + public void before() { + openTestURL(); + } + + @Test + public void columnReorderEventTriggered() { + final int firstIndex = 3; + final int secondIndex = 4; + final String firstHeaderText = getGridElement().getHeaderCell(0, + firstIndex).getText(); + final String secondHeaderText = getGridElement().getHeaderCell(0, + secondIndex).getText(); + selectMenuPath("Component", "Internals", "Listeners", + "Add ColumnReorder listener"); + selectMenuPath("Component", "Columns", "Column " + secondIndex, + "Move column left"); + // columns 3 and 4 should have swapped to 4 and 3 + GridCellElement headerCell = getGridElement().getHeaderCell(0, + firstIndex); + assertEquals(secondHeaderText, headerCell.getText()); + headerCell = getGridElement().getHeaderCell(0, secondIndex); + assertEquals(firstHeaderText, headerCell.getText()); + + // the reorder event should have typed the order to this label + WebElement columnReorderElement = findElement(By.id("columnreorder")); + int eventIndex = Integer.parseInt(columnReorderElement + .getAttribute("columns")); + assertEquals(1, eventIndex); + + // trigger another event + selectMenuPath("Component", "Columns", "Column " + secondIndex, + "Move column left"); + columnReorderElement = findElement(By.id("columnreorder")); + eventIndex = Integer.parseInt(columnReorderElement + .getAttribute("columns")); + assertEquals(2, eventIndex); + } + + @Test + public void testColumnReorder_onReorder_columnReorderEventTriggered() { + final int firstIndex = 3; + final int secondIndex = 4; + final String firstHeaderText = getGridElement().getHeaderCell(0, + firstIndex).getText(); + final String secondHeaderText = getGridElement().getHeaderCell(0, + secondIndex).getText(); + selectMenuPath("Component", "Internals", "Listeners", + "Add ColumnReorder listener"); + selectMenuPath("Component", "Columns", "Column " + secondIndex, + "Move column left"); + // columns 3 and 4 should have swapped to 4 and 3 + GridCellElement headerCell = getGridElement().getHeaderCell(0, + firstIndex); + assertEquals(secondHeaderText, headerCell.getText()); + headerCell = getGridElement().getHeaderCell(0, secondIndex); + assertEquals(firstHeaderText, headerCell.getText()); + + // the reorder event should have typed the order to this label + WebElement columnReorderElement = findElement(By.id("columnreorder")); + int eventIndex = Integer.parseInt(columnReorderElement + .getAttribute("columns")); + assertEquals(1, eventIndex); + + // trigger another event + selectMenuPath("Component", "Columns", "Column " + secondIndex, + "Move column left"); + columnReorderElement = findElement(By.id("columnreorder")); + eventIndex = Integer.parseInt(columnReorderElement + .getAttribute("columns")); + assertEquals(2, eventIndex); + } + + @Test + public void testColumnReorder_draggingSortedColumn_sortIndicatorShownOnDraggedElement() { + // given + toggleColumnReorder(); + toggleSortableColumn(0); + sortColumn(0); + + // when + startDragButDontDropOnDefaultColumnHeader(0); + + // then + WebElement draggedElement = getDraggedHeaderElement(); + assertTrue(draggedElement.getAttribute("class").contains("sort")); + } + + @Test + public void testColumnReorder_draggingSortedColumn_sortStays() { + // given + toggleColumnReorder(); + toggleSortableColumn(0); + sortColumn(0); + + // when + dragDefaultColumnHeader(0, 2, 10); + + // then + assertColumnIsSorted(1); + } + + @Test + public void testColumnReorder_draggingFocusedHeader_focusShownOnDraggedElement() { + // given + toggleColumnReorder(); + focusDefaultHeader(0); + + // when + startDragButDontDropOnDefaultColumnHeader(0); + + // then + WebElement draggedElement = getDraggedHeaderElement(); + assertTrue(draggedElement.getAttribute("class").contains("focused")); + } + + @Test + public void testColumnReorder_draggingFocusedHeader_focusIsKeptOnHeader() { + // given + toggleColumnReorder(); + focusDefaultHeader(0); + + // when + dragDefaultColumnHeader(0, 3, 10); + + // then + WebElement defaultColumnHeader = getDefaultColumnHeader(2); + String attribute = defaultColumnHeader.getAttribute("class"); + assertTrue(attribute.contains("focused")); + } + + @Test + public void testColumnReorder_draggingFocusedCellColumn_focusIsKeptOnCell() { + // given + toggleColumnReorder(); + focusCell(2, 2); + + // when + dragDefaultColumnHeader(2, 0, 10); + + // then + assertFocusedCell(2, 0); + } + + @Test + public void testColumnReorder_dragColumnFromRightToLeftOfFocusedCellColumn_focusIsKept() { + // given + toggleColumnReorder(); + focusCell(1, 3); + + // when + dragDefaultColumnHeader(4, 1, 10); + + // then + assertFocusedCell(1, 4); + } + + @Test + public void testColumnReorder_dragColumnFromLeftToRightOfFocusedCellColumn_focusIsKept() { + // given + toggleColumnReorder(); + focusCell(4, 2); + + // when + dragDefaultColumnHeader(0, 4, 10); + + // then + assertFocusedCell(4, 1); + } + + private void toggleColumnReorder() { + selectMenuPath("Component", "State", "Column Reordering"); + } + + private void toggleSortableColumn(int index) { + selectMenuPath("Component", "Columns", "Column " + index, "Sortable"); + } + + private void startDragButDontDropOnDefaultColumnHeader(int index) { + new Actions(getDriver()) + .clickAndHold(getGridHeaderRowCells().get(index)) + .moveByOffset(100, 0).perform(); + } + + private void sortColumn(int index) { + getGridHeaderRowCells().get(index).click(); + } + + private void focusDefaultHeader(int index) { + getGridHeaderRowCells().get(index).click(); + } + + private WebElement getDraggedHeaderElement() { + return findElement(By.className("dragged-column-header")); + } +} diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridColumnReorderTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridColumnReorderTest.java index 2f00071351..2cc8610209 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridColumnReorderTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridColumnReorderTest.java @@ -15,18 +15,12 @@ */ package com.vaadin.tests.components.grid.basicfeatures.server; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import java.util.List; - import org.junit.Before; import org.junit.Test; -import org.openqa.selenium.WebElement; -import org.openqa.selenium.interactions.Actions; -import com.vaadin.testbench.TestBenchElement; import com.vaadin.tests.components.grid.basicfeatures.GridBasicFeaturesTest; /** @@ -212,30 +206,4 @@ public class GridColumnReorderTest extends GridBasicFeaturesTest { assertFalse(logRow.contains("Columns reordered")); } - private void assertColumnHeaderOrder(int... indices) { - List headers = getGridHeaderRowCells(); - for (int i = 0; i < indices.length; i++) { - assertColumnHeader("Column " + indices[i], headers.get(i)); - } - } - - private void assertColumnHeader(String expectedHeaderCaption, - TestBenchElement testBenchElement) { - assertEquals(expectedHeaderCaption.toLowerCase(), testBenchElement - .getText().toLowerCase()); - } - - private WebElement getDefaultColumnHeader(int index) { - List headerRowCells = getGridHeaderRowCells(); - return headerRowCells.get(index); - } - - private void dragDefaultColumnHeader(int draggedColumnHeaderIndex, - int onTopOfColumnHeaderIndex, int xOffsetFromColumnTopLeftCorner) { - new Actions(getDriver()) - .clickAndHold(getDefaultColumnHeader(draggedColumnHeaderIndex)) - .moveToElement( - getDefaultColumnHeader(onTopOfColumnHeaderIndex), - xOffsetFromColumnTopLeftCorner, 0).release().perform(); - } } -- cgit v1.2.3 From fa50ea28870436c5dedb459e364d24f14f7ab001 Mon Sep 17 00:00:00 2001 From: Pekka Hyvönen Date: Thu, 26 Feb 2015 09:44:23 +0200 Subject: Support column reordering from all header rows. (#16643) Change-Id: I78a4a50b011576a026518a58c36822a383979986 --- WebContent/VAADIN/themes/base/grid/grid.scss | 3 ++- client/src/com/vaadin/client/widgets/Grid.java | 14 +++++----- .../grid/basicfeatures/GridBasicFeaturesTest.java | 19 +++++++++++-- .../server/GridColumnReorderTest.java | 31 ++++++++++++++++++++++ 4 files changed, 58 insertions(+), 9 deletions(-) (limited to 'WebContent/VAADIN/themes') diff --git a/WebContent/VAADIN/themes/base/grid/grid.scss b/WebContent/VAADIN/themes/base/grid/grid.scss index 35024e27c0..20f8478885 100644 --- a/WebContent/VAADIN/themes/base/grid/grid.scss +++ b/WebContent/VAADIN/themes/base/grid/grid.scss @@ -55,11 +55,12 @@ $v-grid-editor-background-color: $v-grid-row-background-color !default; .#{$primaryStyleName} .header-drag-table { border-spacing: 0; + position: relative; table-layout: fixed; width: inherit; // a decent default fallback .#{$primaryStyleName}-header { - + position: absolute; > .#{$primaryStyleName}-cell { border: $v-grid-border; margin-top: -10px; diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index fc208843e4..ebc2fcf97e 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -2930,6 +2930,13 @@ public class Grid extends ResizeComposite implements tableHeader.setClassName(escalator.getHeader().getElement() .getClassName()); dropMarker.setClassName(getStylePrimaryName() + "-drop-marker"); + int topOffset = 0; + for (int i = 0; i < eventCell.getRowIndex(); i++) { + topOffset += escalator.getHeader().getRowElement(i) + .getFirstChildElement().getOffsetHeight(); + } + tableHeader.getStyle().setTop(topOffset, Unit.PX); + getElement().appendChild(table); } @@ -5375,17 +5382,12 @@ public class Grid extends ResizeComposite implements private boolean handleHeaderCellDragStartEvent(Event event, RowContainer container) { - if (!columnReorderingAllowed) { + if (!isColumnReorderingAllowed()) { return false; } if (container != escalator.getHeader()) { return false; } - // for now only support reordering of default row as the only row - if (!getHeader().getRow(eventCell.getRowIndex()).isDefault() - || getHeader().getRowCount() != 1) { - return false; - } if (eventCell.getColumnIndex() < escalator.getColumnConfiguration() .getFrozenColumnCount()) { return false; diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeaturesTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeaturesTest.java index d387bbe22f..13f32fd2a2 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeaturesTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeaturesTest.java @@ -30,6 +30,7 @@ import org.openqa.selenium.remote.DesiredCapabilities; import com.vaadin.testbench.TestBenchElement; import com.vaadin.testbench.elements.GridElement; +import com.vaadin.testbench.elements.GridElement.GridCellElement; import com.vaadin.tests.annotations.TestCategory; import com.vaadin.tests.tb3.MultiBrowserTest; @@ -158,8 +159,9 @@ public abstract class GridBasicFeaturesTest extends MultiBrowserTest { .getText().toLowerCase()); } - protected WebElement getDefaultColumnHeader(int index) { - List headerRowCells = getGridHeaderRowCells(); + protected GridCellElement getDefaultColumnHeader(int index) { + List headerRowCells = getGridElement().getHeaderCells( + 0); return headerRowCells.get(index); } @@ -172,6 +174,19 @@ public abstract class GridBasicFeaturesTest extends MultiBrowserTest { xOffsetFromColumnTopLeftCorner, 0).release().perform(); } + protected void dragAndDropColumnHeader(int headerRow, + int draggedColumnHeaderIndex, int onTopOfColumnHeaderIndex, + int xOffsetFromColumnTopLeftCorner) { + new Actions(getDriver()) + .clickAndHold( + getGridElement().getHeaderCell(headerRow, + draggedColumnHeaderIndex)) + .moveToElement( + getGridElement().getHeaderCell(headerRow, + onTopOfColumnHeaderIndex), + xOffsetFromColumnTopLeftCorner, 0).release().perform(); + } + protected void assertColumnIsSorted(int index) { WebElement columnHeader = getDefaultColumnHeader(index); assertTrue(columnHeader.getAttribute("class").contains("sort")); diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridColumnReorderTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridColumnReorderTest.java index 7ab66aad95..ae0ab62aab 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridColumnReorderTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridColumnReorderTest.java @@ -212,6 +212,37 @@ public class GridColumnReorderTest extends GridBasicFeaturesTest { assertColumnHeaderOrder(0, 2, 1, 3); } + @Test + public void testColumnReordering_twoHeaderRows_dndReorderingPossibleFromFirstRow() { + // given + openTestURL(); + toggleColumnReordering(); + selectMenuPath("Component", "Header", "Append row"); + assertColumnHeaderOrder(0, 1, 2, 3); + + // when + dragAndDropColumnHeader(0, 0, 2, 100); + + // then + assertColumnHeaderOrder(1, 2, 0, 3); + } + + @Test + public void testColumnReordering_twoHeaderRows_dndReorderingPossibleFromSecondRow() { + // given + openTestURL(); + toggleColumnReordering(); + selectMenuPath("Component", "Header", "Append row"); + assertColumnHeaderOrder(0, 1, 2, 3); + + // when + // when + dragAndDropColumnHeader(1, 0, 2, 100); + + // then + assertColumnHeaderOrder(1, 2, 0, 3); + } + private void toggleColumnReordering() { selectMenuPath(COLUMN_REORDERING_PATH); } -- cgit v1.2.3