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 --- .../vaadin/client/ui/dd/DragAndDropHandler.java | 240 +++++++++++++++++++++ client/src/com/vaadin/client/widgets/Grid.java | 209 +++++++++++++++++- 2 files changed, 448 insertions(+), 1 deletion(-) create mode 100644 client/src/com/vaadin/client/ui/dd/DragAndDropHandler.java (limited to 'client/src') diff --git a/client/src/com/vaadin/client/ui/dd/DragAndDropHandler.java b/client/src/com/vaadin/client/ui/dd/DragAndDropHandler.java new file mode 100644 index 0000000000..c8b651bd4e --- /dev/null +++ b/client/src/com/vaadin/client/ui/dd/DragAndDropHandler.java @@ -0,0 +1,240 @@ +/* + * 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.ui.dd; + +import java.util.logging.Logger; + +import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.event.dom.client.KeyCodes; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.Event.NativePreviewEvent; +import com.google.gwt.user.client.Event.NativePreviewHandler; +import com.google.gwt.user.client.ui.RootPanel; +import com.vaadin.client.WidgetUtil; +import com.vaadin.client.widgets.Grid; + +/** + * A simple event handler for elements that can be drag and dropped. Loosely + * based on {@link VDragAndDropManager}, but without any Vaadin related stuff. + * Properly handles drag start, cancel and end. For example, used in + * {@link Grid} column header reordering. + *

+ * The showing of the dragged element, drag hints and reacting to drop/cancel is + * delegated to {@link DragAndDropCallback} implementation. + * + * @since + * @author Vaadin Ltd + */ +public class DragAndDropHandler { + + /** + * Callback interface for drag and drop. + */ + public interface DragAndDropCallback { + /** + * Called when the drag has started. + */ + void showDragElement(); + + /** + * Called on drag. + * + * @param event + * the event related to the drag + */ + void updateDragElement(NativePreviewEvent event); + + /** + * Called when the has ended on a drop or cancel. + */ + void removeDragElement(); + + /** + * Called when the drag has ended on a drop. + */ + void onDrop(); + + /** + * Called when the drag has been canceled before drop. + */ + void onDragCancel(); + } + + private HandlerRegistration dragStartNativePreviewHandlerRegistration; + private HandlerRegistration dragHandlerRegistration; + + private boolean dragging; + + private DragAndDropCallback callback; + + private final NativePreviewHandler dragHandler = new NativePreviewHandler() { + + @Override + public void onPreviewNativeEvent(NativePreviewEvent event) { + if (dragging) { + final int typeInt = event.getTypeInt(); + switch (typeInt) { + case Event.ONKEYDOWN: + int keyCode = event.getNativeEvent().getKeyCode(); + if (keyCode == KeyCodes.KEY_ESCAPE) { + // end drag if ESC is hit + cancelDrag(event); + } + break; + case Event.ONMOUSEMOVE: + case Event.ONTOUCHMOVE: + callback.updateDragElement(event); + // prevent text selection on IE + event.getNativeEvent().preventDefault(); + break; + case Event.ONTOUCHCANCEL: + cancelDrag(event); + break; + case Event.ONTOUCHEND: + /* Avoid simulated event on drag end */ + event.getNativeEvent().preventDefault(); + //$FALL-THROUGH$ + case Event.ONMOUSEUP: + callback.updateDragElement(event); + callback.onDrop(); + stopDrag(); + event.cancel(); + break; + default: + break; + } + } else { + stopDrag(); + } + } + + private void cancelDrag(NativePreviewEvent event) { + callback.onDragCancel(); + callback.removeDragElement(); + stopDrag(); + event.cancel(); + event.getNativeEvent().preventDefault(); + } + + }; + + private static Logger getLogger() { + return Logger.getLogger(DragAndDropHandler.class.getName()); + } + + /** + * This method can be called to trigger drag and drop on any grid element + * that can be dragged and dropped. + * + * @param dragStartingEvent + * the drag triggering event, usually a {@link Event#ONMOUSEDOWN} + * or {@link Event#ONTOUCHSTART} event on the draggable element + * + * @param callback + * the callback that will handle actual drag and drop related + * operations + */ + public void onDragStartOnDraggableElement( + final NativeEvent dragStartingEvent, + final DragAndDropCallback callback) { + dragStartNativePreviewHandlerRegistration = Event + .addNativePreviewHandler(new NativePreviewHandler() { + + private int startX = WidgetUtil + .getTouchOrMouseClientX(dragStartingEvent); + private int startY = WidgetUtil + .getTouchOrMouseClientY(dragStartingEvent); + + @Override + public void onPreviewNativeEvent(NativePreviewEvent event) { + final int typeInt = event.getTypeInt(); + if (typeInt == -1 + && event.getNativeEvent().getType() + .toLowerCase().contains("pointer")) { + /* + * Ignore PointerEvents since IE10 and IE11 send + * also MouseEvents for backwards compatibility. + */ + return; + } + switch (typeInt) { + case Event.ONMOUSEOVER: + case Event.ONMOUSEOUT: + // we don't care + break; + case Event.ONKEYDOWN: + case Event.ONKEYPRESS: + case Event.ONKEYUP: + case Event.ONBLUR: + case Event.ONFOCUS: + // don't cancel possible drag start + break; + case Event.ONMOUSEMOVE: + case Event.ONTOUCHMOVE: + int currentX = WidgetUtil + .getTouchOrMouseClientX(event + .getNativeEvent()); + int currentY = WidgetUtil + .getTouchOrMouseClientY(event + .getNativeEvent()); + if (Math.abs(startX - currentX) > 3 + || Math.abs(startY - currentY) > 3) { + removeNativePreviewHandlerRegistration(); + startDrag(event, callback); + } + break; + default: + // on any other events, clean up this preview + // listener + removeNativePreviewHandlerRegistration(); + break; + } + } + }); + } + + private void startDrag(NativePreviewEvent event, + DragAndDropCallback callback) { + dragging = true; + // just capture something to prevent text selection in IE + Event.setCapture(RootPanel.getBodyElement()); + this.callback = callback; + dragHandlerRegistration = Event.addNativePreviewHandler(dragHandler); + callback.showDragElement(); + callback.updateDragElement(event); + } + + private void stopDrag() { + dragging = false; + if (dragHandlerRegistration != null) { + dragHandlerRegistration.removeHandler(); + dragHandlerRegistration = null; + } + Event.releaseCapture(RootPanel.getBodyElement()); + if (callback != null) { + callback.removeDragElement(); + callback = null; + } + } + + private void removeNativePreviewHandlerRegistration() { + if (dragStartNativePreviewHandlerRegistration != null) { + dragStartNativePreviewHandlerRegistration.removeHandler(); + dragStartNativePreviewHandlerRegistration = null; + } + } +} diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index 71962d6953..b0e18cc2ab 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -36,6 +36,8 @@ 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.NativeEvent; +import com.google.gwt.dom.client.NodeList; import com.google.gwt.dom.client.Style; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.dom.client.TableCellElement; @@ -53,6 +55,8 @@ import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.touch.client.Point; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.Event.NativePreviewEvent; +import com.google.gwt.user.client.Event.NativePreviewHandler; import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.CheckBox; @@ -68,6 +72,8 @@ import com.vaadin.client.renderers.ComplexRenderer; import com.vaadin.client.renderers.Renderer; import com.vaadin.client.renderers.WidgetRenderer; import com.vaadin.client.ui.SubPartAware; +import com.vaadin.client.ui.dd.DragAndDropHandler; +import com.vaadin.client.ui.dd.DragAndDropHandler.DragAndDropCallback; import com.vaadin.client.widget.escalator.Cell; import com.vaadin.client.widget.escalator.ColumnConfiguration; import com.vaadin.client.widget.escalator.EscalatorUpdater; @@ -2853,6 +2859,153 @@ public class Grid extends ResizeComposite implements private boolean enabled = true; + private boolean columnReorderingAllowed; + + private DragAndDropHandler dndHandler = new DragAndDropHandler(); + + private DragAndDropCallback headerCellDndCallback = new DragAndDropCallback() { + + /** + * Elements for displaying the dragged column(s) and drop marker + * properly + */ + private Element table; + private Element tableHeader; + /** Marks the column drop location */ + private Element dropMarker; + /** A copy of the dragged column(s), moves with cursor. */ + private Element dragElement; + /** Tracks index of the column whose left side the drop would occur */ + private int latestColumnDropIndex; + /** + * Makes sure that drag cancel doesn't cause anything unwanted like sort + */ + private HandlerRegistration columnSortPreventRegistration; + + private void initHeaderDragElementDOM() { + if (table == null) { + tableHeader = DOM.createTHead(); + dropMarker = DOM.createDiv(); + tableHeader.appendChild(dropMarker); + table = DOM.createTable(); + table.appendChild(tableHeader); + table.setClassName("header-drag-table"); + } + // update the style names on each run in case primary name has been + // modified + tableHeader.setClassName(escalator.getHeader().getElement() + .getClassName()); + dropMarker.setClassName(getStylePrimaryName() + "-drop-marker"); + getElement().appendChild(table); + } + + @Override + public void updateDragElement(NativePreviewEvent event) { + int clientX = WidgetUtil.getTouchOrMouseClientX(event + .getNativeEvent()); + resolveDragElementHorizontalPosition(clientX); + updateDragDropMarker(clientX); + } + + private void updateDragDropMarker(final int clientX) { + RowContainer header = escalator.getHeader(); + NodeList cells = header.getRowElement( + eventCell.getRowIndex()).getCells(); + double dropMarkerLeft = 0 - escalator.getScrollLeft(); + latestColumnDropIndex = 0; + for (int i = 0; i < cells.getLength(); i++, latestColumnDropIndex++) { + TableCellElement cellElement = cells.getItem(i); + int cellX = cellElement.getAbsoluteLeft(); + int cellWidth = cellElement.getOffsetWidth(); + if (clientX < cellX || clientX < cellX + (cellWidth / 2)) { + break; + } else { + dropMarkerLeft += cellWidth; + } + } + if (dropMarkerLeft > header.getElement().getOffsetWidth() + || dropMarkerLeft < 0) { + dropMarkerLeft = -10000000; + } + 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())); + left -= dragElement.getClientWidth() / 2; + dragElement.getStyle().setLeft(left, Unit.PX); + } + + @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) + 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(); + } + + @Override + public void removeDragElement() { + table.removeFromParent(); + dragElement.removeFromParent(); + } + + @Override + public void onDrop() { + final int draggedColumnIndex = eventCell.getColumnIndex(); + if (latestColumnDropIndex != draggedColumnIndex + && latestColumnDropIndex != (draggedColumnIndex + 1)) { + List> columns = getColumns(); + List> reordered = new ArrayList>( + columns); + Column moved = reordered.remove(draggedColumnIndex); + if (draggedColumnIndex < latestColumnDropIndex) { + latestColumnDropIndex--; + } + reordered.add(latestColumnDropIndex, moved); + @SuppressWarnings("unchecked") + Column[] array = reordered.toArray(new Column[reordered + .size()]); + setColumnOrder(array); + } // else no reordering + } + + @Override + public void onDragCancel() { + // cancel next click so that we may prevent column sorting if + // mouse was released on top of the dragged cell + if (columnSortPreventRegistration == null) { + columnSortPreventRegistration = Event + .addNativePreviewHandler(new NativePreviewHandler() { + + @Override + public void onPreviewNativeEvent( + NativePreviewEvent event) { + if (event.getTypeInt() == Event.ONCLICK) { + event.cancel(); + event.getNativeEvent().preventDefault(); + columnSortPreventRegistration + .removeHandler(); + columnSortPreventRegistration = null; + } + } + }); + } + } + }; + /** * Enumeration for easy setting of selection mode. */ @@ -4649,7 +4802,7 @@ public class Grid extends ResizeComposite implements + getColumnCount() + ")"); } - this.frozenColumnCount = numberOfColumns; + frozenColumnCount = numberOfColumns; updateFrozenColumns(); } @@ -4949,6 +5102,10 @@ public class Grid extends ResizeComposite implements if (!isElementInChildWidget(e)) { + if (handleHeaderCellDragStartEvent(event, container)) { + return; + } + // Sorting through header Click / KeyUp if (handleHeaderDefaultRowEvent(event, container)) { return; @@ -5097,6 +5254,35 @@ public class Grid extends ResizeComposite implements return true; } + private boolean handleHeaderCellDragStartEvent(Event event, + RowContainer container) { + if (!columnReorderingAllowed) { + 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; + } + if (event.getTypeInt() == Event.ONMOUSEDOWN + && event.getButton() == NativeEvent.BUTTON_LEFT + || event.getTypeInt() == Event.ONTOUCHSTART) { + dndHandler.onDragStartOnDraggableElement(event, + headerCellDndCallback); + event.preventDefault(); + event.stopPropagation(); + return true; + } + return false; + } + private Point rowEventTouchStartingPoint; private CellStyleGenerator cellStyleGenerator; private RowStyleGenerator rowStyleGenerator; @@ -5905,6 +6091,27 @@ public class Grid extends ResizeComposite implements || autoColumnWidthsRecalculator.isScheduled(); } + /** + * Returns whether columns can be reordered with drag and drop. + * + * @since + * @return true if columns can be reordered, false otherwise + */ + public boolean isColumnReorderingAllowed() { + return columnReorderingAllowed; + } + + /** + * Sets whether column reordering with drag and drop is allowed or not. + * + * @since + * @param columnReorderingAllowed + * specifies whether column reordering is allowed + */ + public void setColumnReorderingAllowed(boolean columnReorderingAllowed) { + this.columnReorderingAllowed = columnReorderingAllowed; + } + /** * Sets a new column order for the grid. All columns which are not ordered * here will remain in the order they were before as the last columns of -- 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 'client/src') 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 c72967ba77c2bc0fcc56df499f5a75f6c7ca2c07 Mon Sep 17 00:00:00 2001 From: Henrik Paul Date: Thu, 12 Feb 2015 15:04:31 +0200 Subject: Adds SpacerUpdater support for Escalator (#16644) Change-Id: I9d73a3fc36e94e36c35859a4ae456002c75df2f5 --- .../client/widget/escalator/EscalatorUpdater.java | 2 - .../com/vaadin/client/widget/escalator/Row.java | 1 - .../client/widget/escalator/RowContainer.java | 27 +++ .../com/vaadin/client/widget/escalator/Spacer.java | 41 +++++ .../client/widget/escalator/SpacerUpdater.java | 62 +++++++ .../src/com/vaadin/client/widgets/Escalator.java | 190 +++++++++++---------- .../grid/EscalatorBasicClientFeaturesWidget.java | 30 ++++ .../widgetset/client/grid/EscalatorProxy.java | 12 ++ 8 files changed, 275 insertions(+), 90 deletions(-) create mode 100644 client/src/com/vaadin/client/widget/escalator/Spacer.java create mode 100644 client/src/com/vaadin/client/widget/escalator/SpacerUpdater.java (limited to 'client/src') diff --git a/client/src/com/vaadin/client/widget/escalator/EscalatorUpdater.java b/client/src/com/vaadin/client/widget/escalator/EscalatorUpdater.java index 6109c5e51d..54507a7650 100644 --- a/client/src/com/vaadin/client/widget/escalator/EscalatorUpdater.java +++ b/client/src/com/vaadin/client/widget/escalator/EscalatorUpdater.java @@ -16,8 +16,6 @@ package com.vaadin.client.widget.escalator; -import com.vaadin.client.widgets.Escalator; - /** * An interface that allows client code to define how a certain row in Escalator * will be displayed. The contents of an escalator's header, body and footer are diff --git a/client/src/com/vaadin/client/widget/escalator/Row.java b/client/src/com/vaadin/client/widget/escalator/Row.java index bcb3e163e4..fa89853120 100644 --- a/client/src/com/vaadin/client/widget/escalator/Row.java +++ b/client/src/com/vaadin/client/widget/escalator/Row.java @@ -17,7 +17,6 @@ package com.vaadin.client.widget.escalator; import com.google.gwt.dom.client.TableRowElement; -import com.vaadin.client.widgets.Escalator; /** * A representation of a row in an {@link Escalator}. diff --git a/client/src/com/vaadin/client/widget/escalator/RowContainer.java b/client/src/com/vaadin/client/widget/escalator/RowContainer.java index 5cc58cb47e..ea56c25062 100644 --- a/client/src/com/vaadin/client/widget/escalator/RowContainer.java +++ b/client/src/com/vaadin/client/widget/escalator/RowContainer.java @@ -44,6 +44,7 @@ public interface RowContainer { * @see com.vaadin.client.widgets.Escalator#getBody() */ public interface BodyRowContainer extends RowContainer { + /** * Marks a spacer and its height. *

@@ -61,6 +62,32 @@ public interface RowContainer { */ void setSpacer(int rowIndex, double height) throws IllegalArgumentException; + + /** + * Sets a new spacer updater. + *

+ * Spacers that are currently visible will be updated, i.e. + * {@link SpacerUpdater#destroy(Spacer) destroyed} with the previous + * one, and {@link SpacerUpdater#init(Spacer) initialized} with the new + * one. + * + * @param spacerUpdater + * the new spacer updater + * @throws IllegalArgumentException + * if {@code spacerUpdater} is {@code null} + */ + void setSpacerUpdater(SpacerUpdater spacerUpdater) + throws IllegalArgumentException; + + /** + * Gets the spacer updater currently in use. + *

+ * {@link SpacerUpdater#NULL} is the default. + * + * @return the spacer updater currently in use. Never null + */ + SpacerUpdater getSpacerUpdater(); + } /** diff --git a/client/src/com/vaadin/client/widget/escalator/Spacer.java b/client/src/com/vaadin/client/widget/escalator/Spacer.java new file mode 100644 index 0000000000..0b0a2b257f --- /dev/null +++ b/client/src/com/vaadin/client/widget/escalator/Spacer.java @@ -0,0 +1,41 @@ +/* + * 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.escalator; + +import com.google.gwt.dom.client.Element; + +/** + * A representation of a spacer element in a {@link SpacerContainer}. + * + * @since + * @author Vaadin Ltd + */ +public interface Spacer { + + /** + * Gets the root element for the spacer content. + * + * @return the root element for the spacer content + */ + Element getElement(); + + /** + * Gets the row index. + * + * @return the row index. + */ + int getRow(); +} diff --git a/client/src/com/vaadin/client/widget/escalator/SpacerUpdater.java b/client/src/com/vaadin/client/widget/escalator/SpacerUpdater.java new file mode 100644 index 0000000000..18f53db507 --- /dev/null +++ b/client/src/com/vaadin/client/widget/escalator/SpacerUpdater.java @@ -0,0 +1,62 @@ +/* + * 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.escalator; + +/** + * An interface that handles the display of content for spacers. + *

+ * The updater is responsible for making sure all elements are properly + * constructed and cleaned up. + * + * @since + * @author Vaadin Ltd + * @see Spacer + * @see SpacerContainer + */ +public interface SpacerUpdater { + + /** A spacer updater that does nothing. */ + public static final SpacerUpdater NULL = new SpacerUpdater() { + @Override + public void init(Spacer spacer) { + // NOOP + } + + @Override + public void destroy(Spacer spacer) { + // NOOP + } + }; + + /** + * Called whenever a spacer should be initialized with content. + * + * @param spacer + * the spacer reference that should be initialized + */ + void init(Spacer spacer); + + /** + * Called whenever a spacer should be cleaned. + *

+ * The structure to clean up is the same that has been constructed by + * {@link #init(Spacer)}. + * + * @param spacer + * the spacer reference that should be destroyed + */ + void destroy(Spacer spacer); +} diff --git a/client/src/com/vaadin/client/widgets/Escalator.java b/client/src/com/vaadin/client/widgets/Escalator.java index 2536356c94..6c6998277f 100644 --- a/client/src/com/vaadin/client/widgets/Escalator.java +++ b/client/src/com/vaadin/client/widgets/Escalator.java @@ -74,6 +74,8 @@ import com.vaadin.client.widget.escalator.RowVisibilityChangeHandler; import com.vaadin.client.widget.escalator.ScrollbarBundle; import com.vaadin.client.widget.escalator.ScrollbarBundle.HorizontalScrollbarBundle; import com.vaadin.client.widget.escalator.ScrollbarBundle.VerticalScrollbarBundle; +import com.vaadin.client.widget.escalator.Spacer; +import com.vaadin.client.widget.escalator.SpacerUpdater; import com.vaadin.client.widget.grid.events.ScrollEvent; import com.vaadin.client.widget.grid.events.ScrollHandler; import com.vaadin.client.widgets.Escalator.JsniUtil.TouchHandlerBundle; @@ -3636,6 +3638,17 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker throws IllegalArgumentException { spacerContainer.setSpacer(rowIndex, height); } + + @Override + public void setSpacerUpdater(SpacerUpdater spacerUpdater) + throws IllegalArgumentException { + spacerContainer.setSpacerUpdater(spacerUpdater); + } + + @Override + public SpacerUpdater getSpacerUpdater() { + return spacerContainer.getSpacerUpdater(); + } } private class ColumnConfigurationImpl implements ColumnConfiguration { @@ -4116,41 +4129,21 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker /** 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 final class SpacerImpl implements Spacer { private TableCellElement spacerElement; private TableRowElement root; + private final int rowIndex; - 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"; + public SpacerImpl(int rowIndex, double height) { + this.rowIndex = rowIndex; + // Build DOM structure root = TableRowElement.as(DOM.createTR()); spacerElement = TableCellElement.as(DOM.createTD()); root.appendChild(spacerElement); - spacerElement.setInnerText("IAMA SPACER, AMA"); - initElements(height); - } + root.setPropertyInt(SPACER_LOGICAL_ROW_PROPERTY, rowIndex); - private void initElements(double height) { + // Configure DOM structure setHeight(height); root.getStyle().setWidth(100, Unit.PCT); @@ -4161,15 +4154,12 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker 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; + public TableRowElement getRootElement() { + return root; + } - 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 setPosition(double x, double y) { + positions.set(root, x, y); } public void setStylePrimaryName(String style) { @@ -4182,10 +4172,20 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker "spacer's height changed, but pushing rows out of " + "the way not implemented yet"); } + + @Override + public Element getElement() { + return spacerElement; + } + + @Override + public int getRow() { + return rowIndex; + } } - private final TreeMap rowIndexToHeight = new TreeMap(); - private final TreeMap rowIndexToSpacerElement = new TreeMap(); + private final TreeMap rowIndexToSpacer = new TreeMap(); + private SpacerUpdater spacerUpdater = SpacerUpdater.NULL; public void setSpacer(int rowIndex, double height) throws IllegalArgumentException { @@ -4196,65 +4196,42 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker } if (height >= 0) { - insertOrUpdateSpacer(rowIndex, height); + if (!spacerExists(rowIndex)) { + insertNewSpacer(rowIndex, height); + } else { + updateExistingSpacer(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; + return rowIndexToSpacer.containsKey(Integer.valueOf(rowIndex)); } @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); + private void insertNewSpacer(int rowIndex, double height) { + + SpacerImpl spacer = new SpacerImpl(rowIndex, height); + + rowIndexToSpacer.put(rowIndex, spacer); spacer.setPosition(0, getSpacerTop(rowIndex)); + + TableRowElement spacerRoot = spacer.getRootElement(); spacerRoot.getStyle().setWidth( columnConfiguration.calculateRowWidth(), Unit.PX); body.getElement().appendChild(spacerRoot); + + initSpacerContent(spacer); } - private void updateSpacer(int rowIndex, double newHeight) { + private void updateExistingSpacer(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 SpacerImpl getSpacer(int rowIndex) { + return rowIndexToSpacer.get(Integer.valueOf(rowIndex)); } private double getSpacerTop(int rowIndex) { @@ -4282,32 +4259,71 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker @SuppressWarnings("boxing") private void removeSpacer(int rowIndex) { - Spacer spacer = getSpacer(rowIndex); + SpacerImpl spacer = getSpacer(rowIndex); // fix DOM + destroySpacerContent(spacer); spacer.setHeight(0); // resets row offsets spacer.getRootElement().removeFromParent(); // fix bookkeeping - rowIndexToHeight.remove(rowIndex); - rowIndexToSpacerElement.remove(rowIndex); + rowIndexToSpacer.remove(rowIndex); } public void setStylePrimaryName(String style) { - for (TableRowElement spacerRoot : rowIndexToSpacerElement.values()) { - Spacer spacer = new Spacer(); - spacer.setRootElement(spacerRoot); + for (SpacerImpl spacer : rowIndexToSpacer.values()) { spacer.setStylePrimaryName(style); } } + + public void setSpacerUpdater(SpacerUpdater spacerUpdater) + throws IllegalArgumentException { + if (spacerUpdater == null) { + throw new IllegalArgumentException( + "spacer updater cannot be null"); + } + + destroySpacerContent(rowIndexToSpacer.values()); + this.spacerUpdater = spacerUpdater; + initSpacerContent(rowIndexToSpacer.values()); + } + + public SpacerUpdater getSpacerUpdater() { + return spacerUpdater; + } + + private void destroySpacerContent(Iterable spacers) { + for (SpacerImpl spacer : spacers) { + destroySpacerContent(spacer); + } + } + + private void destroySpacerContent(SpacerImpl spacer) { + assert getElement().isOrHasChild(spacer.getRootElement()) : "Spacer's root element somehow got detached from Escalator before detaching"; + assert getElement().isOrHasChild(spacer.getElement()) : "Spacer element somehow got detached from Escalator before detaching"; + spacerUpdater.destroy(spacer); + assert getElement().isOrHasChild(spacer.getRootElement()) : "Spacer's root element somehow got detached from Escalator before detaching"; + assert getElement().isOrHasChild(spacer.getElement()) : "Spacer element somehow got detached from Escalator before detaching"; + } + + private void initSpacerContent(Iterable spacers) { + for (SpacerImpl spacer : spacers) { + initSpacerContent(spacer); + } + } + + private void initSpacerContent(SpacerImpl spacer) { + assert getElement().isOrHasChild(spacer.getRootElement()) : "Spacer's root element somehow got detached from Escalator before attaching"; + assert getElement().isOrHasChild(spacer.getElement()) : "Spacer element somehow got detached from Escalator before attaching"; + spacerUpdater.init(spacer); + assert getElement().isOrHasChild(spacer.getRootElement()) : "Spacer's root element somehow got detached from Escalator during attaching"; + assert getElement().isOrHasChild(spacer.getElement()) : "Spacer element somehow got detached from Escalator during attaching"; + } } 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(); 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 0905cde4eb..37c0865e35 100644 --- a/uitest/src/com/vaadin/tests/widgetset/client/grid/EscalatorBasicClientFeaturesWidget.java +++ b/uitest/src/com/vaadin/tests/widgetset/client/grid/EscalatorBasicClientFeaturesWidget.java @@ -12,6 +12,9 @@ import com.vaadin.client.widget.escalator.EscalatorUpdater; import com.vaadin.client.widget.escalator.FlyweightCell; import com.vaadin.client.widget.escalator.Row; import com.vaadin.client.widget.escalator.RowContainer; +import com.vaadin.client.widget.escalator.RowContainer.BodyRowContainer; +import com.vaadin.client.widget.escalator.Spacer; +import com.vaadin.client.widget.escalator.SpacerUpdater; import com.vaadin.client.widgets.Escalator; public class EscalatorBasicClientFeaturesWidget extends @@ -615,6 +618,33 @@ public class EscalatorBasicClientFeaturesWidget extends private void createSpacerMenu() { String[] menupath = { "Features", "Spacers" }; + + addMenuCommand("Swap Spacer Updater", new ScheduledCommand() { + private final SpacerUpdater CUSTOM = new SpacerUpdater() { + @Override + public void destroy(Spacer spacer) { + spacer.getElement().setInnerText(""); + } + + @Override + public void init(Spacer spacer) { + spacer.getElement().setInnerText( + "Spacer for row " + spacer.getRow()); + } + }; + + @Override + public void execute() { + BodyRowContainer body = escalator.getBody(); + + if (body.getSpacerUpdater().equals(SpacerUpdater.NULL)) { + body.setSpacerUpdater(CUSTOM); + } else { + body.setSpacerUpdater(SpacerUpdater.NULL); + } + } + }, menupath); + createSpacersMenuForRow(1, menupath); createSpacersMenuForRow(50, menupath); } 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 63286af86f..e7ebcfeb51 100644 --- a/uitest/src/com/vaadin/tests/widgetset/client/grid/EscalatorProxy.java +++ b/uitest/src/com/vaadin/tests/widgetset/client/grid/EscalatorProxy.java @@ -25,6 +25,7 @@ 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.widget.escalator.SpacerUpdater; import com.vaadin.client.widgets.Escalator; import com.vaadin.tests.widgetset.client.grid.EscalatorBasicClientFeaturesWidget.LogWidget; @@ -112,6 +113,17 @@ public class EscalatorProxy extends Escalator { throws IllegalArgumentException { rowContainer.setSpacer(rowIndex, height); } + + @Override + public void setSpacerUpdater(SpacerUpdater spacerUpdater) + throws IllegalArgumentException { + rowContainer.setSpacerUpdater(spacerUpdater); + } + + @Override + public SpacerUpdater getSpacerUpdater() { + return rowContainer.getSpacerUpdater(); + } } private class RowContainerProxy implements RowContainer { -- cgit v1.2.3 From a7db9821bac2e41601dd4fd0f169d8a94d6e282e Mon Sep 17 00:00:00 2001 From: Pekka Hyvönen Date: Fri, 13 Feb 2015 15:53:50 +0200 Subject: Client side event for column reordering in Grid. #16643 Change-Id: I40bb42c7a5a3e9f92051c03dc5fe91b83cd88db7 --- .../widget/grid/events/ColumnReorderEvent.java | 52 +++++++++++++++ .../widget/grid/events/ColumnReorderHandler.java | 41 ++++++++++++ client/src/com/vaadin/client/widgets/Grid.java | 18 ++++++ .../basicfeatures/GridColumnReorderEventTest.java | 74 ++++++++++++++++++++++ .../client/grid/GridBasicClientFeaturesWidget.java | 45 +++++++++++++ 5 files changed, 230 insertions(+) create mode 100644 client/src/com/vaadin/client/widget/grid/events/ColumnReorderEvent.java create mode 100644 client/src/com/vaadin/client/widget/grid/events/ColumnReorderHandler.java create mode 100644 uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderEventTest.java (limited to 'client/src') diff --git a/client/src/com/vaadin/client/widget/grid/events/ColumnReorderEvent.java b/client/src/com/vaadin/client/widget/grid/events/ColumnReorderEvent.java new file mode 100644 index 0000000000..4890ed063d --- /dev/null +++ b/client/src/com/vaadin/client/widget/grid/events/ColumnReorderEvent.java @@ -0,0 +1,52 @@ +/* + * 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.events; + +import com.google.gwt.event.shared.GwtEvent; + +/** + * An event for notifying that the columns in the Grid's columns have been + * reordered. + * + * @param + * The row type of the grid. The row type is the POJO type from where + * the data is retrieved into the column cells. + * @since + * @author Vaadin Ltd + */ +public class ColumnReorderEvent extends GwtEvent> { + + /** + * Handler type. + */ + private final static Type> TYPE = new Type>(); + + public static final Type> getType() { + return TYPE; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public Type> getAssociatedType() { + return (Type) TYPE; + } + + @Override + protected void dispatch(ColumnReorderHandler handler) { + handler.onColumnReorder(this); + } + +} diff --git a/client/src/com/vaadin/client/widget/grid/events/ColumnReorderHandler.java b/client/src/com/vaadin/client/widget/grid/events/ColumnReorderHandler.java new file mode 100644 index 0000000000..e4f258088f --- /dev/null +++ b/client/src/com/vaadin/client/widget/grid/events/ColumnReorderHandler.java @@ -0,0 +1,41 @@ +/* + * 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.events; + +import com.google.gwt.event.shared.EventHandler; + +/** + * Handler for a Grid column reorder event, called when the Grid's columns has + * been reordered. + * + * @param + * The row type of the grid. The row type is the POJO type from where + * the data is retrieved into the column cells. + * @since + * @author Vaadin Ltd + */ +public interface ColumnReorderHandler extends EventHandler { + + /** + * A column reorder event, fired by Grid when the columns of the Grid have + * been reordered. + * + * @since + * @param event + * column reorder event + */ + public void onColumnReorder(ColumnReorderEvent event); +} diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index b0e18cc2ab..643f223068 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -100,6 +100,8 @@ import com.vaadin.client.widget.grid.events.BodyDoubleClickHandler; import com.vaadin.client.widget.grid.events.BodyKeyDownHandler; import com.vaadin.client.widget.grid.events.BodyKeyPressHandler; import com.vaadin.client.widget.grid.events.BodyKeyUpHandler; +import com.vaadin.client.widget.grid.events.ColumnReorderEvent; +import com.vaadin.client.widget.grid.events.ColumnReorderHandler; import com.vaadin.client.widget.grid.events.FooterClickHandler; import com.vaadin.client.widget.grid.events.FooterDoubleClickHandler; import com.vaadin.client.widget.grid.events.FooterKeyDownHandler; @@ -6040,6 +6042,20 @@ public class Grid extends ResizeComposite implements return addHandler(handler, doubleClickEvent.getAssociatedType()); } + /** + * Register a column reorder handler to this Grid. The event for this + * handler is fired when the Grid's columns are reordered. + * + * @since + * @param handler + * the handler for the event + * @return the registration for the event + */ + public HandlerRegistration addColumnReorderHandler( + ColumnReorderHandler handler) { + return addHandler(handler, ColumnReorderEvent.getType()); + } + /** * Apply sorting to data source. */ @@ -6163,6 +6179,8 @@ public class Grid extends ResizeComposite implements for (FooterRow row : footer.getRows()) { row.calculateColspans(); } + + fireEvent(new ColumnReorderEvent()); } /** diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderEventTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderEventTest.java new file mode 100644 index 0000000000..eda064284c --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderEventTest.java @@ -0,0 +1,74 @@ +/* + * 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/widgetset/client/grid/GridBasicClientFeaturesWidget.java b/uitest/src/com/vaadin/tests/widgetset/client/grid/GridBasicClientFeaturesWidget.java index bfe8b3cf4d..265a93fb59 100644 --- a/uitest/src/com/vaadin/tests/widgetset/client/grid/GridBasicClientFeaturesWidget.java +++ b/uitest/src/com/vaadin/tests/widgetset/client/grid/GridBasicClientFeaturesWidget.java @@ -55,6 +55,8 @@ import com.vaadin.client.widget.grid.datasources.ListSorter; import com.vaadin.client.widget.grid.events.BodyKeyDownHandler; import com.vaadin.client.widget.grid.events.BodyKeyPressHandler; import com.vaadin.client.widget.grid.events.BodyKeyUpHandler; +import com.vaadin.client.widget.grid.events.ColumnReorderEvent; +import com.vaadin.client.widget.grid.events.ColumnReorderHandler; import com.vaadin.client.widget.grid.events.FooterKeyDownHandler; import com.vaadin.client.widget.grid.events.FooterKeyPressHandler; import com.vaadin.client.widget.grid.events.FooterKeyUpHandler; @@ -444,6 +446,31 @@ public class GridBasicClientFeaturesWidget extends }); } }, listenersPath); + addMenuCommand("Add ColumnReorder listener", new ScheduledCommand() { + private HandlerRegistration columnReorderHandler = null; + + @Override + public void execute() { + if (columnReorderHandler != null) { + return; + } + final Label columnOrderLabel = new Label(); + columnOrderLabel.getElement().setId("columnreorder"); + addLineEnd(columnOrderLabel, 300); + columnReorderHandler = grid + .addColumnReorderHandler(new ColumnReorderHandler>() { + + private int eventIndex = 0; + + @Override + public void onColumnReorder( + ColumnReorderEvent> event) { + columnOrderLabel.getElement().setAttribute( + "columns", "" + (++eventIndex)); + } + }); + } + }, listenersPath); } private void createStateMenu() { @@ -768,6 +795,24 @@ public class GridBasicClientFeaturesWidget extends }); } }, "Component", "Columns", "Column " + i); + addMenuCommand("Move column left", new ScheduledCommand() { + + @SuppressWarnings("unchecked") + @Override + public void execute() { + List>> cols = grid.getColumns(); + ArrayList reordered = new ArrayList(cols); + if (index == 0) { + Column> col = reordered.remove(0); + reordered.add(col); + } else { + Column> col = reordered.remove(index); + reordered.add(index - 1, col); + } + grid.setColumnOrder(reordered.toArray(new Column[reordered + .size()])); + } + }, "Component", "Columns", "Column " + i); } } -- 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 'client/src') 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 add05e15fcd82d73f4c46245dbe6d0999437cdec Mon Sep 17 00:00:00 2001 From: Henrik Paul Date: Tue, 17 Feb 2015 11:51:11 +0200 Subject: Modifies GridElement to support details (#16644) Grid's SubPartAware logic was refactored, splitting it into both Grid and Escalator. Also adds tests for grid details rows. Change-Id: I4876a8a9a397eea35526e15f7e447c69b0d96983 --- .../src/com/vaadin/client/widgets/Escalator.java | 240 ++++++++++++++++++++- client/src/com/vaadin/client/widgets/Grid.java | 169 ++++----------- .../com/vaadin/testbench/elements/GridElement.java | 38 ++-- .../grid/basicfeatures/client/GridDetailsTest.java | 99 +++++++++ 4 files changed, 410 insertions(+), 136 deletions(-) create mode 100644 uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridDetailsTest.java (limited to 'client/src') diff --git a/client/src/com/vaadin/client/widgets/Escalator.java b/client/src/com/vaadin/client/widgets/Escalator.java index 6c6998277f..567262c6b2 100644 --- a/client/src/com/vaadin/client/widgets/Escalator.java +++ b/client/src/com/vaadin/client/widgets/Escalator.java @@ -16,6 +16,7 @@ package com.vaadin.client.widgets; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; @@ -57,6 +58,7 @@ import com.vaadin.client.BrowserInfo; import com.vaadin.client.DeferredWorker; import com.vaadin.client.Profiler; import com.vaadin.client.WidgetUtil; +import com.vaadin.client.ui.SubPartAware; import com.vaadin.client.widget.escalator.Cell; import com.vaadin.client.widget.escalator.ColumnConfiguration; import com.vaadin.client.widget.escalator.EscalatorUpdater; @@ -267,7 +269,8 @@ abstract class JsniWorkaround { * @since 7.4 * @author Vaadin Ltd */ -public class Escalator extends Widget implements RequiresResize, DeferredWorker { +public class Escalator extends Widget implements RequiresResize, + DeferredWorker, SubPartAware { // todo comments legend /* @@ -4319,6 +4322,26 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker assert getElement().isOrHasChild(spacer.getRootElement()) : "Spacer's root element somehow got detached from Escalator during attaching"; assert getElement().isOrHasChild(spacer.getElement()) : "Spacer element somehow got detached from Escalator during attaching"; } + + public String getSubPartName(Element subElement) { + for (SpacerImpl spacer : rowIndexToSpacer.values()) { + if (spacer.getRootElement().isOrHasChild(subElement)) { + return "spacer[" + spacer.getRow() + "]"; + } + } + return null; + } + + public Element getSubPartElement(int index) { + getLogger().warning("SpacerContainer.getSubPartElement " + index); + + SpacerImpl spacer = rowIndexToSpacer.get(Integer.valueOf(index)); + if (spacer != null) { + return spacer.getElement(); + } else { + return null; + } + } } private class ElementPositionBookkeeper { @@ -4347,6 +4370,50 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker } } + public static class SubPartArguments { + private String type; + private int[] indices; + + private SubPartArguments(String type, int[] indices) { + /* + * The constructor is private so that no third party would by + * mistake start using this parsing scheme, since it's not official + * by TestBench (yet?). + */ + + this.type = type; + this.indices = indices; + } + + public String getType() { + return type; + } + + public int getIndicesLength() { + return indices.length; + } + + public int getIndex(int i) { + return indices[i]; + } + + public int[] getIndices() { + return Arrays.copyOf(indices, indices.length); + } + + static SubPartArguments create(String subPart) { + String[] splitArgs = subPart.split("\\["); + String type = splitArgs[0]; + int[] indices = new int[splitArgs.length - 1]; + for (int i = 0; i < indices.length; ++i) { + String tmp = splitArgs[i + 1]; + indices[i] = Integer + .parseInt(tmp.substring(0, tmp.length() - 1)); + } + return new SubPartArguments(type, indices); + } + } + // abs(atan(y/x))*(180/PI) = n deg, x = 1, solve y /** * The solution to @@ -5295,4 +5362,175 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker int to = (int) Math.ceil(body.heightOfSection); return Range.between(from, to); } + + @Override + @SuppressWarnings("deprecation") + public com.google.gwt.user.client.Element getSubPartElement(String subPart) { + SubPartArguments args = parseSubPartArguments(subPart); + + Element tableStructureElement = getSubPartElementTableStructure(args); + if (tableStructureElement != null) { + return DOM.asOld(tableStructureElement); + } + + Element spacerElement = getSubPartElementSpacer(args); + if (spacerElement != null) { + return DOM.asOld(spacerElement); + } + + return null; + } + + private Element getSubPartElementTableStructure(SubPartArguments args) { + + String type = args.getType(); + int[] indices = args.getIndices(); + + // Get correct RowContainer for type from Escalator + RowContainer container = null; + if (type.equalsIgnoreCase("header")) { + container = getHeader(); + } else if (type.equalsIgnoreCase("cell")) { + // If wanted row is not visible, we need to scroll there. + Range visibleRowRange = getVisibleRowRange(); + if (indices.length > 0 && !visibleRowRange.contains(indices[0])) { + try { + scrollToRow(indices[0], ScrollDestination.ANY, 0); + } catch (IllegalArgumentException e) { + getLogger().log(Level.SEVERE, e.getMessage()); + } + // Scrolling causes a lazy loading event. No element can + // currently be retrieved. + return null; + } + container = getBody(); + } else if (type.equalsIgnoreCase("footer")) { + container = getFooter(); + } + + if (null != container) { + if (indices.length == 0) { + // No indexing. Just return the wanted container element + return container.getElement(); + } else { + try { + return getSubPart(container, indices); + } catch (Exception e) { + getLogger().log(Level.SEVERE, e.getMessage()); + } + } + } + return null; + } + + private Element getSubPart(RowContainer container, int[] indices) { + Element targetElement = container.getRowElement(indices[0]); + + // Scroll wanted column to view if able + if (indices.length > 1 && targetElement != null) { + if (getColumnConfiguration().getFrozenColumnCount() <= indices[1]) { + scrollToColumn(indices[1], ScrollDestination.ANY, 0); + } + + targetElement = getCellFromRow(TableRowElement.as(targetElement), + indices[1]); + + for (int i = 2; i < indices.length && targetElement != null; ++i) { + targetElement = (Element) targetElement.getChild(indices[i]); + } + } + + return targetElement; + } + + private static Element getCellFromRow(TableRowElement rowElement, int index) { + int childCount = rowElement.getCells().getLength(); + if (index < 0 || index >= childCount) { + return null; + } + + TableCellElement currentCell = null; + boolean indexInColspan = false; + int i = 0; + + while (!indexInColspan) { + currentCell = rowElement.getCells().getItem(i); + + // Calculate if this is the cell we are looking for + int colSpan = currentCell.getColSpan(); + indexInColspan = index < colSpan + i; + + // Increment by colspan to skip over hidden cells + i += colSpan; + } + return currentCell; + } + + private Element getSubPartElementSpacer(SubPartArguments args) { + if ("spacer".equals(args.getType()) && args.getIndicesLength() == 1) { + return body.spacerContainer.getSubPartElement(args.getIndex(0)); + } else { + return null; + } + } + + @Override + @SuppressWarnings("deprecation") + public String getSubPartName(com.google.gwt.user.client.Element subElement) { + + /* + * The spacer check needs to be before table structure check, because + * (for now) the table structure will take spacer elements into account + * as well, when it shouldn't. + */ + + String spacer = getSubPartNameSpacer(subElement); + if (spacer != null) { + return spacer; + } + + String tableStructure = getSubPartNameTableStructure(subElement); + if (tableStructure != null) { + return tableStructure; + } + + return null; + } + + private String getSubPartNameTableStructure(Element subElement) { + + List containers = Arrays.asList(getHeader(), getBody(), + getFooter()); + List containerType = Arrays.asList("header", "cell", "footer"); + + for (int i = 0; i < containers.size(); ++i) { + RowContainer container = containers.get(i); + boolean containerRow = (subElement.getTagName().equalsIgnoreCase( + "tr") && subElement.getParentElement() == container + .getElement()); + if (containerRow) { + /* + * Wanted SubPart is row that is a child of containers root to + * get indices, we use a cell that is a child of this row + */ + subElement = subElement.getFirstChildElement(); + } + + Cell cell = container.getCell(subElement); + if (cell != null) { + // Skip the column index if subElement was a child of root + return containerType.get(i) + "[" + cell.getRow() + + (containerRow ? "]" : "][" + cell.getColumn() + "]"); + } + } + return null; + } + + private String getSubPartNameSpacer(Element subElement) { + return body.spacerContainer.getSubPartName(subElement); + } + + public static SubPartArguments parseSubPartArguments(String subPart) { + return SubPartArguments.create(subPart); + } } diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index 31984f7d5b..7890b13904 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -130,6 +130,7 @@ import com.vaadin.client.widget.grid.sort.SortEvent; import com.vaadin.client.widget.grid.sort.SortHandler; import com.vaadin.client.widget.grid.sort.SortOrder; import com.vaadin.client.widgets.Escalator.AbstractRowContainer; +import com.vaadin.client.widgets.Escalator.SubPartArguments; import com.vaadin.client.widgets.Grid.Editor.State; import com.vaadin.shared.data.sort.SortDirection; import com.vaadin.shared.ui.grid.GridConstants; @@ -5250,153 +5251,77 @@ public class Grid extends ResizeComposite implements } @Override + @SuppressWarnings("deprecation") public com.google.gwt.user.client.Element getSubPartElement(String subPart) { - // Parse SubPart string to type and indices - String[] splitArgs = subPart.split("\\["); - - String type = splitArgs[0]; - int[] indices = new int[splitArgs.length - 1]; - for (int i = 0; i < indices.length; ++i) { - String tmp = splitArgs[i + 1]; - indices[i] = Integer.parseInt(tmp.substring(0, tmp.length() - 1)); - } - - // Get correct RowContainer for type from Escalator - RowContainer container = null; - if (type.equalsIgnoreCase("header")) { - container = escalator.getHeader(); - } else if (type.equalsIgnoreCase("cell")) { - // If wanted row is not visible, we need to scroll there. - Range visibleRowRange = escalator.getVisibleRowRange(); - if (indices.length > 0 && !visibleRowRange.contains(indices[0])) { - try { - scrollToRow(indices[0]); - } catch (IllegalArgumentException e) { - getLogger().log(Level.SEVERE, e.getMessage()); - } - // Scrolling causes a lazy loading event. No element can - // currently be retrieved. - return null; - } - container = escalator.getBody(); - } else if (type.equalsIgnoreCase("footer")) { - container = escalator.getFooter(); - } else if (type.equalsIgnoreCase("editor")) { - if (editor.getState() != State.ACTIVE) { - // Editor is not there. - return null; - } - if (indices.length == 0) { - return DOM.asOld(editor.editorOverlay); - } else if (indices.length == 1 && indices[0] < columns.size()) { - escalator.scrollToColumn(indices[0], ScrollDestination.ANY, 0); - return editor.getWidget(columns.get(indices[0])).getElement(); - } else { - return null; - } + Element subPartElement = escalator.getSubPartElement(subPart + .replaceFirst("^details\\[", "spacer[")); + if (subPartElement != null) { + return DOM.asOld(subPartElement); } - if (null != container) { - if (indices.length == 0) { - // No indexing. Just return the wanted container element - return DOM.asOld(container.getElement()); - } else { - try { - return DOM.asOld(getSubPart(container, indices)); - } catch (Exception e) { - getLogger().log(Level.SEVERE, e.getMessage()); - } - } + SubPartArguments args = Escalator.parseSubPartArguments(subPart); + + Element editor = getSubPartElementEditor(args); + if (editor != null) { + return DOM.asOld(editor); } + return null; } - private Element getSubPart(RowContainer container, int[] indices) { - Element targetElement = container.getRowElement(indices[0]); - - // Scroll wanted column to view if able - if (indices.length > 1 && targetElement != null) { - if (escalator.getColumnConfiguration().getFrozenColumnCount() <= indices[1]) { - escalator.scrollToColumn(indices[1], ScrollDestination.ANY, 0); - } + private Element getSubPartElementEditor(SubPartArguments args) { - targetElement = getCellFromRow(TableRowElement.as(targetElement), - indices[1]); - - for (int i = 2; i < indices.length && targetElement != null; ++i) { - targetElement = (Element) targetElement.getChild(indices[i]); - } + if (!args.getType().equalsIgnoreCase("editor") + || editor.getState() != State.ACTIVE) { + return null; } - return targetElement; - } - - private Element getCellFromRow(TableRowElement rowElement, int index) { - int childCount = rowElement.getCells().getLength(); - if (index < 0 || index >= childCount) { - return null; + if (args.getIndicesLength() == 0) { + return editor.editorOverlay; + } else if (args.getIndicesLength() == 1 + && args.getIndex(0) < columns.size()) { + escalator + .scrollToColumn(args.getIndex(0), ScrollDestination.ANY, 0); + return editor.getWidget(columns.get(args.getIndex(0))).getElement(); } - TableCellElement currentCell = null; - boolean indexInColspan = false; - int i = 0; + return null; + } - while (!indexInColspan) { - currentCell = rowElement.getCells().getItem(i); + @Override + @SuppressWarnings("deprecation") + public String getSubPartName(com.google.gwt.user.client.Element subElement) { - // Calculate if this is the cell we are looking for - int colSpan = currentCell.getColSpan(); - indexInColspan = index < colSpan + i; + String escalatorStructureName = escalator.getSubPartName(subElement); + if (escalatorStructureName != null) { + return escalatorStructureName.replaceFirst("^spacer", "details"); + } - // Increment by colspan to skip over hidden cells - i += colSpan; + String editorName = getSubPartNameEditor(subElement); + if (editorName != null) { + return editorName; } - return currentCell; - } - @Override - public String getSubPartName(com.google.gwt.user.client.Element subElement) { - // Containers and matching SubPart types - List containers = Arrays.asList(escalator.getHeader(), - escalator.getBody(), escalator.getFooter()); - List containerType = Arrays.asList("header", "cell", "footer"); + return null; + } - for (int i = 0; i < containers.size(); ++i) { - RowContainer container = containers.get(i); - boolean containerRow = (subElement.getTagName().equalsIgnoreCase( - "tr") && subElement.getParentElement() == container - .getElement()); - if (containerRow) { - // Wanted SubPart is row that is a child of containers root - // To get indices, we use a cell that is a child of this row - subElement = DOM.asOld(subElement.getFirstChildElement()); - } + private String getSubPartNameEditor(Element subElement) { - Cell cell = container.getCell(subElement); - if (cell != null) { - // Skip the column index if subElement was a child of root - return containerType.get(i) + "[" + cell.getRow() - + (containerRow ? "]" : "][" + cell.getColumn() + "]"); - } + if (editor.getState() != State.ACTIVE + || !editor.editorOverlay.isOrHasChild(subElement)) { + return null; } - // Check if subelement is part of editor. - if (editor.getState() == State.ACTIVE) { - if (editor.editorOverlay.isOrHasChild(subElement)) { - int i = 0; - for (Column column : columns) { - if (editor.getWidget(column).getElement() - .isOrHasChild(subElement)) { - return "editor[" + i + "]"; - } - ++i; - } - return "editor"; + int i = 0; + for (Column column : columns) { + if (editor.getWidget(column).getElement().isOrHasChild(subElement)) { + return "editor[" + i + "]"; } + ++i; } - return null; + return "editor"; } private void setSelectColumnRenderer( diff --git a/uitest/src/com/vaadin/testbench/elements/GridElement.java b/uitest/src/com/vaadin/testbench/elements/GridElement.java index 3753696855..ff7343a3d6 100644 --- a/uitest/src/com/vaadin/testbench/elements/GridElement.java +++ b/uitest/src/com/vaadin/testbench/elements/GridElement.java @@ -25,7 +25,7 @@ import com.vaadin.testbench.By; import com.vaadin.testbench.TestBenchElement; /** - * TestBench Element API for Grid + * TestBench Element API for Grid. * * @since * @author Vaadin Ltd @@ -148,7 +148,7 @@ public class GridElement extends AbstractComponentElement { } /** - * Scrolls Grid element so that wanted row is displayed + * Scrolls Grid element so that wanted row is displayed. * * @param index * Target row @@ -251,7 +251,7 @@ public class GridElement extends AbstractComponentElement { } /** - * Get header row count + * Get header row count. * * @return Header row count */ @@ -260,7 +260,7 @@ public class GridElement extends AbstractComponentElement { } /** - * Get footer row count + * Get footer row count. * * @return Footer row count */ @@ -269,7 +269,7 @@ public class GridElement extends AbstractComponentElement { } /** - * Get a header row by index + * Get a header row by index. * * @param rowIndex * Row index @@ -280,7 +280,7 @@ public class GridElement extends AbstractComponentElement { } /** - * Get a footer row by index + * Get a footer row by index. * * @param rowIndex * Row index @@ -291,7 +291,7 @@ public class GridElement extends AbstractComponentElement { } /** - * Get the vertical scroll element + * Get the vertical scroll element. * * @return The element representing the vertical scrollbar */ @@ -301,7 +301,7 @@ public class GridElement extends AbstractComponentElement { } /** - * Get the horizontal scroll element + * Get the horizontal scroll element. * * @return The element representing the horizontal scrollbar */ @@ -311,7 +311,7 @@ public class GridElement extends AbstractComponentElement { } /** - * Get the header element + * Get the header element. * * @return The thead element */ @@ -320,7 +320,7 @@ public class GridElement extends AbstractComponentElement { } /** - * Get the body element + * Get the body element. * * @return the tbody element */ @@ -329,7 +329,7 @@ public class GridElement extends AbstractComponentElement { } /** - * Get the footer element + * Get the footer element. * * @return the tfoot element */ @@ -338,7 +338,7 @@ public class GridElement extends AbstractComponentElement { } /** - * Get the element wrapping the table element + * Get the element wrapping the table element. * * @return The element that wraps the table element */ @@ -353,7 +353,7 @@ public class GridElement extends AbstractComponentElement { } /** - * Helper function to get Grid subparts wrapped correctly + * Helper function to get Grid subparts wrapped correctly. * * @param subPartSelector * SubPart to be used in ComponentLocator @@ -362,4 +362,16 @@ public class GridElement extends AbstractComponentElement { private TestBenchElement getSubPart(String subPartSelector) { return (TestBenchElement) findElement(By.vaadin(subPartSelector)); } + + /** + * Gets the element that contains the details of a row. + * + * @since + * @param rowIndex + * the index of the row for the details + * @return the element that contains the details of a row + */ + public TestBenchElement getDetails(int rowIndex) { + return getSubPart("#details[" + rowIndex + "]"); + } } diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridDetailsTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridDetailsTest.java new file mode 100644 index 0000000000..981a1cc217 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridDetailsTest.java @@ -0,0 +1,99 @@ +/* + * 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.client; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import org.junit.Before; +import org.junit.Test; +import org.openqa.selenium.NoSuchElementException; + +import com.vaadin.testbench.TestBenchElement; +import com.vaadin.tests.components.grid.basicfeatures.GridBasicClientFeaturesTest; + +public class GridDetailsTest extends GridBasicClientFeaturesTest { + + private static final String[] SET_GENERATOR = new String[] { "Component", + "Row details", "Set generator" }; + private static final String[] TOGGLE_DETAILS_FOR_ROW_1 = new String[] { + "Component", "Row details", "Toggle details for row 1" }; + private static final String[] TOGGLE_DETAILS_FOR_ROW_100 = new String[] { + "Component", "Row details", "Toggle details for row 100" }; + + @Before + public void setUp() { + openTestURL(); + } + + @Test(expected = NoSuchElementException.class) + public void noDetailsByDefault() { + assertNull("details for row 1 should not exist at the start", + getGridElement().getDetails(1)); + } + + @Test + public void nullRendererShowsDetailsPlaceholder() { + selectMenuPath(TOGGLE_DETAILS_FOR_ROW_1); + TestBenchElement details = getGridElement().getDetails(1); + assertNotNull("details for row 1 should not exist at the start", + details); + assertTrue("details should've been empty for null renderer", details + .getText().isEmpty()); + } + + @Test + public void applyRendererThenOpenDetails() { + selectMenuPath(SET_GENERATOR); + selectMenuPath(TOGGLE_DETAILS_FOR_ROW_1); + + TestBenchElement details = getGridElement().getDetails(1); + assertTrue("Unexpected details content", + details.getText().startsWith("Row: 1.")); + } + + @Test + public void openDetailsThenAppyRenderer() { + selectMenuPath(TOGGLE_DETAILS_FOR_ROW_1); + selectMenuPath(SET_GENERATOR); + + TestBenchElement details = getGridElement().getDetails(1); + assertTrue("Unexpected details content", + details.getText().startsWith("Row: 1.")); + } + + @Test + public void openHiddenDetailsThenScrollToIt() { + try { + getGridElement().getDetails(100); + fail("details row for 100 was apparently found, while it shouldn't have been."); + } catch (NoSuchElementException e) { + // expected + } + + selectMenuPath(SET_GENERATOR); + selectMenuPath(TOGGLE_DETAILS_FOR_ROW_100); + + // scroll a bit beyond so we see below. + getGridElement().scrollToRow(101); + + TestBenchElement details = getGridElement().getDetails(100); + assertTrue("Unexpected details content", + details.getText().startsWith("Row: 100.")); + } +} -- cgit v1.2.3 From e44ab1ae58b6d622737935b01a2ddacab1661e5a Mon Sep 17 00:00:00 2001 From: Henrik Paul Date: Tue, 17 Feb 2015 17:25:33 +0200 Subject: Adds Widget support for DetailsGenerator (#16644) Change-Id: Ib964b2aa102b8c56e65b0af87bed008248038599 --- .../client/widget/grid/DetailsGenerator.java | 7 +- client/src/com/vaadin/client/widgets/Grid.java | 58 +++++++++++++-- .../grid/basicfeatures/client/GridDetailsTest.java | 85 ++++++++++++++++++++++ .../client/grid/GridBasicClientFeaturesWidget.java | 60 +++++++++++++-- 4 files changed, 195 insertions(+), 15 deletions(-) (limited to 'client/src') diff --git a/client/src/com/vaadin/client/widget/grid/DetailsGenerator.java b/client/src/com/vaadin/client/widget/grid/DetailsGenerator.java index 665362ca8e..264aa4e614 100644 --- a/client/src/com/vaadin/client/widget/grid/DetailsGenerator.java +++ b/client/src/com/vaadin/client/widget/grid/DetailsGenerator.java @@ -15,6 +15,8 @@ */ package com.vaadin.client.widget.grid; +import com.google.gwt.user.client.ui.Widget; + /** * A callback interface for generating details for a particular row in Grid. * @@ -25,7 +27,7 @@ public interface DetailsGenerator { public static final DetailsGenerator NULL = new DetailsGenerator() { @Override - public String getDetails(int rowIndex) { + public Widget getDetails(int rowIndex) { return null; } }; @@ -40,6 +42,5 @@ public interface DetailsGenerator { * 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); + Widget getDetails(int rowIndex); } diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index 7890b13904..c0936bccec 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -2776,19 +2776,44 @@ public class Grid extends ResizeComposite implements } private class GridSpacerUpdater implements SpacerUpdater { + + private final Map elementToWidgetMap = new HashMap(); + @Override public void init(Spacer spacer) { + + assert spacer.getElement().getFirstChild() == null : "The spacer's" + + " element should be empty at this point. (row: " + + spacer.getRow() + ", child: " + + spacer.getElement().getFirstChild() + ")"; + int rowIndex = spacer.getRow(); - String string = detailsGenerator.getDetails(rowIndex); - if (string == null) { - destroy(spacer); + Widget detailsWidget = null; + try { + detailsWidget = detailsGenerator.getDetails(rowIndex); + } catch (Throwable e) { + getLogger().log( + Level.SEVERE, + "Exception while generating details for row " + + rowIndex, e); + } + + if (detailsWidget == null) { + spacer.getElement().removeAllChildren(); escalator.getBody().setSpacer(rowIndex, DETAILS_ROW_INITIAL_HEIGHT); return; } - spacer.getElement().setInnerText(string); + Element element = detailsWidget.getElement(); + spacer.getElement().appendChild(element); + setParent(detailsWidget, Grid.this); + Widget previousWidget = elementToWidgetMap.put(element, + detailsWidget); + + assert previousWidget == null : "Overwrote a pre-existing widget on row " + + rowIndex + " without proper removal first."; /* * Once we have the content properly inside the DOM, we should @@ -2803,7 +2828,30 @@ public class Grid extends ResizeComposite implements @Override public void destroy(Spacer spacer) { - spacer.getElement().setInnerText(""); + + assert getElement().isOrHasChild(spacer.getElement()) : "Trying " + + "to destroy a spacer that is not connected to this " + + "Grid's DOM. (row: " + spacer.getRow() + ", element: " + + spacer.getElement() + ")"; + + Widget detailsWidget = elementToWidgetMap.remove(spacer + .getElement().getFirstChildElement()); + + if (detailsWidget != null) { + /* + * The widget may be null here if the previous generator + * returned a null widget. + */ + + assert spacer.getElement().getFirstChild() != null : "The " + + "details row to destroy did not contain a widget - " + + "probably removed by something else without " + + "permission? (row: " + spacer.getRow() + + ", element: " + spacer.getElement() + ")"; + + setParent(detailsWidget, null); + spacer.getElement().removeAllChildren(); + } } } diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridDetailsTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridDetailsTest.java index 981a1cc217..5e8793c964 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridDetailsTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridDetailsTest.java @@ -15,6 +15,9 @@ */ package com.vaadin.tests.components.grid.basicfeatures.client; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -23,14 +26,22 @@ import static org.junit.Assert.fail; import org.junit.Before; import org.junit.Test; import org.openqa.selenium.NoSuchElementException; +import org.openqa.selenium.WebElement; +import com.vaadin.testbench.By; +import com.vaadin.testbench.ElementQuery; import com.vaadin.testbench.TestBenchElement; +import com.vaadin.testbench.elements.NotificationElement; import com.vaadin.tests.components.grid.basicfeatures.GridBasicClientFeaturesTest; public class GridDetailsTest extends GridBasicClientFeaturesTest { private static final String[] SET_GENERATOR = new String[] { "Component", "Row details", "Set generator" }; + private static final String[] SET_FAULTY_GENERATOR = new String[] { + "Component", "Row details", "Set faulty generator" }; + private static final String[] SET_EMPTY_GENERATOR = new String[] { + "Component", "Row details", "Set empty generator" }; private static final String[] TOGGLE_DETAILS_FOR_ROW_1 = new String[] { "Component", "Row details", "Toggle details for row 1" }; private static final String[] TOGGLE_DETAILS_FOR_ROW_100 = new String[] { @@ -38,6 +49,7 @@ public class GridDetailsTest extends GridBasicClientFeaturesTest { @Before public void setUp() { + setDebug(true); openTestURL(); } @@ -96,4 +108,77 @@ public class GridDetailsTest extends GridBasicClientFeaturesTest { assertTrue("Unexpected details content", details.getText().startsWith("Row: 100.")); } + + @Test + public void errorUpdaterShowsErrorNotification() { + assertFalse("No notifications should've been at the start", + $(NotificationElement.class).exists()); + + selectMenuPath(TOGGLE_DETAILS_FOR_ROW_1); + selectMenuPath(SET_FAULTY_GENERATOR); + + ElementQuery notification = $(NotificationElement.class); + assertTrue("Was expecting an error notification here", + notification.exists()); + notification.first().closeNotification(); + + assertEquals("The error details element should be empty", "", + getGridElement().getDetails(1).getText()); + } + + @Test + public void updaterStillWorksAfterError() { + selectMenuPath(TOGGLE_DETAILS_FOR_ROW_1); + + selectMenuPath(SET_FAULTY_GENERATOR); + $(NotificationElement.class).first().closeNotification(); + selectMenuPath(SET_GENERATOR); + + assertNotEquals( + "New details should've been generated even after error", "", + getGridElement().getDetails(1).getText()); + } + + @Test + public void updaterRendersExpectedWidgets() { + selectMenuPath(SET_GENERATOR); + selectMenuPath(TOGGLE_DETAILS_FOR_ROW_1); + + TestBenchElement detailsElement = getGridElement().getDetails(1); + assertNotNull(detailsElement.findElement(By.className("gwt-Label"))); + assertNotNull(detailsElement.findElement(By.className("gwt-Button"))); + } + + @Test + public void widgetsInUpdaterWorkAsExpected() { + selectMenuPath(SET_GENERATOR); + selectMenuPath(TOGGLE_DETAILS_FOR_ROW_1); + + TestBenchElement detailsElement = getGridElement().getDetails(1); + WebElement button = detailsElement.findElement(By + .className("gwt-Button")); + button.click(); + + WebElement label = detailsElement + .findElement(By.className("gwt-Label")); + assertEquals("clicked", label.getText()); + } + + @Test + public void emptyGenerator() { + selectMenuPath(SET_EMPTY_GENERATOR); + selectMenuPath(TOGGLE_DETAILS_FOR_ROW_1); + + assertEquals("empty generator did not produce an empty details row", + "", getGridElement().getDetails(1).getText()); + } + + @Test(expected = NoSuchElementException.class) + public void removeDetailsRow() { + selectMenuPath(SET_GENERATOR); + selectMenuPath(TOGGLE_DETAILS_FOR_ROW_1); + selectMenuPath(TOGGLE_DETAILS_FOR_ROW_1); + + getGridElement().getDetails(1); + } } 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 7c2ca3eedb..110b14c721 100644 --- a/uitest/src/com/vaadin/tests/widgetset/client/grid/GridBasicClientFeaturesWidget.java +++ b/uitest/src/com/vaadin/tests/widgetset/client/grid/GridBasicClientFeaturesWidget.java @@ -33,9 +33,11 @@ import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.TextBox; +import com.google.gwt.user.client.ui.Widget; import com.vaadin.client.data.DataSource; import com.vaadin.client.data.DataSource.RowHandle; import com.vaadin.client.renderers.DateRenderer; @@ -1224,17 +1226,59 @@ public class GridBasicClientFeaturesWidget extends 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."; + public Widget getDetails(int rowIndex) { + FlowPanel panel = new FlowPanel(); + + final Label label = new Label("Row: " + rowIndex + "."); + Button button = new Button("Button", + new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + label.setText("clicked"); + } + }); + + panel.add(label); + panel.add(button); + return panel; } }); } }, menupath); + + addMenuCommand("Set faulty generator", new ScheduledCommand() { + @Override + public void execute() { + grid.setDetailsGenerator(new DetailsGenerator() { + @Override + public Widget getDetails(int rowIndex) { + throw new RuntimeException("This is by design."); + } + }); + } + }, menupath); + + addMenuCommand("Set empty generator", new ScheduledCommand() { + @Override + public void execute() { + grid.setDetailsGenerator(new DetailsGenerator() { + /* + * While this is functionally equivalent to the NULL + * generator, it's good to be explicit, since the behavior + * isn't strictly tied between them. NULL generator might be + * changed to render something different by default, and an + * empty generator might behave differently also in the + * future. + */ + + @Override + public Widget getDetails(int rowIndex) { + return null; + } + }); + } + }, menupath); + addMenuCommand("Toggle details for row 1", new ScheduledCommand() { boolean visible = false; @@ -1244,6 +1288,7 @@ public class GridBasicClientFeaturesWidget extends grid.setDetailsVisible(1, visible); } }, menupath); + addMenuCommand("Toggle details for row 100", new ScheduledCommand() { boolean visible = false; @@ -1253,5 +1298,6 @@ public class GridBasicClientFeaturesWidget extends grid.setDetailsVisible(100, visible); } }, menupath); + } } -- cgit v1.2.3 From 9b2f51ca9ff48ad9484b8ee67770ab1a58352b35 Mon Sep 17 00:00:00 2001 From: Pekka Hyvönen Date: Wed, 18 Feb 2015 10:05:25 +0200 Subject: Update server side state when columns are reordered. (#16643) Change-Id: I96c65dbb96614a5f5782b747fb8588647211cf4b --- .../vaadin/client/connectors/GridConnector.java | 52 ++++++++++++++++---- server/src/com/vaadin/ui/Grid.java | 40 +++++++++++++++ .../com/vaadin/shared/ui/grid/GridServerRpc.java | 12 +++++ .../grid/basicfeatures/GridBasicFeatures.java | 42 ++++++++++++++++ .../server/GridColumnReorderTest.java | 57 ++++++++++++++++++++++ 5 files changed, 194 insertions(+), 9 deletions(-) (limited to 'client/src') diff --git a/client/src/com/vaadin/client/connectors/GridConnector.java b/client/src/com/vaadin/client/connectors/GridConnector.java index f263b47642..2ae04b2be2 100644 --- a/client/src/com/vaadin/client/connectors/GridConnector.java +++ b/client/src/com/vaadin/client/connectors/GridConnector.java @@ -50,6 +50,8 @@ import com.vaadin.client.widget.grid.RowReference; import com.vaadin.client.widget.grid.RowStyleGenerator; import com.vaadin.client.widget.grid.events.BodyClickHandler; import com.vaadin.client.widget.grid.events.BodyDoubleClickHandler; +import com.vaadin.client.widget.grid.events.ColumnReorderEvent; +import com.vaadin.client.widget.grid.events.ColumnReorderHandler; import com.vaadin.client.widget.grid.events.GridClickEvent; import com.vaadin.client.widget.grid.events.GridDoubleClickEvent; import com.vaadin.client.widget.grid.events.SelectAllEvent; @@ -360,6 +362,24 @@ public class GridConnector extends AbstractHasComponentsConnector implements } } + private ColumnReorderHandler columnReorderHandler = new ColumnReorderHandler() { + + @Override + public void onColumnReorder(ColumnReorderEvent event) { + if (!columnsUpdatedFromState) { + List> columns = getWidget().getColumns(); + final List newColumnOrder = new ArrayList(); + for (Column column : columns) { + newColumnOrder.add(((CustomGridColumn) column).id); + } + getRpcProxy(GridServerRpc.class).columnsReordered( + newColumnOrder, columnOrder); + columnOrder = newColumnOrder; + getState().columnOrder = newColumnOrder; + } + } + }; + /** * Maps a generated column id to a grid column instance */ @@ -370,13 +390,22 @@ public class GridConnector extends AbstractHasComponentsConnector implements private List columnOrder = new ArrayList(); /** - * updateFromState is set to true when {@link #updateSelectionFromState()} - * makes changes to selection. This flag tells the - * {@code internalSelectionChangeHandler} to not send same data straight - * back to server. Said listener sets it back to false when handling that - * event. + * {@link #selectionUpdatedFromState} is set to true when + * {@link #updateSelectionFromState()} makes changes to selection. This flag + * tells the {@code internalSelectionChangeHandler} to not send same data + * straight back to server. Said listener sets it back to false when + * handling that event. + */ + private boolean selectionUpdatedFromState; + + /** + * {@link #columnsUpdatedFromState} is set to true when + * {@link #updateColumnOrderFromState(List)} is updating the column order + * for the widget. This flag tells the {@link #columnReorderHandler} to not + * send same data straight back to server. After updates, listener sets the + * value back to false. */ - private boolean updatedFromState = false; + private boolean columnsUpdatedFromState; private RpcDataSource dataSource; @@ -386,7 +415,7 @@ public class GridConnector extends AbstractHasComponentsConnector implements if (event.isBatchedSelection()) { return; } - if (!updatedFromState) { + if (!selectionUpdatedFromState) { for (JsonObject row : event.getRemoved()) { selectedKeys.remove(dataSource.getRowKey(row)); } @@ -398,7 +427,7 @@ public class GridConnector extends AbstractHasComponentsConnector implements getRpcProxy(GridServerRpc.class).select( new ArrayList(selectedKeys)); } else { - updatedFromState = false; + selectionUpdatedFromState = false; } } }; @@ -496,6 +525,9 @@ public class GridConnector extends AbstractHasComponentsConnector implements }); getWidget().setEditorHandler(new CustomEditorHandler()); + + getWidget().addColumnReorderHandler(columnReorderHandler); + getLayoutManager().registerDependency(this, getWidget().getElement()); layout(); } @@ -589,7 +621,9 @@ public class GridConnector extends AbstractHasComponentsConnector implements columns[i] = columnIdToColumn.get(id); i++; } + columnsUpdatedFromState = true; getWidget().setColumnOrder(columns); + columnsUpdatedFromState = false; columnOrder = stateColumnOrder; } @@ -886,7 +920,7 @@ public class GridConnector extends AbstractHasComponentsConnector implements if (changed) { // At least for now there's no way to send the selected and/or // deselected row data. Some data is only stored as keys - updatedFromState = true; + selectionUpdatedFromState = true; getWidget().fireEvent( new SelectionEvent(getWidget(), (List) null, null, false)); diff --git a/server/src/com/vaadin/ui/Grid.java b/server/src/com/vaadin/ui/Grid.java index 764960606a..72a772713f 100644 --- a/server/src/com/vaadin/ui/Grid.java +++ b/server/src/com/vaadin/ui/Grid.java @@ -18,6 +18,7 @@ package com.vaadin.ui; import java.io.Serializable; import java.lang.reflect.Method; +import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -70,6 +71,7 @@ import com.vaadin.event.SortEvent.SortListener; import com.vaadin.event.SortEvent.SortNotifier; import com.vaadin.server.AbstractClientConnector; import com.vaadin.server.AbstractExtension; +import com.vaadin.server.EncodeResult; import com.vaadin.server.ErrorMessage; import com.vaadin.server.JsonCodec; import com.vaadin.server.KeyMapper; @@ -3088,6 +3090,44 @@ public class Grid extends AbstractComponent implements SelectionNotifier, fireEvent(new ItemClickEvent(Grid.this, item, itemId, propertyId, details)); } + + @Override + public void columnsReordered(List newColumnOrder, + List oldColumnOrder) { + final String diffStateKey = "columnOrder"; + ConnectorTracker connectorTracker = getUI() + .getConnectorTracker(); + JsonObject diffState = connectorTracker.getDiffState(Grid.this); + // discard the change if the columns have been reordered from + // the server side, as the server side is always right + if (getState(false).columnOrder.equals(oldColumnOrder)) { + // Don't mark as dirty since client has the state already + getState(false).columnOrder = newColumnOrder; + // write changes to diffState so that possible reverting the + // column order is sent to client + assert diffState.hasKey(diffStateKey) : "Field name has changed"; + Type type = null; + try { + type = (getState(false).getClass().getDeclaredField( + diffStateKey).getGenericType()); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } catch (SecurityException e) { + e.printStackTrace(); + } + EncodeResult encodeResult = JsonCodec.encode( + getState(false).columnOrder, diffState, type, + connectorTracker); + + diffState.put(diffStateKey, encodeResult.getEncodedValue()); + // TODO fire column reorder event + } else { + // make sure the client is reverted to the order that the + // server thinks it is + diffState.remove(diffStateKey); + markAsDirty(); + } + } }); registerRpc(new EditorServerRpc() { diff --git a/shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java b/shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java index c90a016383..4dec5530aa 100644 --- a/shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java +++ b/shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java @@ -47,4 +47,16 @@ public interface GridServerRpc extends ServerRpc { * mouse event details */ void itemClick(String rowKey, String columnId, MouseEventDetails details); + + /** + * Informs the server that the columns of the Grid have been reordered. + * + * @since + * @param newColumnOrder + * a list of column ids in the new order + * @param oldColumnOrder + * a list of column ids in order before the change + */ + void columnsReordered(List newColumnOrder, + List oldColumnOrder); } diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java index 3a6aca11f2..792ef89d42 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java @@ -248,10 +248,25 @@ public class GridBasicFeatures extends AbstractComponentTest { addFilterActions(); + addInternalActions(); + this.grid = grid; return grid; } + private void addInternalActions() { + createClickAction("Update column order without updating client", + "Internals", new Command() { + @Override + public void execute(Grid grid, Void value, Object data) { + List columns = grid.getColumns(); + grid.setColumnOrder(columns.get(1).getPropertyId(), + columns.get(0).getPropertyId()); + grid.getUI().getConnectorTracker().markClean(grid); + } + }, null); + } + private void addFilterActions() { createClickAction("Column 1 starts with \"(23\"", "Filter", new Command() { @@ -658,6 +673,33 @@ public class GridBasicFeatures extends AbstractComponentTest { } } }, null, c); + createClickAction("Move left", getColumnProperty(c), + new Command() { + + @Override + public void execute(Grid grid, String value, Object data) { + final String columnProperty = getColumnProperty((Integer) data); + List cols = grid.getColumns(); + List reordered = new ArrayList(); + boolean addAsLast = false; + for (int i = 0; i < cols.size(); i++) { + Column col = cols.get(i); + if (col.getPropertyId().equals(columnProperty)) { + if (i == 0) { + addAsLast = true; + } else { + reordered.add(i - 1, columnProperty); + } + } else { + reordered.add(col.getPropertyId()); + } + } + if (addAsLast) { + reordered.add(columnProperty); + } + grid.setColumnOrder(reordered.toArray()); + } + }, null, c); createBooleanAction("Sortable", getColumnProperty(c), true, new Command() { 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 7b62ff85f9..7f4e9bb7e9 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 @@ -70,6 +70,26 @@ public class GridColumnReorderTest extends GridBasicFeaturesTest { assertColumnHeaderOrder(1, 2, 0); } + @Test + public void testColumnReordering_reorderingTwiceBackForth_reordered() { + // given + openTestURL(); + assertColumnHeaderOrder(0, 1, 2); + toggleColumnReordering(); + + // when + dragDefaultColumnHeader(2, 0, 10); + + // then + assertColumnHeaderOrder(2, 0, 1, 3); + + // when + dragDefaultColumnHeader(1, 3, 110); + + // then + assertColumnHeaderOrder(2, 1, 3, 0); + } + @Test public void testColumnReordering_notEnabled_noReordering() { // given @@ -83,10 +103,47 @@ public class GridColumnReorderTest extends GridBasicFeaturesTest { assertColumnHeaderOrder(0, 1, 2); } + @Test + public void testColumnReordering_userChangesRevertedByServer_columnsAreUpdated() { + // given + openTestURL(); + assertColumnHeaderOrder(0, 1, 2); + toggleColumnReordering(); + + // when + dragDefaultColumnHeader(0, 2, 10); + assertColumnHeaderOrder(1, 0, 2); + moveColumnManuallyLeftByOne(0); + + // then + assertColumnHeaderOrder(0, 1, 2); + } + + @Test + public void testColumnReordering_concurrentUpdatesFromServer_columnOrderFromServerUsed() { + // given + openTestURL(); + assertColumnHeaderOrder(0, 1, 2); + toggleColumnReordering(); + + // when + selectMenuPath(new String[] { "Component", "Internals", + "Update column order without updating client" }); + dragDefaultColumnHeader(2, 0, 10); + + // then + assertColumnHeaderOrder(1, 0, 2); + } + private void toggleColumnReordering() { selectMenuPath(COLUMN_REORDERING_PATH); } + private void moveColumnManuallyLeftByOne(int index) { + selectMenuPath(new String[] { "Component", "Columns", + "Column " + index, "Move left" }); + } + private void assertColumnHeaderOrder(int... indices) { List headers = getGridHeaderRowCells(); for (int i = 0; i < indices.length; i++) { -- cgit v1.2.3 From 70b564a6bbb8f1af667a41cee0ef2e00afd5682b Mon Sep 17 00:00:00 2001 From: Henrik Paul Date: Wed, 18 Feb 2015 16:09:10 +0200 Subject: Adding/removing spacers affects Escalator row positions (#16644) Note that this patch is only a first step of many. This patch only moves displayed rows when spacer dimensions change. Doing anything else afterwards (even scrolling) will probably not (and is not intended to) look good at this stage. Change-Id: I4de6abd932f587682840396df7a56a7df710af69 --- .../src/com/vaadin/client/widgets/Escalator.java | 176 ++++++++++++++++----- .../EscalatorBasicClientFeaturesTest.java | 7 +- .../escalator/EscalatorSpacerTest.java | 119 ++++++++++++++ 3 files changed, 257 insertions(+), 45 deletions(-) (limited to 'client/src') diff --git a/client/src/com/vaadin/client/widgets/Escalator.java b/client/src/com/vaadin/client/widgets/Escalator.java index 567262c6b2..1d3faee51b 100644 --- a/client/src/com/vaadin/client/widgets/Escalator.java +++ b/client/src/com/vaadin/client/widgets/Escalator.java @@ -52,6 +52,7 @@ import com.google.gwt.logging.client.LogConfiguration; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.RequiresResize; +import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.UIObject; import com.google.gwt.user.client.ui.Widget; import com.vaadin.client.BrowserInfo; @@ -1692,6 +1693,11 @@ public class Escalator extends Widget implements RequiresResize, public void setColumnFrozen(int column, boolean frozen) { final NodeList childRows = root.getRows(); + if (column == 0 && this instanceof BodyRowContainer) { + // TODO + getLogger().warning("[[spacers]] freezing columns"); + } + for (int row = 0; row < childRows.getLength(); row++) { final TableRowElement tr = childRows.getItem(row); @@ -1712,6 +1718,11 @@ public class Escalator extends Widget implements RequiresResize, public void updateFreezePosition(int column, double scrollLeft) { final NodeList childRows = root.getRows(); + if (column == 0 && this instanceof BodyRowContainer) { + // TODO + getLogger().warning("[[spacers]] update freeze position"); + } + for (int row = 0; row < childRows.getLength(); row++) { final TableRowElement tr = childRows.getItem(row); @@ -2507,6 +2518,9 @@ public class Escalator extends Widget implements RequiresResize, } if (rowsWereMoved) { + // TODO + getLogger().warning("[[spacers]] rotating rows during scroll"); + fireRowVisibilityChangeEvent(); if (scroller.touchHandlerBundle.touches == 0) { @@ -2526,6 +2540,9 @@ public class Escalator extends Widget implements RequiresResize, return; } + // TODO + getLogger().warning("[[spacers]] inserting rows"); + /* * TODO: this method should probably only add physical rows, and not * populate them - let everything be populated as appropriate by the @@ -2814,6 +2831,9 @@ public class Escalator extends Widget implements RequiresResize, return; } + // TODO + getLogger().warning("[[spacers]] removing rows"); + final Range viewportRange = getVisibleRowRange(); final Range removedRowsRange = Range .withLength(index, numberOfRows); @@ -3238,6 +3258,7 @@ public class Escalator extends Widget implements RequiresResize, * current viewport. The first visual row has the index 0. */ private Range convertToVisual(final Range logicalRange) { + if (logicalRange.isEmpty()) { return logicalRange; } else if (visualRowOrder.isEmpty()) { @@ -3341,6 +3362,10 @@ public class Escalator extends Widget implements RequiresResize, * instead. */ public void verifyEscalatorCount() { + + // TODO + getLogger().warning("[[spacers]] verifying escalator row count"); + /* * This method indeed has a smell very similar to paintRemoveRows * and paintInsertRows. @@ -3374,6 +3399,11 @@ public class Escalator extends Widget implements RequiresResize, if (neededEscalatorRowsDiff > 0) { // needs more + // TODO + getLogger().warning( + "[[spacers]] adding more rows while expanding the " + + "body section"); + /* * This is a workaround for the issue where we might be scrolled * to the bottom, and the widget expands beyond the content @@ -3444,6 +3474,11 @@ public class Escalator extends Widget implements RequiresResize, else if (neededEscalatorRowsDiff < 0) { // needs less + // TODO + getLogger().warning( + "[[spacers]] removing rows while shrinking the body " + + "section"); + final ListIterator iter = visualRowOrder .listIterator(visualRowOrder.size()); for (int i = 0; i < -neededEscalatorRowsDiff; i++) { @@ -3652,6 +3687,44 @@ public class Escalator extends Widget implements RequiresResize, public SpacerUpdater getSpacerUpdater() { return spacerContainer.getSpacerUpdater(); } + + private double calculateRowTop(int logicalIndex) { + double top = spacerContainer.getSpacerHeightsSumUntil(logicalIndex); + return top + (logicalIndex * getDefaultRowHeight()); + } + + public void shiftRowPositions(int row, double diff) { + for (TableRowElement tr : getVisibleRowsAfter(row)) { + setRowPosition(tr, 0, getRowTop(tr) + diff); + } + + if (row > getTopRowLogicalIndex()) { + // TODO + getLogger().warning( + "scrollbar compensation not yet implemented"); + } + } + + private List getVisibleRowsAfter(int logicalRow) { + Range visibleRowLogicalRange = getVisibleRowRange(); + + boolean allRowsAreInView = logicalRow < visibleRowLogicalRange + .getStart(); + boolean noRowsAreInView = logicalRow >= visibleRowLogicalRange + .getEnd() - 1; + + if (allRowsAreInView) { + return Collections.unmodifiableList(visualRowOrder); + } else if (noRowsAreInView) { + return Collections.emptyList(); + } else { + int fromIndex = (logicalRow - visibleRowLogicalRange.getStart()) + 1; + int toIndex = visibleRowLogicalRange.length(); + List sublist = visualRowOrder.subList( + fromIndex, toIndex); + return Collections.unmodifiableList(sublist); + } + } } private class ColumnConfigurationImpl implements ColumnConfiguration { @@ -4136,17 +4209,23 @@ public class Escalator extends Widget implements RequiresResize, private TableCellElement spacerElement; private TableRowElement root; private final int rowIndex; + private double height = -1; + private boolean domHasBeenSetup = false; - public SpacerImpl(int rowIndex, double height) { + public SpacerImpl(int rowIndex) { this.rowIndex = rowIndex; - // Build DOM structure root = TableRowElement.as(DOM.createTR()); spacerElement = TableCellElement.as(DOM.createTD()); root.appendChild(spacerElement); root.setPropertyInt(SPACER_LOGICAL_ROW_PROPERTY, rowIndex); + } + + public void setupDom(double height) { + assert !domHasBeenSetup : "DOM can't be set up twice."; + assert RootPanel.get().getElement().isOrHasChild(root) : "Root element should've been attached to the DOM by now."; + domHasBeenSetup = true; - // Configure DOM structure setHeight(height); root.getStyle().setWidth(100, Unit.PCT); @@ -4162,18 +4241,25 @@ public class Escalator extends Widget implements RequiresResize, } public void setPosition(double x, double y) { - positions.set(root, x, y); + positions.set(getRootElement(), x, y); } 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"); + public void setHeight(double height) { + + assert height >= 0 : "Height must be more >= 0 (was " + height + + ")"; + + double diff = height - Math.max(0, this.height); + this.height = height; + root.getStyle().setHeight(height, Unit.PX); + + shiftSpacerPositions(getRow(), diff); + body.shiftRowPositions(getRow(), diff); + body.verifyEscalatorCount(); } @Override @@ -4185,6 +4271,15 @@ public class Escalator extends Widget implements RequiresResize, public int getRow() { return rowIndex; } + + public double getHeight() { + assert height >= 0 : "Height was not previously set by setHeight."; + return height; + } + + public double getTop() { + return positions.getTop(getRootElement()); + } } private final TreeMap rowIndexToSpacer = new TreeMap(); @@ -4209,6 +4304,16 @@ public class Escalator extends Widget implements RequiresResize, } } + @SuppressWarnings("boxing") + public double getSpacerHeightsSumUntil(int logicalIndex) { + double heights = 0; + for (SpacerImpl spacer : rowIndexToSpacer.headMap(logicalIndex, + false).values()) { + heights += spacer.getHeight(); + } + return heights; + } + private boolean spacerExists(int rowIndex) { return rowIndexToSpacer.containsKey(Integer.valueOf(rowIndex)); } @@ -4216,15 +4321,16 @@ public class Escalator extends Widget implements RequiresResize, @SuppressWarnings("boxing") private void insertNewSpacer(int rowIndex, double height) { - SpacerImpl spacer = new SpacerImpl(rowIndex, height); + SpacerImpl spacer = new SpacerImpl(rowIndex); rowIndexToSpacer.put(rowIndex, spacer); - spacer.setPosition(0, getSpacerTop(rowIndex)); + spacer.setPosition(0, calculateSpacerTop(rowIndex)); TableRowElement spacerRoot = spacer.getRootElement(); spacerRoot.getStyle().setWidth( columnConfiguration.calculateRowWidth(), Unit.PX); body.getElement().appendChild(spacerRoot); + spacer.setupDom(height); initSpacerContent(spacer); } @@ -4237,29 +4343,6 @@ public class Escalator extends Widget implements RequiresResize, return rowIndexToSpacer.get(Integer.valueOf(rowIndex)); } - 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) { SpacerImpl spacer = getSpacer(rowIndex); @@ -4333,8 +4416,6 @@ public class Escalator extends Widget implements RequiresResize, } public Element getSubPartElement(int index) { - getLogger().warning("SpacerContainer.getSubPartElement " + index); - SpacerImpl spacer = rowIndexToSpacer.get(Integer.valueOf(index)); if (spacer != null) { return spacer.getElement(); @@ -4342,6 +4423,19 @@ public class Escalator extends Widget implements RequiresResize, return null; } } + + private double calculateSpacerTop(int logicalIndex) { + return body.calculateRowTop(logicalIndex) + + body.getDefaultRowHeight(); + } + + @SuppressWarnings("boxing") + private void shiftSpacerPositions(int changedRowIndex, double diffPx) { + for (SpacerImpl spacer : rowIndexToSpacer.tailMap(changedRowIndex, + false).values()) { + spacer.setPosition(0, spacer.getTop() + diffPx); + } + } } private class ElementPositionBookkeeper { @@ -5025,7 +5119,8 @@ public class Escalator extends Widget implements RequiresResize, /** * Adds an event handler that gets notified when the range of visible rows - * changes e.g. because of scrolling or row resizing. + * changes e.g. because of scrolling, row resizing or spacers + * appearing/disappearing. * * @param rowVisibilityChangeHandler * the event handler @@ -5053,14 +5148,13 @@ public class Escalator extends Widget implements RequiresResize, } /** - * Gets the range of currently visible rows. + * Gets the logical index range of currently visible rows. * - * @return range of visible rows + * @return logical index range of visible rows */ public Range getVisibleRowRange() { if (!body.visualRowOrder.isEmpty()) { - return Range.withLength( - body.getLogicalRowIndex(body.visualRowOrder.getFirst()), + return Range.withLength(body.getTopRowLogicalIndex(), body.visualRowOrder.size()); } else { return Range.withLength(0, 0); 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 b8bc644948..ef2d605928 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/EscalatorBasicClientFeaturesTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/EscalatorBasicClientFeaturesTest.java @@ -240,17 +240,16 @@ public abstract class EscalatorBasicClientFeaturesTest extends MultiBrowserTest } protected void scrollVerticallyTo(int px) { - executeScript("arguments[0].scrollTop = " + px, getVeticalScrollbar()); + getVerticalScrollbar().scroll(px); } - private TestBenchElement getVeticalScrollbar() { + private TestBenchElement getVerticalScrollbar() { return (TestBenchElement) getEscalator().findElement( By.className("v-escalator-scroller-vertical")); } protected void scrollHorizontallyTo(int px) { - executeScript("arguments[0].scrollLeft = " + px, - getHorizontalScrollbar()); + getHorizontalScrollbar().scrollLeft(px); } private TestBenchElement getHorizontalScrollbar() { 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 index 24e474a767..43a18b507d 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/escalator/EscalatorSpacerTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/escalator/EscalatorSpacerTest.java @@ -15,16 +15,68 @@ */ package com.vaadin.tests.components.grid.basicfeatures.escalator; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.junit.Before; import org.junit.Test; +import com.vaadin.testbench.TestBenchElement; import com.vaadin.tests.components.grid.basicfeatures.EscalatorBasicClientFeaturesTest; public class EscalatorSpacerTest extends EscalatorBasicClientFeaturesTest { + //@formatter:off + // separate strings made so that eclipse can show the concatenated string by hovering the mouse over the constant + + // translate3d(0px, 40px, 123px); + // translate3d(24px, 15.251px, 0); + // translate(0, 40px); + private final static String TRANSLATE_VALUE_REGEX = + "translate(?:3d|)" // "translate" or "translate3d" + + "\\(" // literal "(" + + ".+?, " // the x argument, uninteresting + + "(" // start capturing the y argument + + "[0-9]+" // the integer part of the value + + "(?:" // start of the subpixel part of the value + + "\\.[0-9]" // if we have a period, there must be at least one number after it + + "[0-9]*" // any amount of accuracy afterwards is fine + + ")?" // the subpixel part is optional + + ")" + + "(?:px)?" // we don't care if the values are suffixed by "px" or not. + + "(?:, .*?)?" // the possible z argument, uninteresting (translate doesn't have one, translate3d does) + + "\\)" // literal ")" + + ";?"; // optional ending semicolon + + // 40px; + // 12.34px + private final static String TOP_VALUE_REGEX = + "(" // capture the pixel value + + "[0-9]+" // the pixel argument + + "(?:" // start of the subpixel part of the value + + "\\.[0-9]" // if we have a period, there must be at least one number after it + + "[0-9]*" // any amount of accuracy afterwards is fine + + ")?" // the subpixel part is optional + + ")" + + "(?:px)?" // optional "px" string + + ";?"; // optional semicolon + //@formatter:on + + private final static Pattern TRANSFORM_CSS_PATTERN = Pattern + .compile("transform: (.*?);"); // also matches "-webkit-transform"; + private final static Pattern TOP_CSS_PATTERN = Pattern + .compile("top: (.*?);"); + + private final static Pattern TRANSLATE_VALUE_PATTERN = Pattern + .compile(TRANSLATE_VALUE_REGEX); + private final static Pattern TOP_VALUE_PATTERN = Pattern + .compile(TOP_VALUE_REGEX); + @Before public void before() { openTestURL(); @@ -45,4 +97,71 @@ public class EscalatorSpacerTest extends EscalatorBasicClientFeaturesTest { assertNull("Spacer should not exist after removing it", getSpacer(1)); } + @Test + @SuppressWarnings("boxing") + public void spacerPushesVisibleRowsDown() { + double oldTop = getElementTop(getBodyRow(2)); + selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX); + double newTop = getElementTop(getBodyRow(2)); + + assertGreater("Row below a spacer was not pushed down", newTop, oldTop); + } + + private static double getElementTop(TestBenchElement element) { + /* + * we need to parse the style attribute, since using getCssValue gets a + * normalized value that is harder to parse. + */ + String style = element.getAttribute("style"); + + String transform = getTransformFromStyle(style); + if (transform != null) { + return getTranslateYValue(transform); + } + + String top = getTopFromStyle(style); + if (top != null) { + return getTopValue(top); + } + + throw new IllegalArgumentException( + "Could not parse the top position from the CSS \"" + style + + "\""); + } + + private static String getTransformFromStyle(String style) { + return getFromStyle(TRANSFORM_CSS_PATTERN, style); + } + + private static String getTopFromStyle(String style) { + return getFromStyle(TOP_CSS_PATTERN, style); + } + + private static String getFromStyle(Pattern pattern, String style) { + Matcher matcher = pattern.matcher(style); + if (matcher.find()) { + assertEquals("wrong amount of groups matched in " + style, 1, + matcher.groupCount()); + return matcher.group(1); + } else { + return null; + } + } + + private static double getTranslateYValue(String translate) { + return getValueFromCss(TRANSLATE_VALUE_PATTERN, translate); + } + + private static double getTopValue(String top) { + return getValueFromCss(TOP_VALUE_PATTERN, top); + } + + private static double getValueFromCss(Pattern pattern, String css) { + Matcher matcher = pattern.matcher(css); + assertTrue("no matches for " + css + " against " + + TRANSLATE_VALUE_PATTERN, matcher.find()); + assertEquals("wrong amount of groups matched in " + css, 1, + matcher.groupCount()); + return Double.parseDouble(matcher.group(1)); + } } -- 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 'client/src') 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 7d65a56a9b91f37da079dc15d678cff115cd4d46 Mon Sep 17 00:00:00 2001 From: Henrik Paul Date: Thu, 19 Feb 2015 14:53:35 +0200 Subject: Fixes Escalator row offsets with spacers open (#16644) Caveat: Scrolling works, as long as it is done slowly, one-by-one. When moving several rows in a batch, spacers are ignored and rows end up underneath them. Change-Id: I07d0135e4ac559f5553cd8dc85bca39061de69b7 --- .../src/com/vaadin/client/widgets/Escalator.java | 284 +++++++++++++++++---- 1 file changed, 237 insertions(+), 47 deletions(-) (limited to 'client/src') diff --git a/client/src/com/vaadin/client/widgets/Escalator.java b/client/src/com/vaadin/client/widgets/Escalator.java index 1d3faee51b..5711ca4731 100644 --- a/client/src/com/vaadin/client/widgets/Escalator.java +++ b/client/src/com/vaadin/client/widgets/Escalator.java @@ -2406,10 +2406,23 @@ public class Escalator extends Widget implements RequiresResize, boolean rowsWereMoved = false; - final double topRowPos = getRowTop(visualRowOrder.getFirst()); + final double topElementPosition; + final double nextRowBottomOffset; + SpacerContainer.SpacerImpl topSpacer = spacerContainer + .getSpacer(getTopRowLogicalIndex() - 1); + + if (topSpacer != null) { + topElementPosition = topSpacer.getTop(); + nextRowBottomOffset = topSpacer.getHeight() + + getDefaultRowHeight(); + } else { + topElementPosition = getRowTop(visualRowOrder.getFirst()); + nextRowBottomOffset = getDefaultRowHeight(); + } + // TODO [[mpixscroll]] final double scrollTop = tBodyScrollTop; - final double viewportOffset = topRowPos - scrollTop; + final double viewportOffset = topElementPosition - scrollTop; /* * TODO [[optimize]] this if-else can most probably be refactored @@ -2419,14 +2432,17 @@ public class Escalator extends Widget implements RequiresResize, if (viewportOffset > 0) { // there's empty room on top - int originalRowsToMove = (int) Math.ceil(viewportOffset + double rowPx = getRowHeightsSumBetweenPx(scrollTop, + topElementPosition); + int originalRowsToMove = (int) Math.ceil(rowPx / getDefaultRowHeight()); int rowsToMove = Math.min(originalRowsToMove, visualRowOrder.size()); final int end = visualRowOrder.size(); final int start = end - rowsToMove; - final int logicalRowIndex = (int) (scrollTop / getDefaultRowHeight()); + final int logicalRowIndex = getLogicalRowIndex(scrollTop); + moveAndUpdateEscalatorRows(Range.between(start, end), 0, logicalRowIndex); @@ -2435,14 +2451,16 @@ public class Escalator extends Widget implements RequiresResize, rowsWereMoved = true; } - else if (viewportOffset + getDefaultRowHeight() <= 0) { + else if (viewportOffset + nextRowBottomOffset <= 0) { /* * the viewport has been scrolled more than the topmost visual * row. */ - int originalRowsToMove = (int) Math.abs(viewportOffset - / getDefaultRowHeight()); + double rowPx = getRowHeightsSumBetweenPx(topElementPosition, + scrollTop); + + int originalRowsToMove = (int) (rowPx / getDefaultRowHeight()); int rowsToMove = Math.min(originalRowsToMove, visualRowOrder.size()); @@ -2460,7 +2478,7 @@ public class Escalator extends Widget implements RequiresResize, * calculate the first logical row index from the scroll * position. */ - logicalRowIndex = (int) (scrollTop / getDefaultRowHeight()); + logicalRowIndex = getLogicalRowIndex(scrollTop); } /* @@ -2518,9 +2536,6 @@ public class Escalator extends Widget implements RequiresResize, } if (rowsWereMoved) { - // TODO - getLogger().warning("[[spacers]] rotating rows during scroll"); - fireRowVisibilityChangeEvent(); if (scroller.touchHandlerBundle.touches == 0) { @@ -2534,6 +2549,27 @@ public class Escalator extends Widget implements RequiresResize, } } + private double getRowHeightsSumBetweenPx(double y1, double y2) { + assert y1 < y2 : "y1 must be smaller than y2"; + + double viewportPx = y2 - y1; + double spacerPx = spacerContainer.getSpacerHeightsSumBetweenPx(y1, + SpacerMeasurementStrategy.PARTIAL, y2, + SpacerMeasurementStrategy.PARTIAL); + + return viewportPx - spacerPx; + } + + private int getLogicalRowIndex(final double px) { + /* + * FIXME: this is buggy! if px is mid-spacer, it will return the + * pixel count up until PX, not the one after (or before) it. + */ + double rowPx = px + - spacerContainer.getSpacerCompleteHeightsSumUntilPx(px); + return (int) (rowPx / getDefaultRowHeight()); + } + @Override protected void paintInsertRows(final int index, final int numberOfRows) { if (numberOfRows == 0) { @@ -2632,12 +2668,6 @@ public class Escalator extends Widget implements RequiresResize, * the visual index where the rows will be placed to * @param logicalTargetIndex * the logical index to be assigned to the first moved row - * @throws IllegalArgumentException - * if any of visualSourceRange.getStart(), - * visualTargetIndex or - * logicalTargetIndex is a negative number; or - * if visualTargetInfo is greater than the - * number of escalator rows. */ private void moveAndUpdateEscalatorRows(final Range visualSourceRange, final int visualTargetIndex, final int logicalTargetIndex) @@ -2647,28 +2677,24 @@ public class Escalator extends Widget implements RequiresResize, return; } - if (visualSourceRange.getStart() < 0) { - throw new IllegalArgumentException( - "Logical source start must be 0 or greater (was " - + visualSourceRange.getStart() + ")"); - } else if (logicalTargetIndex < 0) { - throw new IllegalArgumentException( - "Logical target must be 0 or greater"); - } else if (visualTargetIndex < 0) { - throw new IllegalArgumentException( - "Visual target must be 0 or greater"); - } else if (visualTargetIndex > root.getChildCount()) { - throw new IllegalArgumentException( - "Visual target must not be greater than the number of escalator rows"); - } else if (logicalTargetIndex + visualSourceRange.length() > getRowCount()) { - Range logicalTargetRange = Range.withLength(logicalTargetIndex, - visualSourceRange.length()); - Range availableRange = Range.withLength(0, getRowCount()); - throw new IllegalArgumentException("Logical target leads " - + "to rows outside of the data range (" - + logicalTargetRange + " goes beyond " + availableRange - + ")"); - } + assert visualSourceRange.getStart() >= 0 : "Visual source start " + + "must be 0 or greater (was " + + visualSourceRange.getStart() + ")"; + + assert logicalTargetIndex >= 0 : "Logical target must be 0 or " + + "greater"; + + assert visualTargetIndex >= 0 : "Visual target must be 0 or greater"; + + assert visualTargetIndex <= root.getChildCount() : "Visual target " + + "must not be greater than the number of escalator rows"; + + assert logicalTargetIndex + visualSourceRange.length() <= getRowCount() : "Logical " + + "target leads to rows outside of the data range (" + + Range.withLength(logicalTargetIndex, + visualSourceRange.length()) + + " goes beyond " + + Range.withLength(0, getRowCount()) + ")"; /* * Since we move a range into another range, the indices might move @@ -2723,7 +2749,7 @@ public class Escalator extends Widget implements RequiresResize, } { // Reposition the rows that were moved - double newRowTop = logicalTargetIndex * getDefaultRowHeight(); + double newRowTop = getRowTop(logicalTargetIndex); final ListIterator iter = visualRowOrder .listIterator(adjustedVisualTargetIndex); @@ -3688,8 +3714,9 @@ public class Escalator extends Widget implements RequiresResize, return spacerContainer.getSpacerUpdater(); } - private double calculateRowTop(int logicalIndex) { - double top = spacerContainer.getSpacerHeightsSumUntil(logicalIndex); + private double getRowTop(int logicalIndex) { + double top = spacerContainer + .getSpacerHeightsSumUntilIndex(logicalIndex); return top + (logicalIndex * getDefaultRowHeight()); } @@ -3701,7 +3728,8 @@ public class Escalator extends Widget implements RequiresResize, if (row > getTopRowLogicalIndex()) { // TODO getLogger().warning( - "scrollbar compensation not yet implemented"); + "[[spacers]] scrollbar compensation not yet " + + "implemented"); } } @@ -4200,6 +4228,21 @@ public class Escalator extends Widget implements RequiresResize, } } + /** + * A decision on how to measure a spacer when it is partially within a + * designated range. + */ + public enum SpacerMeasurementStrategy { + /** Take the entire spacer's height into account. */ + COMPLETE, + + /** Take the visible part into account. */ + PARTIAL, + + /** Exclude the entire spacer. */ + NONE + } + private class SpacerContainer { /** This is used mainly for testing purposes */ @@ -4283,10 +4326,12 @@ public class Escalator extends Widget implements RequiresResize, } private final TreeMap rowIndexToSpacer = new TreeMap(); + private SpacerUpdater spacerUpdater = SpacerUpdater.NULL; 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 " @@ -4304,8 +4349,154 @@ public class Escalator extends Widget implements RequiresResize, } } + /** + * Gets the amount of pixels occupied by spacers between two pixel + * points. + * + * @param rangeTop + * the top pixel point + * @param topInclusion + * how to measure a spacer that happens to lie in the middle + * of {@code rangeTop}. + * @param rangeBottom + * the bottom pixel point + * @param bottomInclusion + * how to measure a spacer that happens to lie in the middle + * of {@code rangeBottom}. + * @return the pixels occupied by spacers between {@code rangeTop} and + * {@code rangeBottom} + */ + public double getSpacerHeightsSumBetweenPx(double rangeTop, + SpacerMeasurementStrategy topInclusion, double rangeBottom, + SpacerMeasurementStrategy bottomInclusion) { + + assert rangeTop <= rangeBottom : "rangeTop must be less than rangeBottom"; + + double heights = 0; + + /* + * TODO [[optimize]]: this might be somewhat inefficient (due to + * iterator-based scanning, instead of using the treemap's search + * functionalities). But it should be easy to write, read, verify + * and maintain. + */ + for (SpacerImpl spacer : rowIndexToSpacer.values()) { + double top = spacer.getTop(); + double height = spacer.getHeight(); + double bottom = top + height; + + /* + * If we happen to implement a DoubleRange (in addition to the + * int-based Range) at some point, the following logic should + * probably be converted into using the + * Range.partitionWith-equivalent. + */ + + boolean topIsAboveRange = top < rangeTop; + boolean topIsInRange = rangeTop <= top && top <= rangeBottom; + boolean topIsBelowRange = rangeBottom < top; + + boolean bottomIsAboveRange = bottom < rangeTop; + boolean bottomIsInRange = rangeTop <= bottom + && bottom <= rangeBottom; + boolean bottomIsBelowRange = rangeBottom < bottom; + + assert topIsAboveRange ^ topIsBelowRange ^ topIsInRange : "Bad top logic"; + assert bottomIsAboveRange ^ bottomIsBelowRange + ^ bottomIsInRange : "Bad bottom logic"; + + if (bottomIsAboveRange) { + continue; + } else if (topIsBelowRange) { + return heights; + } + + else if (topIsAboveRange && bottomIsInRange) { + switch (topInclusion) { + case PARTIAL: + heights += bottom - rangeTop; + break; + case COMPLETE: + heights += height; + break; + default: + break; + } + } + + else if (topIsAboveRange && bottomIsBelowRange) { + + /* + * Here we arbitrarily decide that the top inclusion will + * have the honor of overriding the bottom inclusion if + * happens to be a conflict of interests. + */ + switch (topInclusion) { + case NONE: + return 0; + case COMPLETE: + return height; + case PARTIAL: + return rangeBottom - rangeTop; + default: + throw new IllegalArgumentException( + "Unexpected inclusion state :" + topInclusion); + } + + } else if (topIsInRange && bottomIsInRange) { + heights += height; + } + + else if (topIsInRange && bottomIsBelowRange) { + switch (bottomInclusion) { + case PARTIAL: + heights += rangeBottom - top; + break; + case COMPLETE: + heights += height; + break; + default: + break; + } + + return heights; + } + + else { + assert false : "Unnaccounted-for situation"; + } + } + + return heights; + } + + /** + * Gets the amount of pixels occupied by spacers from the top until a + * certain spot from the top of the body. + *

+ * If a spacer lies in the middle of {@code px}, then the entire height + * of the spacer will be taken into account. + * + * @param px + * pixels counted from the top + * @return the pixels occupied by entire spacers up until {@code px} + */ + public double getSpacerCompleteHeightsSumUntilPx(double px) { + return getSpacerHeightsSumBetweenPx(0, + SpacerMeasurementStrategy.COMPLETE, px, + SpacerMeasurementStrategy.COMPLETE); + } + + /** + * Gets the amount of pixels occupied by spacers until a logical row + * index. + * + * @param logicalIndex + * a logical row index + * @return the pixels occupied by spacers up until {@code logicalIndex} + */ @SuppressWarnings("boxing") - public double getSpacerHeightsSumUntil(int logicalIndex) { + public double getSpacerHeightsSumUntilIndex(int logicalIndex) { double heights = 0; for (SpacerImpl spacer : rowIndexToSpacer.headMap(logicalIndex, false).values()) { @@ -4339,7 +4530,7 @@ public class Escalator extends Widget implements RequiresResize, getSpacer(rowIndex).setHeight(newHeight); } - private SpacerImpl getSpacer(int rowIndex) { + public SpacerImpl getSpacer(int rowIndex) { return rowIndexToSpacer.get(Integer.valueOf(rowIndex)); } @@ -4425,8 +4616,7 @@ public class Escalator extends Widget implements RequiresResize, } private double calculateSpacerTop(int logicalIndex) { - return body.calculateRowTop(logicalIndex) - + body.getDefaultRowHeight(); + return body.getRowTop(logicalIndex) + body.getDefaultRowHeight(); } @SuppressWarnings("boxing") -- cgit v1.2.3 From 2b8846335d5e315966e2698faafe8f8695198f66 Mon Sep 17 00:00:00 2001 From: Henrik Paul Date: Mon, 23 Feb 2015 13:45:44 +0200 Subject: Fixes fast Escalator scrolling with spacers (#16644). Change-Id: I2c79cc972e35ffe37a6fb3cc97b6fc937c3d32f7 --- client/src/com/vaadin/client/widgets/Escalator.java | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'client/src') diff --git a/client/src/com/vaadin/client/widgets/Escalator.java b/client/src/com/vaadin/client/widgets/Escalator.java index 5711ca4731..bca0cc9afb 100644 --- a/client/src/com/vaadin/client/widgets/Escalator.java +++ b/client/src/com/vaadin/client/widgets/Escalator.java @@ -2756,7 +2756,13 @@ public class Escalator extends Widget implements RequiresResize, for (int i = 0; i < visualSourceRange.length(); i++) { final TableRowElement tr = iter.next(); setRowPosition(tr, 0, newRowTop); + newRowTop += getDefaultRowHeight(); + SpacerContainer.SpacerImpl spacer = spacerContainer + .getSpacer(logicalTargetIndex + i); + if (spacer != null) { + newRowTop += spacer.getHeight(); + } } } } -- cgit v1.2.3 From d2706131010e19e3fe5efceab118592efb18cf77 Mon Sep 17 00:00:00 2001 From: Henrik Paul Date: Mon, 23 Feb 2015 17:31:13 +0200 Subject: Spacers affect Escalator's scrollbars (#16644) Change-Id: I73a7a6db4d25c09d4a3eb04c4aea18f06428d320 --- .../src/com/vaadin/client/widgets/Escalator.java | 145 ++++++++++++++++----- 1 file changed, 115 insertions(+), 30 deletions(-) (limited to 'client/src') diff --git a/client/src/com/vaadin/client/widgets/Escalator.java b/client/src/com/vaadin/client/widgets/Escalator.java index bca0cc9afb..185744d2ec 100644 --- a/client/src/com/vaadin/client/widgets/Escalator.java +++ b/client/src/com/vaadin/client/widgets/Escalator.java @@ -1144,6 +1144,9 @@ public class Escalator extends Widget implements RequiresResize, public void scrollToRow(final int rowIndex, final ScrollDestination destination, final double padding) { + + getLogger().warning("[[spacers]] scrollToRow"); + final double targetStartPx = body.getDefaultRowHeight() * rowIndex; final double targetEndPx = targetStartPx + body.getDefaultRowHeight(); @@ -2317,7 +2320,7 @@ public class Escalator extends Widget implements RequiresResize, this.topRowLogicalIndex = topRowLogicalIndex; } - private int getTopRowLogicalIndex() { + public int getTopRowLogicalIndex() { return topRowLogicalIndex; } @@ -2610,7 +2613,7 @@ public class Escalator extends Widget implements RequiresResize, */ final double yDelta = numberOfRows * getDefaultRowHeight(); - adjustScrollPosIgnoreEvents(yDelta); + moveViewportAndContent(yDelta); updateTopRowLogicalIndex(numberOfRows); } @@ -2768,30 +2771,53 @@ public class Escalator extends Widget implements RequiresResize, } /** - * Adjust the scroll position without having the scroll handler have any - * side-effects. + * Adjust the scroll position and move the contained rows. + *

+ * Note: This method does not account for spacers. *

- * Note: {@link Scroller#onScroll()} will be - * triggered, but it will not do anything, with the help of - * {@link Escalator#internalScrollEventCalls}. + * The difference between using this method and simply scrolling is that + * this method "takes the rows with it" and renders them appropriately. + * The viewport may be scrolled any arbitrary amount, and the rows are + * moved appropriately, but always snapped into a plausible place. + *

+ *

+ *
Example 1
+ *
An Escalator with default row height 20px. Adjusting the scroll + * position with 7.5px will move the viewport 7.5px down, but leave the + * row where it is.
+ *
Example 2
+ *
An Escalator with default row height 20px. Adjusting the scroll + * position with 27.5px will move the viewport 27.5px down, and place + * the row at 20px.
+ *
* * @param yDelta - * the delta of pixels to scrolls. A positive value moves the - * viewport downwards, while a negative value moves the - * viewport upwards + * the delta of pixels by which to move the viewport and + * content. A positive value moves everything downwards, + * while a negative value moves everything upwards */ - public void adjustScrollPosIgnoreEvents(final double yDelta) { + public void moveViewportAndContent(final double yDelta) { + + /* + * TODO: When adding and removing rows starts supporting spacers, + * this method should also take spacers into account. Remember to + * adjust the javadoc as well. + */ + if (yDelta == 0) { return; } - verticalScrollbar.setScrollPosByDelta(yDelta); + double newTop = tBodyScrollTop + yDelta; + final double rowTopPos = body.getRowTop(getLogicalRowIndex(newTop)); + + verticalScrollbar.setScrollPos(newTop); - final double rowTopPos = yDelta - (yDelta % getDefaultRowHeight()); - for (final TableRowElement tr : visualRowOrder) { - setRowPosition(tr, 0, getRowTop(tr) + rowTopPos); + for (int i = 0; i < visualRowOrder.size(); i++) { + final TableRowElement tr = visualRowOrder.get(i); + setRowPosition(tr, 0, rowTopPos + getDefaultRowHeight() * i); } - setBodyScrollPosition(tBodyScrollLeft, tBodyScrollTop + yDelta); + setBodyScrollPosition(tBodyScrollLeft, newTop); } /** @@ -2915,7 +2941,7 @@ public class Escalator extends Widget implements RequiresResize, * to do is to adjust the scroll position to account for the * removed rows */ - adjustScrollPosIgnoreEvents(-yDelta); + moveViewportAndContent(-yDelta); } else if (removalScrollsToShowFirstLogicalRow) { /* * It seems like we've removed all rows from above, and also @@ -2924,8 +2950,7 @@ public class Escalator extends Widget implements RequiresResize, * current negative scrolltop, presto!), so that it isn't * aligned funnily */ - adjustScrollPosIgnoreEvents(-verticalScrollbar - .getScrollPos()); + moveViewportAndContent(-verticalScrollbar.getScrollPos()); } } @@ -3730,13 +3755,6 @@ public class Escalator extends Widget implements RequiresResize, for (TableRowElement tr : getVisibleRowsAfter(row)) { setRowPosition(tr, 0, getRowTop(tr) + diff); } - - if (row > getTopRowLogicalIndex()) { - // TODO - getLogger().warning( - "[[spacers]] scrollbar compensation not yet " - + "implemented"); - } } private List getVisibleRowsAfter(int logicalRow) { @@ -4302,13 +4320,80 @@ public class Escalator extends Widget implements RequiresResize, assert height >= 0 : "Height must be more >= 0 (was " + height + ")"; - double diff = height - Math.max(0, this.height); + final double heightDiff = height - Math.max(0, this.height); + final double oldHeight = this.height; + this.height = height; root.getStyle().setHeight(height, Unit.PX); - shiftSpacerPositions(getRow(), diff); - body.shiftRowPositions(getRow(), diff); - body.verifyEscalatorCount(); + // move the visible spacers getRow row onwards. + shiftSpacerPositions(getRow(), heightDiff); + + /* + * If we're growing, we'll adjust the scroll size first, then + * adjust scrolling. If we're shrinking, we do it after the + * second if-clause. + */ + boolean spacerIsGrowing = heightDiff > 0; + if (spacerIsGrowing) { + verticalScrollbar.setScrollSize(verticalScrollbar + .getScrollSize() + heightDiff); + } + + boolean viewportNeedsScrolling = getRow() < body + .getTopRowLogicalIndex(); + if (viewportNeedsScrolling) { + + /* + * We can't use adjustScrollPos here, probably because of a + * bookkeeping-related race condition. + * + * This particular situation is easier, however, since we + * know exactly how many pixels we need to move (heightDiff) + * and all elements below the spacer always need to move + * that pixel amount. + */ + + for (TableRowElement row : body.visualRowOrder) { + body.setRowPosition(row, 0, body.getRowTop(row) + + heightDiff); + } + + double top = getTop(); + double bottom = top + oldHeight; + double scrollTop = verticalScrollbar.getScrollPos(); + + boolean viewportTopIsAtMidSpacer = top < scrollTop + && scrollTop < bottom; + + final double moveDiff; + if (viewportTopIsAtMidSpacer && !spacerIsGrowing) { + + /* + * If the scroll top is in the middle of the modified + * spacer, we want to scroll the viewport up as usual, + * but we don't want to scroll past the top of it. + * + * Math.max ensures this (remember: the result is going + * to be negative). + */ + + moveDiff = Math.max(heightDiff, top - scrollTop); + } else { + moveDiff = heightDiff; + } + body.setBodyScrollPosition(tBodyScrollLeft, tBodyScrollTop + + moveDiff); + verticalScrollbar.setScrollPosByDelta(moveDiff); + + } else { + body.shiftRowPositions(getRow(), heightDiff); + } + + if (!spacerIsGrowing) { + verticalScrollbar.setScrollSize(verticalScrollbar + .getScrollSize() + heightDiff); + } } @Override -- cgit v1.2.3 From 6db686cd52e2c72387f555523eedf4f81708d0ab Mon Sep 17 00:00:00 2001 From: Pekka Hyvönen Date: Tue, 24 Feb 2015 10:56:00 +0200 Subject: Auto scroll when dnd reorder columns and cursor close to Grid edge. (#16643) Change-Id: I38c18d44afb6666677c1593541021d36c8cb2afc --- .../vaadin/client/ui/dd/DragAndDropHandler.java | 45 +- .../vaadin/client/widget/grid/AutoScroller.java | 649 +++++++++++++++++++++ client/src/com/vaadin/client/widgets/Grid.java | 53 +- 3 files changed, 720 insertions(+), 27 deletions(-) create mode 100644 client/src/com/vaadin/client/widget/grid/AutoScroller.java (limited to 'client/src') diff --git a/client/src/com/vaadin/client/ui/dd/DragAndDropHandler.java b/client/src/com/vaadin/client/ui/dd/DragAndDropHandler.java index c8b651bd4e..eec52d5def 100644 --- a/client/src/com/vaadin/client/ui/dd/DragAndDropHandler.java +++ b/client/src/com/vaadin/client/ui/dd/DragAndDropHandler.java @@ -47,8 +47,11 @@ public class DragAndDropHandler { public interface DragAndDropCallback { /** * Called when the drag has started. + * + * @param startEvent + * the original event that started the drag */ - void showDragElement(); + void onDragStart(NativeEvent startEvent); /** * Called on drag. @@ -56,12 +59,12 @@ public class DragAndDropHandler { * @param event * the event related to the drag */ - void updateDragElement(NativePreviewEvent event); + void onDragUpdate(NativePreviewEvent event); /** - * Called when the has ended on a drop or cancel. + * Called after the has ended on a drop or cancel. */ - void removeDragElement(); + void onDragEnd(); /** * Called when the drag has ended on a drop. @@ -69,7 +72,7 @@ public class DragAndDropHandler { void onDrop(); /** - * Called when the drag has been canceled before drop. + * Called when the drag has been canceled. */ void onDragCancel(); } @@ -97,7 +100,7 @@ public class DragAndDropHandler { break; case Event.ONMOUSEMOVE: case Event.ONTOUCHMOVE: - callback.updateDragElement(event); + callback.onDragUpdate(event); // prevent text selection on IE event.getNativeEvent().preventDefault(); break; @@ -109,7 +112,7 @@ public class DragAndDropHandler { event.getNativeEvent().preventDefault(); //$FALL-THROUGH$ case Event.ONMOUSEUP: - callback.updateDragElement(event); + callback.onDragUpdate(event); callback.onDrop(); stopDrag(); event.cancel(); @@ -122,14 +125,6 @@ public class DragAndDropHandler { } } - private void cancelDrag(NativePreviewEvent event) { - callback.onDragCancel(); - callback.removeDragElement(); - stopDrag(); - event.cancel(); - event.getNativeEvent().preventDefault(); - } - }; private static Logger getLogger() { @@ -194,7 +189,7 @@ public class DragAndDropHandler { if (Math.abs(startX - currentX) > 3 || Math.abs(startY - currentY) > 3) { removeNativePreviewHandlerRegistration(); - startDrag(event, callback); + startDrag(dragStartingEvent, event, callback); } break; default: @@ -207,15 +202,15 @@ public class DragAndDropHandler { }); } - private void startDrag(NativePreviewEvent event, - DragAndDropCallback callback) { + private void startDrag(NativeEvent startEvent, + NativePreviewEvent triggerEvent, DragAndDropCallback callback) { dragging = true; // just capture something to prevent text selection in IE Event.setCapture(RootPanel.getBodyElement()); this.callback = callback; dragHandlerRegistration = Event.addNativePreviewHandler(dragHandler); - callback.showDragElement(); - callback.updateDragElement(event); + callback.onDragStart(startEvent); + callback.onDragUpdate(triggerEvent); } private void stopDrag() { @@ -226,11 +221,19 @@ public class DragAndDropHandler { } Event.releaseCapture(RootPanel.getBodyElement()); if (callback != null) { - callback.removeDragElement(); + callback.onDragEnd(); callback = null; } } + private void cancelDrag(NativePreviewEvent event) { + callback.onDragCancel(); + callback.onDragEnd(); + stopDrag(); + event.cancel(); + event.getNativeEvent().preventDefault(); + } + private void removeNativePreviewHandlerRegistration() { if (dragStartNativePreviewHandlerRegistration != null) { dragStartNativePreviewHandlerRegistration.removeHandler(); diff --git a/client/src/com/vaadin/client/widget/grid/AutoScroller.java b/client/src/com/vaadin/client/widget/grid/AutoScroller.java new file mode 100644 index 0000000000..8f4e2036fc --- /dev/null +++ b/client/src/com/vaadin/client/widget/grid/AutoScroller.java @@ -0,0 +1,649 @@ +/* + * 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; + +import com.google.gwt.animation.client.AnimationScheduler; +import com.google.gwt.animation.client.AnimationScheduler.AnimationCallback; +import com.google.gwt.animation.client.AnimationScheduler.AnimationHandle; +import com.google.gwt.dom.client.BrowserEvents; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.dom.client.TableElement; +import com.google.gwt.dom.client.TableSectionElement; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.Event.NativePreviewEvent; +import com.google.gwt.user.client.Event.NativePreviewHandler; +import com.vaadin.client.WidgetUtil; +import com.vaadin.client.widgets.Grid; + +/** + * A class for handling automatic scrolling vertically / horizontally in the + * Grid when the cursor is close enough the edge of the body of the grid, + * depending on the scroll direction chosen. + * + * @since + * @author Vaadin Ltd + */ +public class AutoScroller { + + /** + * Callback that notifies when the cursor is on top of a new row or column + * because of the automatic scrolling. + * + * @since + */ + public interface AutoScrollerCallback { + + /** + * Triggered when doing automatic horizontal scrolling. + * + * @param scrollDiff + * the amount of pixels that have been auto scrolled since + * last call + */ + void onHorizontalAutoScroll(int scrollDiff); + + /** + * Triggered when doing automatic vertical scrolling. + * + * @param scrollDiff + * the amount of pixels that have been auto scrolled since + * last call + */ + void onVerticalAutoScroll(int scrollDiff); + } + + public enum ScrollAxis { + VERTICAL, HORIZONTAL + }; + + /** The maximum number of pixels per second to autoscroll. */ + private static final int SCROLL_TOP_SPEED_PX_SEC = 500; + + /** + * The minimum area where the grid doesn't scroll while the pointer is + * pressed. + */ + private static final int MIN_NO_AUTOSCROLL_AREA_PX = 50; + + /** The size of the autoscroll area, both top/left and bottom/right. */ + private int scrollAreaPX = 100; + + /** + * This class's main objective is to listen when to stop autoscrolling, and + * make sure everything stops accordingly. + */ + private class TouchEventHandler implements NativePreviewHandler { + @Override + public void onPreviewNativeEvent(final NativePreviewEvent event) { + /* + * Remember: targetElement is always where touchstart started, not + * where the finger is pointing currently. + */ + switch (event.getTypeInt()) { + case Event.ONTOUCHSTART: { + if (event.getNativeEvent().getTouches().length() == 1) { + /* + * Something has dropped a touchend/touchcancel and the + * scroller is most probably running amok. Let's cancel it + * and pretend that everything's going as expected + * + * Because this is a preview, this code is run before start + * event can be passed to the start(...) method. + */ + stop(); + + /* + * Related TODO: investigate why iOS seems to ignore a + * touchend/touchcancel when frames are dropped, and/or if + * something can be done about that. + */ + } + break; + } + + case Event.ONTOUCHMOVE: + event.cancel(); + break; + + case Event.ONTOUCHEND: + case Event.ONTOUCHCANCEL: + // TODO investigate if this works as desired + stop(); + break; + } + } + + } + + /** + * This class's responsibility is to scroll the table while a pointer is + * kept in a scrolling zone. + *

+ * Techical note: This class is an AnimationCallback because we + * need a timer: when the finger is kept in place while the grid scrolls, we + * still need to be able to make new selections. So, instead of relying on + * events (which won't be fired, since the pointer isn't necessarily + * moving), we do this check on each frame while the pointer is "active" + * (mouse is pressed, finger is on screen). + */ + private class AutoScrollingFrame implements AnimationCallback { + + /** + * If the acceleration gradient area is smaller than this, autoscrolling + * will be disabled (it becomes too quick to accelerate to be usable). + */ + private static final int GRADIENT_MIN_THRESHOLD_PX = 10; + + /** + * The speed at which the gradient area recovers, once scrolling in that + * direction has started. + */ + private static final int SCROLL_AREA_REBOUND_PX_PER_SEC = 1; + private static final double SCROLL_AREA_REBOUND_PX_PER_MS = SCROLL_AREA_REBOUND_PX_PER_SEC / 1000.0d; + + /** + * The lowest y/x-coordinate on the {@link Event#getClientY() client-y} + * or {@link Event#getClientX() client-x} from where we need to start + * scrolling towards the top/left. + */ + private int startBound = -1; + + /** + * The highest y/x-coordinate on the {@link Event#getClientY() client-y} + * or {@link Event#getClientX() client-x} from where we need to + * scrolling towards the bottom. + */ + private int endBound = -1; + + /** + * The area where the selection acceleration takes place. If < + * {@link #GRADIENT_MIN_THRESHOLD_PX}, autoscrolling is disabled + */ + private final int gradientArea; + + /** + * The number of pixels per seconds we currently are scrolling (negative + * is towards the top/left, positive is towards the bottom/right). + */ + private double scrollSpeed = 0; + + private double prevTimestamp = 0; + + /** + * This field stores fractions of pixels to scroll, to make sure that + * we're able to scroll less than one px per frame. + */ + private double pixelsToScroll = 0.0d; + + /** Should this animator be running. */ + private boolean running = false; + + /** The handle in which this instance is running. */ + private AnimationHandle handle; + + /** + * The pointer's pageY (VERTICAL) / pageX (HORIZONTAL) coordinate + * depending on scrolling axis. + */ + private int scrollingAxisPageCoordinate; + + /** @see #doScrollAreaChecks(int) */ + private int finalStartBound; + + /** @see #doScrollAreaChecks(int) */ + private int finalEndBound; + + private boolean scrollAreaShouldRebound = false; + + public AutoScrollingFrame(final int startBound, final int endBound, + final int gradientArea) { + finalStartBound = startBound; + finalEndBound = endBound; + this.gradientArea = gradientArea; + } + + @Override + public void execute(final double timestamp) { + final double timeDiff = timestamp - prevTimestamp; + prevTimestamp = timestamp; + + reboundScrollArea(timeDiff); + + pixelsToScroll += scrollSpeed * (timeDiff / 1000.0d); + final int intPixelsToScroll = (int) pixelsToScroll; + pixelsToScroll -= intPixelsToScroll; + + if (intPixelsToScroll != 0) { + if (scrollDirection == ScrollAxis.VERTICAL) { + grid.setScrollTop(grid.getScrollTop() + intPixelsToScroll); + callback.onVerticalAutoScroll(intPixelsToScroll); + } else { + grid.setScrollLeft(grid.getScrollLeft() + intPixelsToScroll); + callback.onHorizontalAutoScroll(intPixelsToScroll); + } + } + + reschedule(); + } + + /** + * If the scroll are has been offset by the pointer starting out there, + * move it back a bit + */ + private void reboundScrollArea(double timeDiff) { + if (!scrollAreaShouldRebound) { + return; + } + + int reboundPx = (int) Math.ceil(SCROLL_AREA_REBOUND_PX_PER_MS + * timeDiff); + if (startBound < finalStartBound) { + startBound += reboundPx; + startBound = Math.min(startBound, finalStartBound); + updateScrollSpeed(scrollingAxisPageCoordinate); + } else if (endBound > finalEndBound) { + endBound -= reboundPx; + endBound = Math.max(endBound, finalEndBound); + updateScrollSpeed(scrollingAxisPageCoordinate); + } + } + + private void updateScrollSpeed(final int pointerPageCordinate) { + + final double ratio; + if (pointerPageCordinate < startBound) { + final double distance = pointerPageCordinate - startBound; + ratio = Math.max(-1, distance / gradientArea); + } + + else if (pointerPageCordinate > endBound) { + final double distance = pointerPageCordinate - endBound; + ratio = Math.min(1, distance / gradientArea); + } + + else { + ratio = 0; + } + + scrollSpeed = ratio * SCROLL_TOP_SPEED_PX_SEC; + } + + public void start() { + running = true; + reschedule(); + } + + public void stop() { + running = false; + + if (handle != null) { + handle.cancel(); + handle = null; + } + } + + private void reschedule() { + if (running && gradientArea >= GRADIENT_MIN_THRESHOLD_PX) { + handle = AnimationScheduler.get().requestAnimationFrame(this, + grid.getElement()); + } + } + + public void updatePointerCoords(int pageX, int pageY) { + final int pageCordinate; + if (scrollDirection == ScrollAxis.VERTICAL) { + pageCordinate = pageY; + } else { + pageCordinate = pageX; + } + doScrollAreaChecks(pageCordinate); + updateScrollSpeed(pageCordinate); + scrollingAxisPageCoordinate = pageCordinate; + } + + /** + * This method checks whether the first pointer event started in an area + * that would start scrolling immediately, and does some actions + * accordingly. + *

+ * If it is, that scroll area will be offset "beyond" the pointer (above + * if pointer is towards the top/left, otherwise below/right). + */ + private void doScrollAreaChecks(int pageCordinate) { + /* + * The first run makes sure that neither scroll position is + * underneath the finger, but offset to either direction from + * underneath the pointer. + */ + if (startBound == -1) { + startBound = Math.min(finalStartBound, pageCordinate); + endBound = Math.max(finalEndBound, pageCordinate); + } + + /* + * Subsequent runs make sure that the scroll area grows (but doesn't + * shrink) with the finger, but no further than the final bound. + */ + else { + int oldTopBound = startBound; + if (startBound < finalStartBound) { + startBound = Math.max(startBound, + Math.min(finalStartBound, pageCordinate)); + } + + int oldBottomBound = endBound; + if (endBound > finalEndBound) { + endBound = Math.min(endBound, + Math.max(finalEndBound, pageCordinate)); + } + + final boolean startDidNotMove = oldTopBound == startBound; + final boolean endDidNotMove = oldBottomBound == endBound; + final boolean wasMovement = pageCordinate != scrollingAxisPageCoordinate; + scrollAreaShouldRebound = (startDidNotMove && endDidNotMove && wasMovement); + } + } + } + + /** + * This handler makes sure that pointer movements are handled. + *

+ * Essentially, a native preview handler is registered (so that selection + * gestures can happen outside of the selection column). The handler itself + * makes sure that it's detached when the pointer is "lifted". + */ + private final NativePreviewHandler scrollPreviewHandler = new NativePreviewHandler() { + @Override + public void onPreviewNativeEvent(final NativePreviewEvent event) { + if (autoScroller == null) { + stop(); + return; + } + + final NativeEvent nativeEvent = event.getNativeEvent(); + int pageY = 0; + int pageX = 0; + switch (event.getTypeInt()) { + case Event.ONMOUSEMOVE: + case Event.ONTOUCHMOVE: + pageY = WidgetUtil.getTouchOrMouseClientY(nativeEvent); + pageX = WidgetUtil.getTouchOrMouseClientX(nativeEvent); + autoScroller.updatePointerCoords(pageX, pageY); + break; + case Event.ONMOUSEUP: + case Event.ONTOUCHEND: + case Event.ONTOUCHCANCEL: + stop(); + break; + } + } + }; + /** The registration info for {@link #scrollPreviewHandler} */ + private HandlerRegistration handlerRegistration; + + /** + * The top/left bound, as calculated from the {@link Event#getClientY() + * client-y} or {@link Event#getClientX() client-x} coordinates. + */ + private double startingBound = -1; + + /** + * The bottom/right bound, as calculated from the {@link Event#getClientY() + * client-y} or or {@link Event#getClientX() client-x} coordinates. + */ + private int endingBound = -1; + + /** The size of the autoscroll acceleration area. */ + private int gradientArea; + + private Grid grid; + + private HandlerRegistration nativePreviewHandlerRegistration; + + private ScrollAxis scrollDirection; + + private AutoScrollingFrame autoScroller; + + private AutoScrollerCallback callback; + + /** + * Creates a new instance for scrolling the given grid. + * + * @param grid + * the grid to auto scroll + */ + public AutoScroller(Grid grid) { + this.grid = grid; + } + + /** + * Starts the automatic scrolling detection. The given event must be a touch + * start or a left mouse triggered mouse down event. + * + * @param startEvent + * the event that starts the automatic scroll + * @param scrollAxis + * the axis along which the scrolling should happen + * @param callback + * the callback for getting info about the automatic scrolling + */ + public void start(final NativeEvent startEvent, ScrollAxis scrollAxis, + AutoScrollerCallback callback) { + if (BrowserEvents.TOUCHSTART.equals(startEvent.getType()) + || (BrowserEvents.MOUSEDOWN.equals(startEvent.getType()) && startEvent + .getButton() == NativeEvent.BUTTON_LEFT)) { + scrollDirection = scrollAxis; + this.callback = callback; + injectNativeHandler(); + start(startEvent); + startEvent.preventDefault(); + startEvent.stopPropagation(); + } else { + throw new IllegalStateException("received unexpected event: " + + startEvent.getType()); + } + } + + /** + * Stops the automatic scrolling. + */ + public void stop() { + if (handlerRegistration != null) { + handlerRegistration.removeHandler(); + handlerRegistration = null; + } + + if (autoScroller != null) { + autoScroller.stop(); + autoScroller = null; + } + + removeNativeHandler(); + } + + /** + * Set the auto scroll area height or width depending on the scrolling axis. + * This is the amount of pixels from the edge of the grid that the scroll is + * triggered. + *

+ * Defaults to 100px. + * + * @param px + * the height/width for the auto scroll area depending on + * direction + */ + public void setScrollAreaPX(int px) { + scrollAreaPX = px; + } + + /** + * Returns the size of the auto scroll area in pixels. + *

+ * Defaults to 100px. + * + * @return size in pixels + */ + public int getScrollAreaPX() { + return scrollAreaPX; + } + + private void start(final NativeEvent event) { + /* + * bounds are updated whenever the autoscroll cycle starts, to make sure + * that the widget hasn't changed in size, moved around, or whatnot. + */ + updateScrollBounds(); + + assert handlerRegistration == null : "handlerRegistration was not null"; + assert autoScroller == null : "autoScroller was not null"; + handlerRegistration = Event + .addNativePreviewHandler(scrollPreviewHandler); + autoScroller = new AutoScrollingFrame((int) Math.ceil(startingBound), + endingBound, gradientArea); + autoScroller.start(); + } + + private void updateScrollBounds() { + double startBorder = getBodyClientStart(); + final int endBorder = getBodyClientEnd(); + + for (int i = 0; i < grid.getFrozenColumnCount(); i++) { + startBorder += grid.getColumn(i).getWidthActual(); + } + + final int scrollCompensation = getScrollCompensation(); + startingBound = scrollCompensation + startBorder + scrollAreaPX; + endingBound = scrollCompensation + endBorder - scrollAreaPX; + gradientArea = scrollAreaPX; + + // modify bounds if they're too tightly packed + if (endingBound - startingBound < MIN_NO_AUTOSCROLL_AREA_PX) { + double adjustment = MIN_NO_AUTOSCROLL_AREA_PX + - (endingBound - startingBound); + startingBound -= adjustment / 2; + endingBound += adjustment / 2; + gradientArea -= adjustment / 2; + } + } + + private int getScrollCompensation() { + Element cursor = grid.getElement(); + int scroll = 0; + while (cursor != null) { + scroll -= scrollDirection == ScrollAxis.VERTICAL ? cursor + .getScrollTop() : cursor.getScrollLeft(); + cursor = cursor.getParentElement(); + } + + return scroll; + } + + private void injectNativeHandler() { + removeNativeHandler(); + nativePreviewHandlerRegistration = Event + .addNativePreviewHandler(new TouchEventHandler()); + } + + private void removeNativeHandler() { + if (nativePreviewHandlerRegistration != null) { + nativePreviewHandlerRegistration.removeHandler(); + nativePreviewHandlerRegistration = null; + } + } + + private TableElement getTableElement() { + final Element root = grid.getElement(); + final Element tablewrapper = Element.as(root.getChild(2)); + if (tablewrapper != null) { + return TableElement.as(tablewrapper.getFirstChildElement()); + } else { + return null; + } + } + + private TableSectionElement getTbodyElement() { + TableElement table = getTableElement(); + if (table != null) { + return table.getTBodies().getItem(0); + } else { + return null; + } + } + + private TableSectionElement getTheadElement() { + TableElement table = getTableElement(); + if (table != null) { + return table.getTHead(); + } else { + return null; + } + } + + private TableSectionElement getTfootElement() { + TableElement table = getTableElement(); + if (table != null) { + return table.getTFoot(); + } else { + return null; + } + } + + /** Get the "top" of an element in relation to "client" coordinates. */ + @SuppressWarnings("static-method") + private int getClientTop(final Element e) { + Element cursor = e; + int top = 0; + while (cursor != null) { + top += cursor.getOffsetTop(); + cursor = cursor.getOffsetParent(); + } + return top; + } + + /** Get the "left" of an element in relation to "client" coordinates. */ + @SuppressWarnings("static-method") + private int getClientLeft(final Element e) { + Element cursor = e; + int left = 0; + while (cursor != null) { + left += cursor.getOffsetLeft(); + cursor = cursor.getOffsetParent(); + } + return left; + } + + private int getBodyClientEnd() { + if (scrollDirection == ScrollAxis.VERTICAL) { + return getClientTop(getTfootElement()) - 1; + } else { + TableSectionElement tbodyElement = getTbodyElement(); + return getClientLeft(tbodyElement) + tbodyElement.getOffsetWidth() + - 1; + } + + } + + private int getBodyClientStart() { + if (scrollDirection == ScrollAxis.VERTICAL) { + return getClientTop(grid.getElement()) + + getTheadElement().getOffsetHeight(); + } else { + return getClientLeft(getTbodyElement()); + } + } +} diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index d54c023e3d..514cb6185c 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -83,6 +83,9 @@ 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.grid.AutoScroller; +import com.vaadin.client.widget.grid.AutoScroller.AutoScrollerCallback; +import com.vaadin.client.widget.grid.AutoScroller.ScrollAxis; import com.vaadin.client.widget.grid.CellReference; import com.vaadin.client.widget.grid.CellStyleGenerator; import com.vaadin.client.widget.grid.DataAvailableEvent; @@ -2865,8 +2868,22 @@ public class Grid extends ResizeComposite implements private DragAndDropHandler dndHandler = new DragAndDropHandler(); + private AutoScroller autoScroller = new AutoScroller(this); + private DragAndDropCallback headerCellDndCallback = new DragAndDropCallback() { + private final AutoScrollerCallback autoScrollerCallback = new AutoScrollerCallback() { + + @Override + public void onVerticalAutoScroll(int scrollDiff) { + // NOP + } + + @Override + public void onHorizontalAutoScroll(int scrollDiff) { + onDragUpdate(null); + } + }; /** * Elements for displaying the dragged column(s) and drop marker * properly @@ -2884,6 +2901,8 @@ public class Grid extends ResizeComposite implements */ private HandlerRegistration columnSortPreventRegistration; + private int clientX; + private void initHeaderDragElementDOM() { if (table == null) { tableHeader = DOM.createTHead(); @@ -2902,9 +2921,11 @@ public class Grid extends ResizeComposite implements } @Override - public void updateDragElement(NativePreviewEvent event) { - int clientX = WidgetUtil.getTouchOrMouseClientX(event - .getNativeEvent()); + public void onDragUpdate(NativePreviewEvent event) { + if (event != null) { + clientX = WidgetUtil.getTouchOrMouseClientX(event + .getNativeEvent()); + } resolveDragElementHorizontalPosition(clientX); updateDragDropMarker(clientX); } @@ -2940,7 +2961,7 @@ public class Grid extends ResizeComposite implements } @Override - public void showDragElement() { + public void onDragStart(NativeEvent startingEvent) { initHeaderDragElementDOM(); // needs to clone focus and sorting indicators too (UX) dragElement = DOM.clone(eventCell.getElement(), true); @@ -2952,10 +2973,14 @@ public class Grid extends ResizeComposite implements eventCell.getElement().addClassName("dragged"); // mark the floating cell, for styling & testing dragElement.addClassName("dragged-column-header"); + // start the auto scroll handler + autoScroller.setScrollAreaPX(60); + autoScroller.start(startingEvent, ScrollAxis.HORIZONTAL, + autoScrollerCallback); } @Override - public void removeDragElement() { + public void onDragEnd() { table.removeFromParent(); dragElement.removeFromParent(); eventCell.getElement().removeClassName("dragged"); @@ -2977,8 +3002,8 @@ public class Grid extends ResizeComposite implements @SuppressWarnings("unchecked") Column[] array = reordered.toArray(new Column[reordered .size()]); - transferCellFocusOnDrop(); setColumnOrder(array); + transferCellFocusOnDrop(); } // else no reordering } @@ -3007,6 +3032,9 @@ public class Grid extends ResizeComposite implements && draggedColumnIndex < focusedCellColumnIndex) { cellFocusHandler.setCellFocus(focusedRowIndex, focusedCellColumnIndex - 1, rowContainer); + } else { + cellFocusHandler.setCellFocus(focusedRowIndex, + focusedCellColumnIndex, rowContainer); } } } @@ -3032,7 +3060,9 @@ public class Grid extends ResizeComposite implements } }); } + autoScroller.stop(); } + }; /** @@ -4972,6 +5002,17 @@ public class Grid extends ResizeComposite implements return escalator.getScrollTop(); } + /** + * Sets the horizontal scroll offset + * + * @since + * @param px + * the number of pixels this grid should be scrolled right + */ + public void setScrollLeft(double px) { + escalator.setScrollLeft(px); + } + /** * Gets the horizontal scroll offset * -- cgit v1.2.3 From e88d99ca21c25067960836d520dda8b96f23b04d Mon Sep 17 00:00:00 2001 From: Henrik Paul Date: Wed, 25 Feb 2015 09:34:36 +0200 Subject: Fixes a bug with scrolling the Escalator with a spacer open (#16644) Mostly cleanup/clarity enhancements, the actual bug fix is in getSpacerHeightsSumUntilPx Change-Id: Ie9aafc0398e4ed293067c9aeac6281abc0c1df82 --- .../src/com/vaadin/client/widgets/Escalator.java | 52 +++++++++++++++------- 1 file changed, 37 insertions(+), 15 deletions(-) (limited to 'client/src') diff --git a/client/src/com/vaadin/client/widgets/Escalator.java b/client/src/com/vaadin/client/widgets/Escalator.java index 185744d2ec..dcc6c98960 100644 --- a/client/src/com/vaadin/client/widgets/Escalator.java +++ b/client/src/com/vaadin/client/widgets/Escalator.java @@ -1913,6 +1913,18 @@ public class Escalator extends Widget implements RequiresResize, positions.set(tr, x, y); } + /** + * Returns the assigned top position for the given element. + *

+ * Note: This method does not calculate what a row's top + * position should be. It just returns an assigned value, correct or + * not. + * + * @param tr + * the table row element to measure + * @return the current top position for {@code tr} + * @see BodyRowContainerImpl#getRowTop(int) + */ protected double getRowTop(final TableRowElement tr) { return positions.getTop(tr); } @@ -2564,12 +2576,8 @@ public class Escalator extends Widget implements RequiresResize, } private int getLogicalRowIndex(final double px) { - /* - * FIXME: this is buggy! if px is mid-spacer, it will return the - * pixel count up until PX, not the one after (or before) it. - */ double rowPx = px - - spacerContainer.getSpacerCompleteHeightsSumUntilPx(px); + - spacerContainer.getSpacerHeightsSumUntilPx(px); return (int) (rowPx / getDefaultRowHeight()); } @@ -2685,12 +2693,15 @@ public class Escalator extends Widget implements RequiresResize, + visualSourceRange.getStart() + ")"; assert logicalTargetIndex >= 0 : "Logical target must be 0 or " - + "greater"; + + "greater (was " + logicalTargetIndex + ")"; - assert visualTargetIndex >= 0 : "Visual target must be 0 or greater"; + assert visualTargetIndex >= 0 : "Visual target must be 0 or greater (was " + + visualTargetIndex + ")"; assert visualTargetIndex <= root.getChildCount() : "Visual target " - + "must not be greater than the number of escalator rows"; + + "must not be greater than the number of escalator rows (was " + + visualTargetIndex + ", escalator rows " + + root.getChildCount() + ")"; assert logicalTargetIndex + visualSourceRange.length() <= getRowCount() : "Logical " + "target leads to rows outside of the data range (" @@ -3745,6 +3756,20 @@ public class Escalator extends Widget implements RequiresResize, return spacerContainer.getSpacerUpdater(); } + /** + * Calculates the correct top position of a row at a logical + * index, regardless if there is one there or not. + *

+ * A correct result requires that both {@link #getDefaultRowHeight()} is + * consistent, and the placement and height of all spacers above the + * given logical index are consistent. + * + * @param logicalIndex + * the logical index of the row for which to calculate the + * top position + * @return the position at which to place a row in {@code logicalIndex} + * @see #getRowTop(TableRowElement) + */ private double getRowTop(int logicalIndex) { double top = spacerContainer .getSpacerHeightsSumUntilIndex(logicalIndex); @@ -4564,18 +4589,15 @@ public class Escalator extends Widget implements RequiresResize, /** * Gets the amount of pixels occupied by spacers from the top until a * certain spot from the top of the body. - *

- * If a spacer lies in the middle of {@code px}, then the entire height - * of the spacer will be taken into account. * * @param px * pixels counted from the top - * @return the pixels occupied by entire spacers up until {@code px} + * @return the pixels occupied by spacers up until {@code px} */ - public double getSpacerCompleteHeightsSumUntilPx(double px) { + public double getSpacerHeightsSumUntilPx(double px) { return getSpacerHeightsSumBetweenPx(0, - SpacerMeasurementStrategy.COMPLETE, px, - SpacerMeasurementStrategy.COMPLETE); + SpacerMeasurementStrategy.PARTIAL, px, + SpacerMeasurementStrategy.PARTIAL); } /** -- cgit v1.2.3 From c2fdfcab42f663e46d8f5bff84aa7ca90f7a531e Mon Sep 17 00:00:00 2001 From: Pekka Hyvönen Date: Wed, 25 Feb 2015 09:22:06 +0200 Subject: Adjust column reorder drop marker when auto scrolling grid. (#16643) Change-Id: I11377fb8b007c3c50c3e3bd1c29afcf8485431f0 --- .../vaadin/client/widget/grid/AutoScroller.java | 75 ++++++++++++++++------ .../src/com/vaadin/client/widgets/Escalator.java | 22 +++++++ client/src/com/vaadin/client/widgets/Grid.java | 50 +++++++++++---- 3 files changed, 118 insertions(+), 29 deletions(-) (limited to 'client/src') diff --git a/client/src/com/vaadin/client/widget/grid/AutoScroller.java b/client/src/com/vaadin/client/widget/grid/AutoScroller.java index 8f4e2036fc..60c9535ca7 100644 --- a/client/src/com/vaadin/client/widget/grid/AutoScroller.java +++ b/client/src/com/vaadin/client/widget/grid/AutoScroller.java @@ -43,28 +43,34 @@ public class AutoScroller { /** * Callback that notifies when the cursor is on top of a new row or column * because of the automatic scrolling. - * - * @since */ public interface AutoScrollerCallback { /** - * Triggered when doing automatic horizontal scrolling. + * Triggered when doing automatic scrolling. + *

+ * Because the auto scroller currently only supports scrolling in one + * axis, this method is used for both vertical and horizontal scrolling. * * @param scrollDiff * the amount of pixels that have been auto scrolled since * last call */ - void onHorizontalAutoScroll(int scrollDiff); + void onAutoScroll(int scrollDiff); /** - * Triggered when doing automatic vertical scrolling. - * - * @param scrollDiff - * the amount of pixels that have been auto scrolled since - * last call + * Triggered when the grid scroll has reached the minimum scroll + * position. Depending on the scroll axis, either scrollLeft or + * scrollTop is 0. */ - void onVerticalAutoScroll(int scrollDiff); + void onAutoScrollReachedMin(); + + /** + * Triggered when the grid scroll has reached the max scroll position. + * Depending on the scroll axis, either scrollLeft or scrollTop is at + * its maximum value. + */ + void onAutoScrollReachedMax(); } public enum ScrollAxis { @@ -229,12 +235,30 @@ public class AutoScroller { pixelsToScroll -= intPixelsToScroll; if (intPixelsToScroll != 0) { + double scrollPos; + double maxScrollPos; + double newScrollPos; if (scrollDirection == ScrollAxis.VERTICAL) { - grid.setScrollTop(grid.getScrollTop() + intPixelsToScroll); - callback.onVerticalAutoScroll(intPixelsToScroll); + scrollPos = grid.getScrollTop(); + maxScrollPos = getMaxScrollTop(); } else { - grid.setScrollLeft(grid.getScrollLeft() + intPixelsToScroll); - callback.onHorizontalAutoScroll(intPixelsToScroll); + scrollPos = grid.getScrollLeft(); + maxScrollPos = getMaxScrollLeft(); + } + if (intPixelsToScroll > 0 && scrollPos < maxScrollPos + || intPixelsToScroll < 0 && scrollPos > 0) { + newScrollPos = scrollPos + intPixelsToScroll; + if (scrollDirection == ScrollAxis.VERTICAL) { + grid.setScrollTop(newScrollPos); + } else { + grid.setScrollLeft(newScrollPos); + } + callback.onAutoScroll(intPixelsToScroll); + if (newScrollPos <= 0) { + callback.onAutoScrollReachedMin(); + } else if (newScrollPos >= maxScrollPos) { + callback.onAutoScrollReachedMax(); + } } } @@ -521,10 +545,7 @@ public class AutoScroller { private void updateScrollBounds() { double startBorder = getBodyClientStart(); final int endBorder = getBodyClientEnd(); - - for (int i = 0; i < grid.getFrozenColumnCount(); i++) { - startBorder += grid.getColumn(i).getWidthActual(); - } + startBorder += getFrozenColumnsWidth(); final int scrollCompensation = getScrollCompensation(); startingBound = scrollCompensation + startBorder + scrollAreaPX; @@ -646,4 +667,22 @@ public class AutoScroller { return getClientLeft(getTbodyElement()); } } + + private double getFrozenColumnsWidth() { + double value = 0; + for (int i = 0; i < grid.getFrozenColumnCount(); i++) { + value += grid.getColumn(i).getWidthActual(); + } + return value; + } + + private double getMaxScrollLeft() { + return grid.getScrollWidth() + - (getTableElement().getParentElement().getOffsetWidth() - getFrozenColumnsWidth()); + } + + private double getMaxScrollTop() { + return grid.getScrollHeight() - getTfootElement().getOffsetHeight() + - getTheadElement().getOffsetHeight(); + } } diff --git a/client/src/com/vaadin/client/widgets/Escalator.java b/client/src/com/vaadin/client/widgets/Escalator.java index ca54b97ca5..c9a1efde98 100644 --- a/client/src/com/vaadin/client/widgets/Escalator.java +++ b/client/src/com/vaadin/client/widgets/Escalator.java @@ -4713,6 +4713,28 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker horizontalScrollbar.setScrollPos(scrollLeft); } + /** + * Returns the scroll width for the escalator. Note that this is not + * necessary the same as {@code Element.scrollWidth} in the DOM. + * + * @since + * @return the scroll width in pixels + */ + public double getScrollWidth() { + return horizontalScrollbar.getScrollSize(); + } + + /** + * Returns the scroll height for the escalator. Note that this is not + * necessary the same as {@code Element.scrollHeight} in the DOM. + * + * @since + * @return the scroll height in pixels + */ + public double getScrollHeight() { + return verticalScrollbar.getScrollSize(); + } + /** * Scrolls the body horizontally so that the column at the given index is * visible and there is at least {@code padding} pixels in the direction of diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index 514cb6185c..1d3f090a62 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -2875,13 +2875,23 @@ public class Grid extends ResizeComposite implements private final AutoScrollerCallback autoScrollerCallback = new AutoScrollerCallback() { @Override - public void onVerticalAutoScroll(int scrollDiff) { - // NOP + public void onAutoScroll(int scrollDiff) { + autoScrollX = scrollDiff; + onDragUpdate(null); } @Override - public void onHorizontalAutoScroll(int scrollDiff) { - onDragUpdate(null); + public void onAutoScrollReachedMin() { + // make sure the drop marker is visible on the left + autoScrollX = 0; + updateDragDropMarker(clientX); + } + + @Override + public void onAutoScrollReachedMax() { + // make sure the drop marker is visible on the right + autoScrollX = 0; + updateDragDropMarker(clientX); } }; /** @@ -2903,6 +2913,9 @@ public class Grid extends ResizeComposite implements private int clientX; + /** How much the grid is being auto scrolled while dragging. */ + private int autoScrollX; + private void initHeaderDragElementDOM() { if (table == null) { tableHeader = DOM.createTHead(); @@ -2925,6 +2938,7 @@ public class Grid extends ResizeComposite implements if (event != null) { clientX = WidgetUtil.getTouchOrMouseClientX(event .getNativeEvent()); + autoScrollX = 0; } resolveDragElementHorizontalPosition(clientX); updateDragDropMarker(clientX); @@ -2946,6 +2960,7 @@ public class Grid extends ResizeComposite implements dropMarkerLeft += cellWidth; } } + dropMarkerLeft += autoScrollX; if (dropMarkerLeft > header.getElement().getOffsetWidth() || dropMarkerLeft < 0) { dropMarkerLeft = -10000000; @@ -3014,10 +3029,6 @@ public class Grid extends ResizeComposite implements 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) { @@ -3032,9 +3043,6 @@ public class Grid extends ResizeComposite implements && draggedColumnIndex < focusedCellColumnIndex) { cellFocusHandler.setCellFocus(focusedRowIndex, focusedCellColumnIndex - 1, rowContainer); - } else { - cellFocusHandler.setCellFocus(focusedRowIndex, - focusedCellColumnIndex, rowContainer); } } } @@ -5022,6 +5030,26 @@ public class Grid extends ResizeComposite implements return escalator.getScrollLeft(); } + /** + * Returns the height of the scrollable area in pixels. + * + * @since + * @return the height of the scrollable area in pixels + */ + public double getScrollHeight() { + return escalator.getScrollHeight(); + } + + /** + * Returns the width of the scrollable area in pixels. + * + * @since + * @return the width of the scrollable area in pixels. + */ + public double getScrollWidth() { + return escalator.getScrollWidth(); + } + private static final Logger getLogger() { return Logger.getLogger(Grid.class.getName()); } -- cgit v1.2.3 From b90e4d307c286a56a38fc3cc2ec2dd73017e9458 Mon Sep 17 00:00:00 2001 From: Pekka Hyvönen Date: Wed, 25 Feb 2015 16:16:10 +0200 Subject: Column reordering in Grid restricted to non-frozen columns. (#16643) Can't drag frozen columns. Dragging no top of frozen column will keep drag element and drop marker on "right". Dropping on frozen column will drop the column to the right of the frozen columns. Change-Id: Ib04af96b21072e9f19dc7640a67bc68d49a9ae54 --- client/src/com/vaadin/client/widgets/Grid.java | 23 +++++++++- .../grid/basicfeatures/GridBasicFeaturesTest.java | 7 ++- .../grid/basicfeatures/GridColumnReorderTest.java | 10 ++--- .../server/GridColumnReorderTest.java | 52 +++++++++++++++++----- 4 files changed, 74 insertions(+), 18 deletions(-) (limited to 'client/src') diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index 1d3f090a62..fc208843e4 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -2961,7 +2961,16 @@ public class Grid extends ResizeComposite implements } } dropMarkerLeft += autoScrollX; - if (dropMarkerLeft > header.getElement().getOffsetWidth() + + final double frozenColumnsWidth = getFrozenColumnsWidth(); + if (dropMarkerLeft < frozenColumnsWidth) { + latestColumnDropIndex = getFrozenColumnCount(); + if (getScrollLeft() == 0) { + dropMarkerLeft = frozenColumnsWidth; + } else { + dropMarkerLeft = -10000000; + } + } else if (dropMarkerLeft > header.getElement().getOffsetWidth() || dropMarkerLeft < 0) { dropMarkerLeft = -10000000; } @@ -2971,6 +2980,11 @@ public class Grid extends ResizeComposite implements private void resolveDragElementHorizontalPosition(final int clientX) { int left = clientX - table.getAbsoluteLeft(); left = Math.max(0, Math.min(left, table.getClientWidth())); + final double frozenColumnsWidth = getFrozenColumnsWidth(); + if (left < frozenColumnsWidth) { + left = (int) frozenColumnsWidth; + } + left -= dragElement.getClientWidth() / 2; dragElement.getStyle().setLeft(left, Unit.PX); } @@ -3071,6 +3085,13 @@ public class Grid extends ResizeComposite implements autoScroller.stop(); } + private double getFrozenColumnsWidth() { + double result = 0.0d; + for (int i = 0; i < getFrozenColumnCount(); i++) { + result += getColumn(i).getWidthActual(); + } + return result; + } }; /** 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 e7fbc14d89..d387bbe22f 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeaturesTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeaturesTest.java @@ -140,6 +140,11 @@ public abstract class GridBasicFeaturesTest extends MultiBrowserTest { getGridElement().getCell(row, column).click(); } + protected void setFrozenColumns(int numberOfFrozenColumns) { + selectMenuPath("Component", "State", "Frozen column count", + Integer.toString(numberOfFrozenColumns)); + } + protected void assertColumnHeaderOrder(int... indices) { List headers = getGridHeaderRowCells(); for (int i = 0; i < indices.length; i++) { @@ -158,7 +163,7 @@ public abstract class GridBasicFeaturesTest extends MultiBrowserTest { return headerRowCells.get(index); } - protected void dragDefaultColumnHeader(int draggedColumnHeaderIndex, + protected void dragAndDropDefaultColumnHeader(int draggedColumnHeaderIndex, int onTopOfColumnHeaderIndex, int xOffsetFromColumnTopLeftCorner) { new Actions(getDriver()) .clickAndHold(getDefaultColumnHeader(draggedColumnHeaderIndex)) diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderTest.java index 9f43c39b1e..ac267dc42e 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderTest.java @@ -131,7 +131,7 @@ public class GridColumnReorderTest extends GridBasicClientFeaturesTest { sortColumn(0); // when - dragDefaultColumnHeader(0, 2, 10); + dragAndDropDefaultColumnHeader(0, 2, 10); // then assertColumnIsSorted(1); @@ -158,7 +158,7 @@ public class GridColumnReorderTest extends GridBasicClientFeaturesTest { focusDefaultHeader(0); // when - dragDefaultColumnHeader(0, 3, 10); + dragAndDropDefaultColumnHeader(0, 3, 10); // then WebElement defaultColumnHeader = getDefaultColumnHeader(2); @@ -173,7 +173,7 @@ public class GridColumnReorderTest extends GridBasicClientFeaturesTest { focusCell(2, 2); // when - dragDefaultColumnHeader(2, 0, 10); + dragAndDropDefaultColumnHeader(2, 0, 10); // then assertFocusedCell(2, 0); @@ -186,7 +186,7 @@ public class GridColumnReorderTest extends GridBasicClientFeaturesTest { focusCell(1, 3); // when - dragDefaultColumnHeader(4, 1, 10); + dragAndDropDefaultColumnHeader(4, 1, 10); // then assertFocusedCell(1, 4); @@ -199,7 +199,7 @@ public class GridColumnReorderTest extends GridBasicClientFeaturesTest { focusCell(4, 2); // when - dragDefaultColumnHeader(0, 4, 10); + dragAndDropDefaultColumnHeader(0, 4, 10); // then assertFocusedCell(4, 1); 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 2cc8610209..7ab66aad95 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 @@ -48,7 +48,7 @@ public class GridColumnReorderTest extends GridBasicFeaturesTest { toggleColumnReordering(); // when - dragDefaultColumnHeader(0, 2, 10); + dragAndDropDefaultColumnHeader(0, 2, 10); // then assertColumnHeaderOrder(1, 0, 2); @@ -62,7 +62,7 @@ public class GridColumnReorderTest extends GridBasicFeaturesTest { toggleColumnReordering(); // when - dragDefaultColumnHeader(0, 2, 110); + dragAndDropDefaultColumnHeader(0, 2, 110); // then assertColumnHeaderOrder(1, 2, 0); @@ -76,13 +76,13 @@ public class GridColumnReorderTest extends GridBasicFeaturesTest { toggleColumnReordering(); // when - dragDefaultColumnHeader(2, 0, 10); + dragAndDropDefaultColumnHeader(2, 0, 10); // then assertColumnHeaderOrder(2, 0, 1, 3); // when - dragDefaultColumnHeader(1, 3, 110); + dragAndDropDefaultColumnHeader(1, 3, 110); // then assertColumnHeaderOrder(2, 1, 3, 0); @@ -95,7 +95,7 @@ public class GridColumnReorderTest extends GridBasicFeaturesTest { assertColumnHeaderOrder(0, 1, 2); // when - dragDefaultColumnHeader(0, 2, 110); + dragAndDropDefaultColumnHeader(0, 2, 110); // then assertColumnHeaderOrder(0, 1, 2); @@ -109,7 +109,7 @@ public class GridColumnReorderTest extends GridBasicFeaturesTest { toggleColumnReordering(); // when - dragDefaultColumnHeader(0, 2, 10); + dragAndDropDefaultColumnHeader(0, 2, 10); assertColumnHeaderOrder(1, 0, 2); moveColumnManuallyLeftByOne(0); @@ -127,7 +127,7 @@ public class GridColumnReorderTest extends GridBasicFeaturesTest { // when selectMenuPath(new String[] { "Component", "Internals", "Update column order without updating client" }); - dragDefaultColumnHeader(2, 0, 10); + dragAndDropDefaultColumnHeader(2, 0, 10); // then assertColumnHeaderOrder(1, 0, 2); @@ -141,7 +141,7 @@ public class GridColumnReorderTest extends GridBasicFeaturesTest { // when toggleColumnReorderListener(); - dragDefaultColumnHeader(0, 2, 10); + dragAndDropDefaultColumnHeader(0, 2, 10); // then assertColumnReorderEvent(true); @@ -152,19 +152,19 @@ public class GridColumnReorderTest extends GridBasicFeaturesTest { // given openTestURL(); toggleColumnReordering(); - dragDefaultColumnHeader(0, 2, 10); + dragAndDropDefaultColumnHeader(0, 2, 10); assertNoColumnReorderEvent(); // when toggleColumnReorderListener(); - dragDefaultColumnHeader(0, 2, 110); + dragAndDropDefaultColumnHeader(0, 2, 110); // then assertColumnReorderEvent(true); // when toggleColumnReorderListener(); - dragDefaultColumnHeader(0, 3, 10); + dragAndDropDefaultColumnHeader(0, 3, 10); // then assertNoColumnReorderEvent(); @@ -182,6 +182,36 @@ public class GridColumnReorderTest extends GridBasicFeaturesTest { assertColumnReorderEvent(false); } + @Test + public void testColumnReorder_draggingFrozenColumns_impossible() { + // given + openTestURL(); + toggleColumnReordering(); + setFrozenColumns(2); + assertColumnHeaderOrder(0, 1, 2, 3); + + // when + dragAndDropDefaultColumnHeader(0, 2, 10); + + // then + assertColumnHeaderOrder(0, 1, 2, 3); + } + + @Test + public void testColumnReorder_draggingColumnOnTopOfFrozenColumn_columnDroppedRightOfFrozenColumns() { + // given + openTestURL(); + toggleColumnReordering(); + setFrozenColumns(1); + assertColumnHeaderOrder(0, 1, 2, 3); + + // when + dragAndDropDefaultColumnHeader(2, 0, 10); + + // then + assertColumnHeaderOrder(0, 2, 1, 3); + } + private void toggleColumnReordering() { selectMenuPath(COLUMN_REORDERING_PATH); } -- cgit v1.2.3 From 0a78453dfa920768f2f6a45b27eb005826530677 Mon Sep 17 00:00:00 2001 From: Leif Åstrand Date: Tue, 24 Feb 2015 14:51:58 +0200 Subject: Don't reference private class in javadoc (#16644) Change-Id: I27e6ab90790fc2f4c39679a1e7f69c6ecf13a276 --- client/src/com/vaadin/client/widget/escalator/Spacer.java | 3 ++- client/src/com/vaadin/client/widget/escalator/SpacerUpdater.java | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) (limited to 'client/src') diff --git a/client/src/com/vaadin/client/widget/escalator/Spacer.java b/client/src/com/vaadin/client/widget/escalator/Spacer.java index 0b0a2b257f..371d539f57 100644 --- a/client/src/com/vaadin/client/widget/escalator/Spacer.java +++ b/client/src/com/vaadin/client/widget/escalator/Spacer.java @@ -16,9 +16,10 @@ package com.vaadin.client.widget.escalator; import com.google.gwt.dom.client.Element; +import com.vaadin.client.widget.escalator.RowContainer.BodyRowContainer; /** - * A representation of a spacer element in a {@link SpacerContainer}. + * A representation of a spacer element in a {@link BodyRowContainer}. * * @since * @author Vaadin Ltd diff --git a/client/src/com/vaadin/client/widget/escalator/SpacerUpdater.java b/client/src/com/vaadin/client/widget/escalator/SpacerUpdater.java index 18f53db507..01d715fdd0 100644 --- a/client/src/com/vaadin/client/widget/escalator/SpacerUpdater.java +++ b/client/src/com/vaadin/client/widget/escalator/SpacerUpdater.java @@ -15,6 +15,8 @@ */ package com.vaadin.client.widget.escalator; +import com.vaadin.client.widget.escalator.RowContainer.BodyRowContainer; + /** * An interface that handles the display of content for spacers. *

@@ -24,7 +26,7 @@ package com.vaadin.client.widget.escalator; * @since * @author Vaadin Ltd * @see Spacer - * @see SpacerContainer + * @see BodyRowContainer */ public interface SpacerUpdater { -- 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 'client/src') 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 From c521daf78b0897b31ca516c10ceb092ffdca43f2 Mon Sep 17 00:00:00 2001 From: Henrik Paul Date: Wed, 25 Feb 2015 10:24:01 +0200 Subject: Adds support for inserting rows to Escalator while spacers are open (#16644) Change-Id: Id8689ed308926debee385e158d045f014fae21e0 --- .../src/com/vaadin/client/widgets/Escalator.java | 215 ++++++++++++++++----- 1 file changed, 172 insertions(+), 43 deletions(-) (limited to 'client/src') diff --git a/client/src/com/vaadin/client/widgets/Escalator.java b/client/src/com/vaadin/client/widgets/Escalator.java index dcc6c98960..30ecf64ff4 100644 --- a/client/src/com/vaadin/client/widgets/Escalator.java +++ b/client/src/com/vaadin/client/widgets/Escalator.java @@ -17,6 +17,7 @@ package com.vaadin.client.widgets; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; @@ -2569,15 +2570,14 @@ public class Escalator extends Widget implements RequiresResize, double viewportPx = y2 - y1; double spacerPx = spacerContainer.getSpacerHeightsSumBetweenPx(y1, - SpacerMeasurementStrategy.PARTIAL, y2, - SpacerMeasurementStrategy.PARTIAL); + SpacerInclusionStrategy.PARTIAL, y2, + SpacerInclusionStrategy.PARTIAL); return viewportPx - spacerPx; } private int getLogicalRowIndex(final double px) { - double rowPx = px - - spacerContainer.getSpacerHeightsSumUntilPx(px); + double rowPx = px - spacerContainer.getSpacerHeightsSumUntilPx(px); return (int) (rowPx / getDefaultRowHeight()); } @@ -2587,9 +2587,6 @@ public class Escalator extends Widget implements RequiresResize, return; } - // TODO - getLogger().warning("[[spacers]] inserting rows"); - /* * TODO: this method should probably only add physical rows, and not * populate them - let everything be populated as appropriate by the @@ -2635,6 +2632,13 @@ public class Escalator extends Widget implements RequiresResize, final int visualOffset = getLogicalRowIndex(visualRowOrder .getFirst()); + final double pxDiff = numberOfRows * getDefaultRowHeight(); + for (SpacerContainer.SpacerImpl spacer : spacerContainer + .getSpacersForRowAndAfter(index)) { + spacer.setPositionDiff(0, pxDiff); + spacer.setRowIndex(spacer.getRow() + numberOfRows); + } + /* * At this point, we have added new escalator rows, if so * needed. @@ -2646,7 +2650,7 @@ public class Escalator extends Widget implements RequiresResize, final int rowsStillNeeded = numberOfRows - addedRows.size(); final Range unupdatedVisual = convertToVisual(Range.withLength( unupdatedLogicalStart, rowsStillNeeded)); - final int end = root.getChildCount(); + final int end = getEscalatorRowCount(); final int start = end - unupdatedVisual.length(); final int visualTargetIndex = unupdatedLogicalStart - visualOffset; @@ -2658,7 +2662,16 @@ public class Escalator extends Widget implements RequiresResize, * getDefaultRowHeight(); final ListIterator i = visualRowOrder .listIterator(visualTargetIndex + (end - start)); + + int logicalRowIndexCursor = unupdatedLogicalStart; while (i.hasNext()) { + SpacerContainer.SpacerImpl spacer = spacerContainer + .getSpacer(logicalRowIndexCursor); + if (spacer != null) { + rowTop += spacer.getHeight(); + } + logicalRowIndexCursor++; + final TableRowElement tr = i.next(); setRowPosition(tr, 0, rowTop); rowTop += getDefaultRowHeight(); @@ -2698,10 +2711,11 @@ public class Escalator extends Widget implements RequiresResize, assert visualTargetIndex >= 0 : "Visual target must be 0 or greater (was " + visualTargetIndex + ")"; - assert visualTargetIndex <= root.getChildCount() : "Visual target " + assert visualTargetIndex <= getEscalatorRowCount() : "Visual target " + "must not be greater than the number of escalator rows (was " - + visualTargetIndex + ", escalator rows " - + root.getChildCount() + ")"; + + visualTargetIndex + + ", escalator rows " + + getEscalatorRowCount() + ")"; assert logicalTargetIndex + visualSourceRange.length() <= getRowCount() : "Logical " + "target leads to rows outside of the data range (" @@ -2784,12 +2798,11 @@ public class Escalator extends Widget implements RequiresResize, /** * Adjust the scroll position and move the contained rows. *

- * Note: This method does not account for spacers. - *

* The difference between using this method and simply scrolling is that - * this method "takes the rows with it" and renders them appropriately. - * The viewport may be scrolled any arbitrary amount, and the rows are - * moved appropriately, but always snapped into a plausible place. + * this method "takes the rows and spacers with it" and renders them + * appropriately. The viewport may be scrolled any arbitrary amount, and + * the contents are moved appropriately, but always snapped into a + * plausible place. *

*

*
Example 1
@@ -2809,25 +2822,31 @@ public class Escalator extends Widget implements RequiresResize, */ public void moveViewportAndContent(final double yDelta) { - /* - * TODO: When adding and removing rows starts supporting spacers, - * this method should also take spacers into account. Remember to - * adjust the javadoc as well. - */ - if (yDelta == 0) { return; } double newTop = tBodyScrollTop + yDelta; - final double rowTopPos = body.getRowTop(getLogicalRowIndex(newTop)); - verticalScrollbar.setScrollPos(newTop); - for (int i = 0; i < visualRowOrder.size(); i++) { - final TableRowElement tr = visualRowOrder.get(i); - setRowPosition(tr, 0, rowTopPos + getDefaultRowHeight() * i); + final double defaultRowHeight = getDefaultRowHeight(); + double rowPxDelta = yDelta - (yDelta % defaultRowHeight); + int rowIndexDelta = (int) (yDelta / defaultRowHeight); + if (!WidgetUtil.pixelValuesEqual(rowPxDelta, 0)) { + + Collection spacers = spacerContainer + .getSpacersAfterPx(tBodyScrollTop, + SpacerInclusionStrategy.PARTIAL); + for (SpacerContainer.SpacerImpl spacer : spacers) { + spacer.setPositionDiff(0, rowPxDelta); + spacer.setRowIndex(spacer.getRow() + rowIndexDelta); + } + + for (TableRowElement tr : visualRowOrder) { + setRowPosition(tr, 0, getRowTop(tr) + rowPxDelta); + } } + setBodyScrollPosition(tBodyScrollLeft, newTop); } @@ -2850,7 +2869,7 @@ public class Escalator extends Widget implements RequiresResize, final int index, final int numberOfRows) { final int escalatorRowsStillFit = getMaxEscalatorRowCapacity() - - root.getChildCount(); + - getEscalatorRowCount(); final int escalatorRowsNeeded = Math.min(numberOfRows, escalatorRowsStillFit); @@ -3802,6 +3821,21 @@ public class Escalator extends Widget implements RequiresResize, return Collections.unmodifiableList(sublist); } } + + /** + * This method calculates the current escalator row count directly from + * the DOM. + *

+ * While Escalator is stable, this value should equal to + * {@link #visualRowOrder}.size(), but while row counts are being + * updated, these two values might differ for a short while. + * + * @return the actual DOM count of escalator rows + */ + private int getEscalatorRowCount() { + return root.getChildCount() + - spacerContainer.getSpacersInDom().size(); + } } private class ColumnConfigurationImpl implements ColumnConfiguration { @@ -4280,15 +4314,18 @@ public class Escalator extends Widget implements RequiresResize, /** * A decision on how to measure a spacer when it is partially within a * designated range. + *

+ * The meaning of each value may differ depending on the context it is being + * used in. Check that particular method's JavaDoc. */ - public enum SpacerMeasurementStrategy { - /** Take the entire spacer's height into account. */ + public enum SpacerInclusionStrategy { + /** A representation of "the entire spacer". */ COMPLETE, - /** Take the visible part into account. */ + /** A representation of "a partial spacer". */ PARTIAL, - /** Exclude the entire spacer. */ + /** A representation of "no spacer at all". */ NONE } @@ -4300,7 +4337,7 @@ public class Escalator extends Widget implements RequiresResize, private final class SpacerImpl implements Spacer { private TableCellElement spacerElement; private TableRowElement root; - private final int rowIndex; + private int rowIndex; private double height = -1; private boolean domHasBeenSetup = false; @@ -4313,6 +4350,15 @@ public class Escalator extends Widget implements RequiresResize, root.setPropertyInt(SPACER_LOGICAL_ROW_PROPERTY, rowIndex); } + public void setPositionDiff(double x, double y) { + setPosition(getLeft() + x, getTop() + y); + } + + public double getLeft() { + // not implemented yet. + return 0; + } + public void setupDom(double height) { assert !domHasBeenSetup : "DOM can't be set up twice."; assert RootPanel.get().getElement().isOrHasChild(root) : "Root element should've been attached to the DOM by now."; @@ -4352,7 +4398,7 @@ public class Escalator extends Widget implements RequiresResize, root.getStyle().setHeight(height, Unit.PX); // move the visible spacers getRow row onwards. - shiftSpacerPositions(getRow(), heightDiff); + shiftSpacerPositionsAfterRow(getRow(), heightDiff); /* * If we're growing, we'll adjust the scroll size first, then @@ -4439,6 +4485,14 @@ public class Escalator extends Widget implements RequiresResize, public double getTop() { return positions.getTop(getRootElement()); } + + @SuppressWarnings("boxing") + public void setRowIndex(int rowIndex) { + SpacerImpl spacer = rowIndexToSpacer.remove(this.rowIndex); + assert this == spacer : "trying to move an unexpected spacer."; + this.rowIndex = rowIndex; + rowIndexToSpacer.put(this.rowIndex, this); + } } private final TreeMap rowIndexToSpacer = new TreeMap(); @@ -4465,26 +4519,100 @@ public class Escalator extends Widget implements RequiresResize, } } + @SuppressWarnings("boxing") + public Collection getSpacersForRowAndAfter( + int logicalRowIndex) { + return new ArrayList(rowIndexToSpacer.tailMap( + logicalRowIndex, true).values()); + } + + /** + * Get all spacers from one pixel point onwards. + *

+ * + * In this method, the {@link SpacerInclusionStrategy} has the following + * meaning when a spacer lies in the middle of either pixel argument: + *

+ *
{@link SpacerInclusionStrategy#COMPLETE COMPLETE} + *
include the spacer + *
{@link SpacerInclusionStrategy#PARTIAL PARTIAL} + *
include the spacer + *
{@link SpacerInclusionStrategy#NONE NONE} + *
ignore the spacer + *
+ * + * @param px + * the pixel point after which to return all spacers + * @param strategy + * the inclusion strategy regarding the {@code px} + * @return a collection of the spacers that exist after {@code px} + */ + public Collection getSpacersAfterPx(final double px, + final SpacerInclusionStrategy strategy) { + + ArrayList spacers = new ArrayList( + rowIndexToSpacer.values()); + + for (int i = 0; i < spacers.size(); i++) { + SpacerImpl spacer = spacers.get(i); + + double top = spacer.getTop(); + double bottom = top + spacer.getHeight(); + + if (top > px) { + return spacers.subList(i, spacers.size()); + } else if (bottom > px) { + if (strategy == SpacerInclusionStrategy.NONE) { + return spacers.subList(i + 1, spacers.size()); + } else { + return spacers.subList(i, spacers.size()); + } + } + } + + return Collections.emptySet(); + } + + /** + * Gets the spacers currently rendered in the DOM. + * + * @return an unmodifiable (but live) collection of the spacers + * currently in the DOM + */ + public Collection getSpacersInDom() { + return Collections + .unmodifiableCollection(rowIndexToSpacer.values()); + } + /** * Gets the amount of pixels occupied by spacers between two pixel * points. + *

+ * In this method, the {@link SpacerInclusionStrategy} has the following + * meaning when a spacer lies in the middle of either pixel argument: + *

+ *
{@link SpacerInclusionStrategy#COMPLETE COMPLETE} + *
take the entire spacer into account + *
{@link SpacerInclusionStrategy#PARTIAL PARTIAL} + *
take only the visible area into account + *
{@link SpacerInclusionStrategy#NONE NONE} + *
ignore that spacer + *
* * @param rangeTop * the top pixel point * @param topInclusion - * how to measure a spacer that happens to lie in the middle - * of {@code rangeTop}. + * the inclusion strategy regarding {@code rangeTop}. * @param rangeBottom * the bottom pixel point * @param bottomInclusion - * how to measure a spacer that happens to lie in the middle - * of {@code rangeBottom}. + * the inclusion strategy regarding {@code rangeBottom}. * @return the pixels occupied by spacers between {@code rangeTop} and * {@code rangeBottom} */ public double getSpacerHeightsSumBetweenPx(double rangeTop, - SpacerMeasurementStrategy topInclusion, double rangeBottom, - SpacerMeasurementStrategy bottomInclusion) { + SpacerInclusionStrategy topInclusion, double rangeBottom, + SpacerInclusionStrategy bottomInclusion) { assert rangeTop <= rangeBottom : "rangeTop must be less than rangeBottom"; @@ -4596,8 +4724,8 @@ public class Escalator extends Widget implements RequiresResize, */ public double getSpacerHeightsSumUntilPx(double px) { return getSpacerHeightsSumBetweenPx(0, - SpacerMeasurementStrategy.PARTIAL, px, - SpacerMeasurementStrategy.PARTIAL); + SpacerInclusionStrategy.PARTIAL, px, + SpacerInclusionStrategy.PARTIAL); } /** @@ -4733,7 +4861,8 @@ public class Escalator extends Widget implements RequiresResize, } @SuppressWarnings("boxing") - private void shiftSpacerPositions(int changedRowIndex, double diffPx) { + private void shiftSpacerPositionsAfterRow(int changedRowIndex, + double diffPx) { for (SpacerImpl spacer : rowIndexToSpacer.tailMap(changedRowIndex, false).values()) { spacer.setPosition(0, spacer.getTop() + diffPx); -- cgit v1.2.3 From 4ba54083a8a4e0aeada8a558c3c817c66b67a95c Mon Sep 17 00:00:00 2001 From: Henrik Paul Date: Thu, 26 Feb 2015 09:32:43 +0200 Subject: Fixes two bugs when inserting escalator rows with spacers open (#16644) If the viewport is not yet filled with escalator rows, it didn't account for spacers. Also, the scrollbar was updated incorrectly after adding new rows. Change-Id: Id9cab71c2c4b82331771d1243143eb9db0883a6c --- .../src/com/vaadin/client/widgets/Escalator.java | 157 +++++++++++++-------- .../EscalatorBasicClientFeaturesTest.java | 2 + .../escalator/EscalatorSpacerTest.java | 51 ++++++- 3 files changed, 152 insertions(+), 58 deletions(-) (limited to 'client/src') diff --git a/client/src/com/vaadin/client/widgets/Escalator.java b/client/src/com/vaadin/client/widgets/Escalator.java index 30ecf64ff4..0df078d26e 100644 --- a/client/src/com/vaadin/client/widgets/Escalator.java +++ b/client/src/com/vaadin/client/widgets/Escalator.java @@ -820,7 +820,8 @@ public class Escalator extends Widget implements RequiresResize, * that the sizes of the scroll handles appear correct in the browser */ public void recalculateScrollbarsForVirtualViewport() { - double scrollContentHeight = body.calculateTotalRowHeight(); + double scrollContentHeight = body.calculateTotalRowHeight() + + body.spacerContainer.getSpacerHeightsSum(); double scrollContentWidth = columnConfiguration.calculateRowWidth(); double tableWrapperHeight = heightOfEscalator; @@ -2420,6 +2421,13 @@ public class Escalator extends Widget implements RequiresResize, return; } + /* + * TODO This will break the logical index calculation, as it will + * try to search for non- + */ + getLogger().warning( + "[[spacers]] scrolling and spacers near the bottom"); + boolean rowsWereMoved = false; final double topElementPosition; @@ -2587,6 +2595,13 @@ public class Escalator extends Widget implements RequiresResize, return; } + final double pxDiff = numberOfRows * getDefaultRowHeight(); + for (SpacerContainer.SpacerImpl spacer : spacerContainer + .getSpacersForRowAndAfter(index)) { + spacer.setPositionDiff(0, pxDiff); + spacer.setRowIndex(spacer.getRow() + numberOfRows); + } + /* * TODO: this method should probably only add physical rows, and not * populate them - let everything be populated as appropriate by the @@ -2632,13 +2647,6 @@ public class Escalator extends Widget implements RequiresResize, final int visualOffset = getLogicalRowIndex(visualRowOrder .getFirst()); - final double pxDiff = numberOfRows * getDefaultRowHeight(); - for (SpacerContainer.SpacerImpl spacer : spacerContainer - .getSpacersForRowAndAfter(index)) { - spacer.setPositionDiff(0, pxDiff); - spacer.setRowIndex(spacer.getRow() + numberOfRows); - } - /* * At this point, we have added new escalator rows, if so * needed. @@ -2648,33 +2656,33 @@ public class Escalator extends Widget implements RequiresResize, * the remaining rows aswell. */ final int rowsStillNeeded = numberOfRows - addedRows.size(); - final Range unupdatedVisual = convertToVisual(Range.withLength( - unupdatedLogicalStart, rowsStillNeeded)); - final int end = getEscalatorRowCount(); - final int start = end - unupdatedVisual.length(); - final int visualTargetIndex = unupdatedLogicalStart - - visualOffset; - moveAndUpdateEscalatorRows(Range.between(start, end), - visualTargetIndex, unupdatedLogicalStart); - - // move the surrounding rows to their correct places. - double rowTop = (unupdatedLogicalStart + (end - start)) - * getDefaultRowHeight(); - final ListIterator i = visualRowOrder - .listIterator(visualTargetIndex + (end - start)); - - int logicalRowIndexCursor = unupdatedLogicalStart; - while (i.hasNext()) { - SpacerContainer.SpacerImpl spacer = spacerContainer - .getSpacer(logicalRowIndexCursor); - if (spacer != null) { - rowTop += spacer.getHeight(); - } - logicalRowIndexCursor++; - final TableRowElement tr = i.next(); - setRowPosition(tr, 0, rowTop); - rowTop += getDefaultRowHeight(); + if (rowsStillNeeded > 0) { + final Range unupdatedVisual = convertToVisual(Range + .withLength(unupdatedLogicalStart, rowsStillNeeded)); + final int end = getEscalatorRowCount(); + final int start = end - unupdatedVisual.length(); + final int visualTargetIndex = unupdatedLogicalStart + - visualOffset; + moveAndUpdateEscalatorRows(Range.between(start, end), + visualTargetIndex, unupdatedLogicalStart); + + // move the surrounding rows to their correct places. + double rowTop = (unupdatedLogicalStart + (end - start)) + * getDefaultRowHeight(); + final ListIterator i = visualRowOrder + .listIterator(visualTargetIndex + (end - start)); + + int logicalRowIndexCursor = unupdatedLogicalStart; + while (i.hasNext()) { + rowTop += spacerContainer + .getSpacerHeight(logicalRowIndexCursor++); + + final TableRowElement tr = i.next(); + getLogger().warning("iterate"); + setRowPosition(tr, 0, rowTop); + rowTop += getDefaultRowHeight(); + } } fireRowVisibilityChangeEvent(); @@ -2786,11 +2794,8 @@ public class Escalator extends Widget implements RequiresResize, setRowPosition(tr, 0, newRowTop); newRowTop += getDefaultRowHeight(); - SpacerContainer.SpacerImpl spacer = spacerContainer - .getSpacer(logicalTargetIndex + i); - if (spacer != null) { - newRowTop += spacer.getHeight(); - } + newRowTop += spacerContainer + .getSpacerHeight(logicalTargetIndex + i); } } } @@ -2879,25 +2884,29 @@ public class Escalator extends Widget implements RequiresResize, index, escalatorRowsNeeded); visualRowOrder.addAll(index, addedRows); - /* - * We need to figure out the top positions for the rows we just - * added. - */ - for (int i = 0; i < addedRows.size(); i++) { - setRowPosition(addedRows.get(i), 0, (index + i) - * getDefaultRowHeight()); - } + double y = index * getDefaultRowHeight() + + spacerContainer.getSpacerHeightsSumUntilIndex(index); + for (int i = index; i < visualRowOrder.size(); i++) { + + final TableRowElement tr; + if (i - index < addedRows.size()) { + tr = addedRows.get(i - index); + } else { + tr = visualRowOrder.get(i); + } - /* Move the other rows away from above the added escalator rows */ - for (int i = index + addedRows.size(); i < visualRowOrder - .size(); i++) { - final TableRowElement tr = visualRowOrder.get(i); - setRowPosition(tr, 0, i * getDefaultRowHeight()); + getLogger().warning("y: " + y + ", index: " + i); + setRowPosition(tr, 0, y); + y += getDefaultRowHeight(); + double spacerHeight = spacerContainer.getSpacerHeight(i); + getLogger().warning( + "height: " + spacerHeight + ", index: " + i); + y += spacerHeight; } return addedRows; } else { - return new ArrayList(); + return Collections.emptyList(); } } @@ -4519,6 +4528,23 @@ public class Escalator extends Widget implements RequiresResize, } } + /** + * Calculates the sum of all spacers. + * + * @return sum of all spacers, or 0 if no spacers present + */ + public double getSpacerHeightsSum() { + return getHeights(rowIndexToSpacer.values()); + } + + /** + * Calculates the sum of all spacers from one row index onwards. + * + * @param logicalRowIndex + * the spacer to include as the first calculated spacer + * @return the sum of all spacers from {@code logicalRowIndex} and + * onwards, or 0 if no suitable spacers were found + */ @SuppressWarnings("boxing") public Collection getSpacersForRowAndAfter( int logicalRowIndex) { @@ -4738,14 +4764,35 @@ public class Escalator extends Widget implements RequiresResize, */ @SuppressWarnings("boxing") public double getSpacerHeightsSumUntilIndex(int logicalIndex) { + return getHeights(rowIndexToSpacer.headMap(logicalIndex, false) + .values()); + } + + private double getHeights(Collection spacers) { double heights = 0; - for (SpacerImpl spacer : rowIndexToSpacer.headMap(logicalIndex, - false).values()) { + for (SpacerImpl spacer : spacers) { heights += spacer.getHeight(); } return heights; } + /** + * Gets the height of the spacer for a row index. + * + * @param rowIndex + * the index of the row where the spacer should be + * @return the height of the spacer at index {@code rowIndex}, or 0 if + * there is no spacer there + */ + public double getSpacerHeight(int rowIndex) { + SpacerImpl spacer = getSpacer(rowIndex); + if (spacer != null) { + return spacer.getHeight(); + } else { + return 0; + } + } + private boolean spacerExists(int rowIndex) { return rowIndexToSpacer.containsKey(Integer.valueOf(rowIndex)); } 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 ef2d605928..c22da47d62 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/EscalatorBasicClientFeaturesTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/EscalatorBasicClientFeaturesTest.java @@ -38,8 +38,10 @@ public abstract class EscalatorBasicClientFeaturesTest extends MultiBrowserTest protected static final String COLUMNS = "Columns"; protected static final String ADD_ONE_COLUMN_TO_BEGINNING = "Add one column to beginning"; protected static final String ADD_ONE_ROW_TO_BEGINNING = "Add one row to beginning"; + protected static final String ADD_ONE_ROW_TO_END = "Add one row to end"; protected static final String REMOVE_ONE_COLUMN_FROM_BEGINNING = "Remove one column from beginning"; protected static final String REMOVE_ONE_ROW_FROM_BEGINNING = "Remove one row from beginning"; + protected static final String REMOVE_ALL_ROWS = "Remove all rows"; protected static final String REMOVE_50_ROWS_FROM_BOTTOM = "Remove 50 rows from bottom"; protected static final String REMOVE_50_ROWS_FROM_ALMOST_BOTTOM = "Remove 50 rows from almost bottom"; protected static final String ADD_ONE_OF_EACH_ROW = "Add one of each row"; 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 index 43a18b507d..1985de1c82 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/escalator/EscalatorSpacerTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/escalator/EscalatorSpacerTest.java @@ -25,10 +25,12 @@ import java.util.regex.Pattern; import org.junit.Before; import org.junit.Test; +import org.openqa.selenium.WebElement; -import com.vaadin.testbench.TestBenchElement; +import com.vaadin.client.WidgetUtil; import com.vaadin.tests.components.grid.basicfeatures.EscalatorBasicClientFeaturesTest; +@SuppressWarnings("boxing") public class EscalatorSpacerTest extends EscalatorBasicClientFeaturesTest { //@formatter:off @@ -98,7 +100,6 @@ public class EscalatorSpacerTest extends EscalatorBasicClientFeaturesTest { } @Test - @SuppressWarnings("boxing") public void spacerPushesVisibleRowsDown() { double oldTop = getElementTop(getBodyRow(2)); selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX); @@ -107,7 +108,51 @@ public class EscalatorSpacerTest extends EscalatorBasicClientFeaturesTest { assertGreater("Row below a spacer was not pushed down", newTop, oldTop); } - private static double getElementTop(TestBenchElement element) { + @Test + public void addingRowAboveSpacerPushesItDown() { + selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, REMOVE_ALL_ROWS); + selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, ADD_ONE_ROW_TO_BEGINNING); + selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, ADD_ONE_ROW_TO_BEGINNING); + + selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX); + double oldTop = getElementTop(getSpacer(1)); + selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, ADD_ONE_ROW_TO_BEGINNING); + double newTop = getElementTop(getSpacer(1)); + + assertGreater("Spacer should've been pushed down", newTop, oldTop); + } + + @Test + public void addingRowBelowSpacerDoesNotPushItDown() { + selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, REMOVE_ALL_ROWS); + selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, ADD_ONE_ROW_TO_BEGINNING); + selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, ADD_ONE_ROW_TO_BEGINNING); + + selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX); + double oldTop = getElementTop(getSpacer(1)); + selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, ADD_ONE_ROW_TO_END); + double newTop = getElementTop(getSpacer(1)); + + assertEquals("Spacer should've not been pushed down", newTop, oldTop, + WidgetUtil.PIXEL_EPSILON); + } + + @Test + public void addingRowBelowSpacerIsActuallyRenderedBelowWhenEscalatorIsEmpty() { + selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, REMOVE_ALL_ROWS); + selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, ADD_ONE_ROW_TO_BEGINNING); + selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, ADD_ONE_ROW_TO_BEGINNING); + + selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX); + double spacerTop = getElementTop(getSpacer(1)); + selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, ADD_ONE_ROW_TO_END); + double rowTop = getElementTop(getBodyRow(2)); + + assertEquals("Next row should've been rendered below the spacer", + spacerTop + 100, rowTop, WidgetUtil.PIXEL_EPSILON); + } + + private static double getElementTop(WebElement element) { /* * we need to parse the style attribute, since using getCssValue gets a * normalized value that is harder to parse. -- cgit v1.2.3 From 31b188e370b60832ecc05df8ddd41a6884996aad Mon Sep 17 00:00:00 2001 From: Pekka Hyvönen Date: Thu, 26 Feb 2015 16:23:32 +0200 Subject: Handle spanned header cells when dnd reordering columns in grid. (#16643) Prevents dragging columns from outside spanned cells to inside them, on any row. Currently prevents dragging columns inside spanned cells. Will change this later. Change-Id: Ie832b3c404a3afbcce0374f8b5088dc8cb124fb8 --- .../vaadin/client/widget/grid/AutoScroller.java | 1 + client/src/com/vaadin/client/widgets/Grid.java | 129 +++++++++++++++++---- .../basicfeatures/GridBasicClientFeaturesTest.java | 13 ++- .../grid/basicfeatures/GridColumnReorderTest.java | 30 +++++ 4 files changed, 147 insertions(+), 26 deletions(-) (limited to 'client/src') diff --git a/client/src/com/vaadin/client/widget/grid/AutoScroller.java b/client/src/com/vaadin/client/widget/grid/AutoScroller.java index 60c9535ca7..71851d67c8 100644 --- a/client/src/com/vaadin/client/widget/grid/AutoScroller.java +++ b/client/src/com/vaadin/client/widget/grid/AutoScroller.java @@ -669,6 +669,7 @@ public class AutoScroller { } private double getFrozenColumnsWidth() { + // TODO handle the case where the checkbox column is present double value = 0; for (int i = 0; i < grid.getFrozenColumnCount(); i++) { value += grid.getColumn(i).getWidthActual(); diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index ebc2fcf97e..3fd119d9ea 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -25,7 +25,9 @@ import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; +import java.util.TreeMap; import java.util.logging.Level; import java.util.logging.Logger; @@ -37,7 +39,6 @@ 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.NativeEvent; -import com.google.gwt.dom.client.NodeList; import com.google.gwt.dom.client.Style; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.dom.client.TableCellElement; @@ -415,6 +416,16 @@ public class Grid extends ResizeComposite implements return cells.get(column); } + /** + * Returns true if this row contains spanned cells. + * + * @since + * @return does this row contain spanned cells + */ + public boolean hasSpannedCells() { + return !cellGroups.isEmpty(); + } + /** * Merges columns cells in a row * @@ -2906,6 +2917,11 @@ public class Grid extends ResizeComposite implements private Element dragElement; /** Tracks index of the column whose left side the drop would occur */ private int latestColumnDropIndex; + /** + * Map of possible drop positions for the column and the corresponding + * column index. + */ + private final TreeMap possibleDropPositions = new TreeMap(); /** * Makes sure that drag cancel doesn't cause anything unwanted like sort */ @@ -2952,33 +2968,33 @@ public class Grid extends ResizeComposite implements } private void updateDragDropMarker(final int clientX) { - RowContainer header = escalator.getHeader(); - NodeList cells = header.getRowElement( - eventCell.getRowIndex()).getCells(); - double dropMarkerLeft = 0 - escalator.getScrollLeft(); - latestColumnDropIndex = 0; - for (int i = 0; i < cells.getLength(); i++, latestColumnDropIndex++) { - TableCellElement cellElement = cells.getItem(i); - int cellX = cellElement.getAbsoluteLeft(); - int cellWidth = cellElement.getOffsetWidth(); - if (clientX < cellX || clientX < cellX + (cellWidth / 2)) { - break; - } else { - dropMarkerLeft += cellWidth; - } + final double scrollLeft = getScrollLeft(); + final double cursorXCoordinate = clientX + - escalator.getHeader().getElement().getAbsoluteLeft(); + final Entry cellEdgeOnRight = possibleDropPositions + .ceilingEntry(cursorXCoordinate); + final Entry cellEdgeOnLeft = possibleDropPositions + .floorEntry(cursorXCoordinate); + final double diffToRightEdge = cellEdgeOnRight == null ? Double.MAX_VALUE + : cellEdgeOnRight.getKey() - cursorXCoordinate; + final double diffToLeftEdge = cellEdgeOnLeft == null ? Double.MAX_VALUE + : cursorXCoordinate - cellEdgeOnLeft.getKey(); + + double dropMarkerLeft = 0 - scrollLeft; + if (diffToRightEdge > diffToLeftEdge) { + latestColumnDropIndex = cellEdgeOnLeft.getValue(); + dropMarkerLeft += cellEdgeOnLeft.getKey(); + } else { + latestColumnDropIndex = cellEdgeOnRight.getValue(); + dropMarkerLeft += cellEdgeOnRight.getKey(); } + dropMarkerLeft += autoScrollX; final double frozenColumnsWidth = getFrozenColumnsWidth(); - if (dropMarkerLeft < frozenColumnsWidth) { - latestColumnDropIndex = getFrozenColumnCount(); - if (getScrollLeft() == 0) { - dropMarkerLeft = frozenColumnsWidth; - } else { - dropMarkerLeft = -10000000; - } - } else if (dropMarkerLeft > header.getElement().getOffsetWidth() - || dropMarkerLeft < 0) { + if (dropMarkerLeft < frozenColumnsWidth + || dropMarkerLeft > escalator.getHeader().getElement() + .getOffsetWidth() || dropMarkerLeft < 0) { dropMarkerLeft = -10000000; } dropMarker.getStyle().setLeft(dropMarkerLeft, Unit.PX); @@ -3009,6 +3025,9 @@ public class Grid extends ResizeComposite implements eventCell.getElement().addClassName("dragged"); // mark the floating cell, for styling & testing dragElement.addClassName("dragged-column-header"); + + calculatePossibleDropPositions(); + // start the auto scroll handler autoScroller.setScrollAreaPX(60); autoScroller.start(startingEvent, ScrollAxis.HORIZONTAL, @@ -3099,6 +3118,58 @@ public class Grid extends ResizeComposite implements } return result; } + + private void calculatePossibleDropPositions() { + possibleDropPositions.clear(); + + if (!calculatePossibleDropPositionInsideSpannedHeader()) { + HashMap columnIndexToDropPositionMap = new HashMap(); + + final int frozenColumns = Math.max(0, getFrozenColumnCount()); + double position = getFrozenColumnsWidth(); + // add all columns except frozen columns + // TODO handle the case where the checkbox column is present + for (int i = frozenColumns; i < getColumnCount(); i++) { + columnIndexToDropPositionMap.put(i, position); + position += getColumn(i).getWidthActual(); + } + // add the right side of the last column as columns.size() + columnIndexToDropPositionMap.put(getColumnCount(), position); + + // can't drop inside a spanned header from outside it + // -> remove all column indexes that are inside a spanned cell + // in any header row + for (int c = frozenColumns; c < getColumnCount(); c++) { + for (int r = 0; r < getHeaderRowCount(); r++) { + HeaderRow headerRow = getHeaderRow(r); + if (headerRow.hasSpannedCells()) { + HeaderCell cell = headerRow.getCell(getColumn(c)); + assert cell != null : "Somehow got a null cell for row:cell " + + r + ":" + c; + int colspan = cell.getColspan(); + while (colspan > 1) { + c++; + colspan--; + columnIndexToDropPositionMap.remove(Integer + .valueOf(c)); + } + } + } + } + // finally lets flip the map, because while dragging we want the + // column index matching the X-coordinate + for (Entry entry : columnIndexToDropPositionMap + .entrySet()) { + possibleDropPositions.put(entry.getValue(), entry.getKey()); + } + } + } + + private boolean calculatePossibleDropPositionInsideSpannedHeader() { + // TODO if the dragged column is inside a spanned header on any row, + // then dragging is limited to inside that spanned cell + return false; + } }; /** @@ -5392,6 +5463,16 @@ public class Grid extends ResizeComposite implements .getFrozenColumnCount()) { return false; } + // for now prevent dragging of spanned cells + if (eventCell.getElement().getColSpan() > 1) { + return false; + } + // for now prevent dragging of columns belonging to a spanned cell + for (int r = 0; r < getHeaderRowCount(); r++) { + if (getHeaderRow(r).getCell(eventCell.getColumn()).getColspan() > 1) { + return false; + } + } if (event.getTypeInt() == Event.ONMOUSEDOWN && event.getButton() == NativeEvent.BUTTON_LEFT || event.getTypeInt() == Event.ONTOUCHSTART) { diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicClientFeaturesTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicClientFeaturesTest.java index d0e076fd3b..8e07729719 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicClientFeaturesTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicClientFeaturesTest.java @@ -15,6 +15,8 @@ */ package com.vaadin.tests.components.grid.basicfeatures; +import java.util.List; + import org.openqa.selenium.Dimension; import org.openqa.selenium.WebElement; import org.openqa.selenium.interactions.Actions; @@ -83,10 +85,17 @@ public abstract class GridBasicClientFeaturesTest extends GridBasicFeaturesTest if (composite) { // Composite requires the basic client features widget for subparts return ((TestBenchElement) findElement(By - .vaadin("//TestWidgetComponent"))) - .wrap(GridElement.class); + .vaadin("//TestWidgetComponent"))).wrap(GridElement.class); } else { return super.getGridElement(); } } + + @Override + protected void assertColumnHeaderOrder(int... indices) { + List headers = getGridHeaderRowCells(); + for (int i = 0; i < indices.length; i++) { + assertColumnHeader("HEADER (0," + indices[i] + ")", headers.get(i)); + } + } } diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderTest.java index ac267dc42e..6e5eda43f1 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderTest.java @@ -205,6 +205,36 @@ public class GridColumnReorderTest extends GridBasicClientFeaturesTest { assertFocusedCell(4, 1); } + @Test + public void testColumnReorder_draggingHeaderRowThatHasColumnHeadersSpanned_cantDropInsideSpannedHeaderFromOutside() { + // given + toggleColumnReorder(); + selectMenuPath("Component", "Header", "Append row"); + selectMenuPath("Component", "Header", "Row 2", "Join columns 1, 2"); + assertColumnHeaderOrder(0, 1, 2, 3, 4); + + // when + dragAndDropColumnHeader(1, 3, 1, 80); + + // then + assertColumnHeaderOrder(0, 3, 1, 2, 4); + } + + @Test + public void testColumnReorder_anotherRowHasColumnHeadersSpanned_cantDropInsideSpannedHeaderFromOutside() { + // given + toggleColumnReorder(); + selectMenuPath("Component", "Header", "Append row"); + selectMenuPath("Component", "Header", "Row 2", "Join columns 1, 2"); + assertColumnHeaderOrder(0, 1, 2, 3, 4); + + // when + dragAndDropColumnHeader(0, 0, 2, 20); + + // then + assertColumnHeaderOrder(1, 2, 0, 3, 4); + } + private void toggleColumnReorder() { selectMenuPath("Component", "State", "Column Reordering"); } -- cgit v1.2.3 From 40916228681314c843b32e59fc91bbec18548332 Mon Sep 17 00:00:00 2001 From: Pekka Hyvönen Date: Fri, 27 Feb 2015 16:13:41 +0200 Subject: Handle selection column oddity when dnd reordering columns (#16443) Change-Id: I7bb20fb98a79e77acc9073bc9c2210d1dc3fd49e --- .../vaadin/client/connectors/GridConnector.java | 4 +- .../vaadin/client/widget/grid/AutoScroller.java | 13 +++- client/src/com/vaadin/client/widgets/Grid.java | 37 +++++++++-- .../server/GridColumnReorderTest.java | 75 +++++++++++++++++++++- 4 files changed, 120 insertions(+), 9 deletions(-) (limited to 'client/src') diff --git a/client/src/com/vaadin/client/connectors/GridConnector.java b/client/src/com/vaadin/client/connectors/GridConnector.java index 2ae04b2be2..71450f6146 100644 --- a/client/src/com/vaadin/client/connectors/GridConnector.java +++ b/client/src/com/vaadin/client/connectors/GridConnector.java @@ -370,7 +370,9 @@ public class GridConnector extends AbstractHasComponentsConnector implements List> columns = getWidget().getColumns(); final List newColumnOrder = new ArrayList(); for (Column column : columns) { - newColumnOrder.add(((CustomGridColumn) column).id); + if (column instanceof CustomGridColumn) { + newColumnOrder.add(((CustomGridColumn) column).id); + } // the other case would be the multi selection column } getRpcProxy(GridServerRpc.class).columnsReordered( newColumnOrder, columnOrder); diff --git a/client/src/com/vaadin/client/widget/grid/AutoScroller.java b/client/src/com/vaadin/client/widget/grid/AutoScroller.java index 71851d67c8..f7c80df623 100644 --- a/client/src/com/vaadin/client/widget/grid/AutoScroller.java +++ b/client/src/com/vaadin/client/widget/grid/AutoScroller.java @@ -669,14 +669,23 @@ public class AutoScroller { } private double getFrozenColumnsWidth() { - // TODO handle the case where the checkbox column is present - double value = 0; + double value = getMultiSelectColumnWidth(); for (int i = 0; i < grid.getFrozenColumnCount(); i++) { value += grid.getColumn(i).getWidthActual(); } return value; } + private double getMultiSelectColumnWidth() { + if (grid.getFrozenColumnCount() >= 0 + && grid.getSelectionModel().getSelectionColumnRenderer() != null) { + // frozen checkbox column is present + return getTheadElement().getFirstChildElement() + .getFirstChildElement().getOffsetWidth(); + } + return 0.0; + } + private double getMaxScrollLeft() { return grid.getScrollWidth() - (getTableElement().getParentElement().getOffsetWidth() - getFrozenColumnsWidth()); diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index 3fd119d9ea..c237a7ba2f 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -3054,6 +3054,9 @@ public class Grid extends ResizeComposite implements latestColumnDropIndex--; } reordered.add(latestColumnDropIndex, moved); + reordered.remove(selectionColumn); // since setColumnOrder will + // add it anyway! + @SuppressWarnings("unchecked") Column[] array = reordered.toArray(new Column[reordered .size()]); @@ -3112,11 +3115,36 @@ public class Grid extends ResizeComposite implements } private double getFrozenColumnsWidth() { - double result = 0.0d; + double value = getMultiSelectColumnWidth(); for (int i = 0; i < getFrozenColumnCount(); i++) { - result += getColumn(i).getWidthActual(); + value += getColumn(i).getWidthActual(); + } + return value; + } + + private double getMultiSelectColumnWidth() { + if (getSelectionModel().getSelectionColumnRenderer() != null) { + // frozen checkbox column is present, it is always the first + // column + return escalator.getHeader().getElement() + .getFirstChildElement().getFirstChildElement() + .getOffsetWidth(); + } + return 0.0; + } + + /** + * Returns the amount of frozen columns. The selection column is always + * considered frozen, since it can't be moved. + */ + private int getSelectionAndFrozenColumnCount() { + // no matter if selection column is frozen or not, it is considered + // frozen for column dnd reorder + if (getSelectionModel().getSelectionColumnRenderer() != null) { + return Math.max(0, getFrozenColumnCount()) + 1; + } else { + return Math.max(0, getFrozenColumnCount()); } - return result; } private void calculatePossibleDropPositions() { @@ -3125,10 +3153,9 @@ public class Grid extends ResizeComposite implements if (!calculatePossibleDropPositionInsideSpannedHeader()) { HashMap columnIndexToDropPositionMap = new HashMap(); - final int frozenColumns = Math.max(0, getFrozenColumnCount()); + final int frozenColumns = getSelectionAndFrozenColumnCount(); double position = getFrozenColumnsWidth(); // add all columns except frozen columns - // TODO handle the case where the checkbox column is present for (int i = frozenColumns; i < getColumnCount(); i++) { columnIndexToDropPositionMap.put(i, position); position += getColumn(i).getWidthActual(); 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 ae0ab62aab..c7113dbd5e 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 @@ -18,9 +18,12 @@ package com.vaadin.tests.components.grid.basicfeatures.server; 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 com.vaadin.testbench.TestBenchElement; import com.vaadin.tests.components.grid.basicfeatures.GridBasicFeaturesTest; /** @@ -212,6 +215,77 @@ public class GridColumnReorderTest extends GridBasicFeaturesTest { assertColumnHeaderOrder(0, 2, 1, 3); } + @Test + public void testColumnReorder_draggingColumnLeftOfMultiSelectionColumn_columnDroppedRight() { + // given + openTestURL(); + toggleColumnReordering(); + selectMenuPath("Component", "State", "Selection mode", "multi"); + List gridHeaderRowCells = getGridHeaderRowCells(); + assertTrue(gridHeaderRowCells.get(0).getText().equals("")); + assertColumnHeader("Column 0", gridHeaderRowCells.get(1)); + assertColumnHeader("Column 1", gridHeaderRowCells.get(2)); + assertColumnHeader("Column 2", gridHeaderRowCells.get(3)); + + // when + dragAndDropDefaultColumnHeader(2, 0, 2); + + // then + gridHeaderRowCells = getGridHeaderRowCells(); + assertTrue(gridHeaderRowCells.get(0).getText().equals("")); + assertColumnHeader("Column 1", gridHeaderRowCells.get(1)); + assertColumnHeader("Column 0", gridHeaderRowCells.get(2)); + assertColumnHeader("Column 2", gridHeaderRowCells.get(3)); + } + + @Test + public void testColumnReorder_multiSelectionAndFrozenColumns_columnDroppedRight() { + // given + openTestURL(); + toggleColumnReordering(); + selectMenuPath("Component", "State", "Selection mode", "multi"); + setFrozenColumns(1); + List gridHeaderRowCells = getGridHeaderRowCells(); + assertTrue(gridHeaderRowCells.get(0).getText().equals("")); + assertColumnHeader("Column 0", gridHeaderRowCells.get(1)); + assertColumnHeader("Column 1", gridHeaderRowCells.get(2)); + assertColumnHeader("Column 2", gridHeaderRowCells.get(3)); + + // when + dragAndDropDefaultColumnHeader(3, 0, 2); + + // then + gridHeaderRowCells = getGridHeaderRowCells(); + assertTrue(gridHeaderRowCells.get(0).getText().equals("")); + assertColumnHeader("Column 0", gridHeaderRowCells.get(1)); + assertColumnHeader("Column 2", gridHeaderRowCells.get(2)); + assertColumnHeader("Column 1", gridHeaderRowCells.get(3)); + } + + @Test + public void testColumnReordering_multiSelectionColumnNotFrozen_stillCantDropLeftSide() { + // given + openTestURL(); + toggleColumnReordering(); + selectMenuPath("Component", "State", "Selection mode", "multi"); + setFrozenColumns(-1); + List gridHeaderRowCells = getGridHeaderRowCells(); + assertTrue(gridHeaderRowCells.get(0).getText().equals("")); + assertColumnHeader("Column 0", gridHeaderRowCells.get(1)); + assertColumnHeader("Column 1", gridHeaderRowCells.get(2)); + assertColumnHeader("Column 2", gridHeaderRowCells.get(3)); + + // when + dragAndDropDefaultColumnHeader(2, 0, 2); + + // then + gridHeaderRowCells = getGridHeaderRowCells(); + assertTrue(gridHeaderRowCells.get(0).getText().equals("")); + assertColumnHeader("Column 1", gridHeaderRowCells.get(1)); + assertColumnHeader("Column 0", gridHeaderRowCells.get(2)); + assertColumnHeader("Column 2", gridHeaderRowCells.get(3)); + } + @Test public void testColumnReordering_twoHeaderRows_dndReorderingPossibleFromFirstRow() { // given @@ -235,7 +309,6 @@ public class GridColumnReorderTest extends GridBasicFeaturesTest { selectMenuPath("Component", "Header", "Append row"); assertColumnHeaderOrder(0, 1, 2, 3); - // when // when dragAndDropColumnHeader(1, 0, 2, 100); -- cgit v1.2.3 From e70ac88c0f34c0d0ce9344da2891e466223632b5 Mon Sep 17 00:00:00 2001 From: Henrik Paul Date: Fri, 27 Feb 2015 11:31:17 +0200 Subject: Escalator can now remove rows while spacers are open (#16644) Change-Id: I1fad1d14727d706bcc36021b77ec33dc8e76b632 --- .../src/com/vaadin/client/widgets/Escalator.java | 123 +++++++++++++++------ 1 file changed, 88 insertions(+), 35 deletions(-) (limited to 'client/src') diff --git a/client/src/com/vaadin/client/widgets/Escalator.java b/client/src/com/vaadin/client/widgets/Escalator.java index 0df078d26e..0f7ac891a9 100644 --- a/client/src/com/vaadin/client/widgets/Escalator.java +++ b/client/src/com/vaadin/client/widgets/Escalator.java @@ -2595,12 +2595,7 @@ public class Escalator extends Widget implements RequiresResize, return; } - final double pxDiff = numberOfRows * getDefaultRowHeight(); - for (SpacerContainer.SpacerImpl spacer : spacerContainer - .getSpacersForRowAndAfter(index)) { - spacer.setPositionDiff(0, pxDiff); - spacer.setRowIndex(spacer.getRow() + numberOfRows); - } + spacerContainer.shiftSpacersByRows(index, numberOfRows); /* * TODO: this method should probably only add physical rows, and not @@ -2679,7 +2674,6 @@ public class Escalator extends Widget implements RequiresResize, .getSpacerHeight(logicalRowIndexCursor++); final TableRowElement tr = i.next(); - getLogger().warning("iterate"); setRowPosition(tr, 0, rowTop); rowTop += getDefaultRowHeight(); } @@ -2895,13 +2889,9 @@ public class Escalator extends Widget implements RequiresResize, tr = visualRowOrder.get(i); } - getLogger().warning("y: " + y + ", index: " + i); setRowPosition(tr, 0, y); y += getDefaultRowHeight(); - double spacerHeight = spacerContainer.getSpacerHeight(i); - getLogger().warning( - "height: " + spacerHeight + ", index: " + i); - y += spacerHeight; + y += spacerContainer.getSpacerHeight(i); } return addedRows; @@ -2928,13 +2918,16 @@ public class Escalator extends Widget implements RequiresResize, return; } - // TODO - getLogger().warning("[[spacers]] removing rows"); - final Range viewportRange = getVisibleRowRange(); final Range removedRowsRange = Range .withLength(index, numberOfRows); + /* + * Removing spacers as the very first step will correct the + * scrollbars and row offsets right away. + */ + spacerContainer.paintRemoveSpacers(removedRowsRange); + final Range[] partitions = removedRowsRange .partitionWith(viewportRange); final Range removedAbove = partitions[0]; @@ -2960,7 +2953,7 @@ public class Escalator extends Widget implements RequiresResize, * absolute 0) * * The logic is optimized in such a way that the - * adjustScrollPosIgnoreEvents is called only once, to avoid extra + * moveViewportAndContent is called only once, to avoid extra * reflows, and thus the code might seem a bit obscure. */ final boolean firstVisualRowIsRemoved = !removedVisualInside @@ -2995,7 +2988,7 @@ public class Escalator extends Widget implements RequiresResize, // ranges evaluated, let's do things. if (!removedVisualInside.isEmpty()) { - int escalatorRowCount = bodyElem.getChildCount(); + int escalatorRowCount = body.getEscalatorRowCount(); /* * remember: the rows have already been subtracted from the row @@ -3026,9 +3019,12 @@ public class Escalator extends Widget implements RequiresResize, * visualIndex == logicalIndex applies now. */ final int dirtyRowsStart = removedLogicalInside.getStart(); + double y = getRowTop(dirtyRowsStart); for (int i = dirtyRowsStart; i < escalatorRowCount; i++) { final TableRowElement tr = visualRowOrder.get(i); - setRowPosition(tr, 0, i * getDefaultRowHeight()); + setRowPosition(tr, 0, y); + y += getDefaultRowHeight(); + y += spacerContainer.getSpacerHeight(i); } /* @@ -3185,6 +3181,8 @@ public class Escalator extends Widget implements RequiresResize, final TableRowElement tr = visualRowOrder.get(i); setRowPosition(tr, 0, (int) newTop); newTop += getDefaultRowHeight(); + newTop += spacerContainer.getSpacerHeight(i + + removedLogicalInside.getStart()); } /* @@ -3283,13 +3281,15 @@ public class Escalator extends Widget implements RequiresResize, final ListIterator iterator = visualRowOrder .listIterator(removedVisualInside.getStart()); - double rowTop = (removedLogicalInside.getStart() + logicalOffset) - * getDefaultRowHeight(); + double rowTop = getRowTop(removedLogicalInside.getStart() + + logicalOffset); for (int i = removedVisualInside.getStart(); i < escalatorRowCount - removedVisualInside.length(); i++) { final TableRowElement tr = iterator.next(); setRowPosition(tr, 0, rowTop); rowTop += getDefaultRowHeight(); + rowTop += spacerContainer.getSpacerHeight(i + + removedLogicalInside.getStart()); } } @@ -3309,14 +3309,18 @@ public class Escalator extends Widget implements RequiresResize, logicalTargetIndex); // move the surrounding rows to their correct places. + int firstUpdatedIndex = removedVisualInside.getEnd(); final ListIterator iterator = visualRowOrder - .listIterator(removedVisualInside.getEnd()); - double rowTop = removedLogicalInside.getStart() - * getDefaultRowHeight(); + .listIterator(firstUpdatedIndex); + + double rowTop = getRowTop(removedLogicalInside.getStart()); + int i = 0; while (iterator.hasNext()) { final TableRowElement tr = iterator.next(); setRowPosition(tr, 0, rowTop); rowTop += getDefaultRowHeight(); + rowTop += spacerContainer.getSpacerHeight(firstUpdatedIndex + + i++); } } @@ -3625,6 +3629,9 @@ public class Escalator extends Widget implements RequiresResize, Profiler.enter("Escalator.BodyRowContainer.reapplyDefaultRowHeights"); + // TODO + getLogger().warning("[[spacer]] reapply default body row heights"); + /* step 1: resize and reposition rows */ for (int i = 0; i < visualRowOrder.size(); i++) { TableRowElement tr = visualRowOrder.get(i); @@ -3841,7 +3848,7 @@ public class Escalator extends Widget implements RequiresResize, * * @return the actual DOM count of escalator rows */ - private int getEscalatorRowCount() { + public int getEscalatorRowCount() { return root.getChildCount() - spacerContainer.getSpacersInDom().size(); } @@ -4495,6 +4502,10 @@ public class Escalator extends Widget implements RequiresResize, return positions.getTop(getRootElement()); } + /** + * Sets a new row index for this spacer. Also updates the bookeeping + * at {@link SpacerContainer#rowIndexToSpacer}. + */ @SuppressWarnings("boxing") public void setRowIndex(int rowIndex) { SpacerImpl spacer = rowIndexToSpacer.remove(this.rowIndex); @@ -4528,6 +4539,34 @@ public class Escalator extends Widget implements RequiresResize, } } + public void paintRemoveSpacers(Range removedRowsRange) { + removeSpacers(removedRowsRange); + shiftSpacersByRows(removedRowsRange.getStart(), + -removedRowsRange.length()); + } + + @SuppressWarnings("boxing") + public void removeSpacers(Range removedRange) { + + Map removedSpacers = rowIndexToSpacer + .subMap(removedRange.getStart(), true, + removedRange.getEnd(), false); + + for (SpacerImpl spacer : removedSpacers.values()) { + /* + * [[optimization]] TODO: Each invocation of the setHeight + * method has a cascading effect in the DOM. if this proves to + * be slow, the DOM offset could be updated as a batch. + */ + + destroySpacerContent(spacer); + spacer.setHeight(0); // resets row offsets + spacer.getRootElement().removeFromParent(); + } + + removedSpacers.clear(); + } + /** * Calculates the sum of all spacers. * @@ -4822,17 +4861,8 @@ public class Escalator extends Widget implements RequiresResize, return rowIndexToSpacer.get(Integer.valueOf(rowIndex)); } - @SuppressWarnings("boxing") private void removeSpacer(int rowIndex) { - SpacerImpl spacer = getSpacer(rowIndex); - - // fix DOM - destroySpacerContent(spacer); - spacer.setHeight(0); // resets row offsets - spacer.getRootElement().removeFromParent(); - - // fix bookkeeping - rowIndexToSpacer.remove(rowIndex); + removeSpacers(Range.withOnly(rowIndex)); } public void setStylePrimaryName(String style) { @@ -4915,6 +4945,29 @@ public class Escalator extends Widget implements RequiresResize, spacer.setPosition(0, spacer.getTop() + diffPx); } } + + /** + * Shifts spacers at and after a specific row by an amount of rows. + *

+ * This moves both their associated row index and also their visual + * placement. + *

+ * Note: This method does not check for the validity of any + * arguments. + * + * @param index + * the index of first row to move + * @param numberOfRows + * the number of rows to shift the spacers with. A positive + * value is downwards, a negative value is upwards. + */ + public void shiftSpacersByRows(int index, int numberOfRows) { + final double pxDiff = numberOfRows * body.getDefaultRowHeight(); + for (SpacerContainer.SpacerImpl spacer : getSpacersForRowAndAfter(index)) { + spacer.setPositionDiff(0, pxDiff); + spacer.setRowIndex(spacer.getRow() + numberOfRows); + } + } } private class ElementPositionBookkeeper { @@ -5251,7 +5304,7 @@ public class Escalator extends Widget implements RequiresResize, * updated correctly. Since it isn't, we'll simply and brutally rip out * the DOM elements (in an elegant way, of course). */ - int rowsToRemove = bodyElem.getChildCount(); + int rowsToRemove = body.getEscalatorRowCount(); for (int i = 0; i < rowsToRemove; i++) { int index = rowsToRemove - i - 1; TableRowElement tr = bodyElem.getRows().getItem(index); -- cgit v1.2.3 From 1d5e5a9b1fdc23d0c61783eb5224cd0d826e881e Mon Sep 17 00:00:00 2001 From: Henrik Paul Date: Mon, 2 Mar 2015 11:55:35 +0200 Subject: Adds support for spacers near the bottom of Escalator (#16644) Change-Id: Idca22ecbe9a2db4dbfb26481694255c3a8791b67 --- client/src/com/vaadin/client/widgets/Escalator.java | 14 +++++++------- .../client/grid/EscalatorBasicClientFeaturesWidget.java | 1 + 2 files changed, 8 insertions(+), 7 deletions(-) (limited to 'client/src') diff --git a/client/src/com/vaadin/client/widgets/Escalator.java b/client/src/com/vaadin/client/widgets/Escalator.java index 0f7ac891a9..b36b94c8f3 100644 --- a/client/src/com/vaadin/client/widgets/Escalator.java +++ b/client/src/com/vaadin/client/widgets/Escalator.java @@ -2421,13 +2421,6 @@ public class Escalator extends Widget implements RequiresResize, return; } - /* - * TODO This will break the logical index calculation, as it will - * try to search for non- - */ - getLogger().warning( - "[[spacers]] scrolling and spacers near the bottom"); - boolean rowsWereMoved = false; final double topElementPosition; @@ -2527,6 +2520,13 @@ public class Escalator extends Widget implements RequiresResize, aRowWasLeftBehind = true; } + /* + * Make sure we don't scroll beyond the row content. This can + * happen if we have spacers for the last rows. + */ + rowsToMove = Math.max(0, + Math.min(rowsToMove, getRowCount() - logicalRowIndex)); + moveAndUpdateEscalatorRows(Range.between(0, rowsToMove), targetVisualIndex, logicalRowIndex); 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 37c0865e35..538f7a21a1 100644 --- a/uitest/src/com/vaadin/tests/widgetset/client/grid/EscalatorBasicClientFeaturesWidget.java +++ b/uitest/src/com/vaadin/tests/widgetset/client/grid/EscalatorBasicClientFeaturesWidget.java @@ -647,6 +647,7 @@ public class EscalatorBasicClientFeaturesWidget extends createSpacersMenuForRow(1, menupath); createSpacersMenuForRow(50, menupath); + createSpacersMenuForRow(99, menupath); } private void createSpacersMenuForRow(final int rowIndex, String[] menupath) { -- cgit v1.2.3 From 46be8e40721621e8946b2f2d64e6ecd1d9accdce Mon Sep 17 00:00:00 2001 From: Henrik Paul Date: Mon, 2 Mar 2015 15:56:17 +0200 Subject: Adds tests and JavaDoc regarding Escalator's spacers (#16644) Change-Id: I9b35a48df8b4e9801c26a25eb1ccce5351c61ddd --- .../client/widget/escalator/RowContainer.java | 26 +++++++++ .../src/com/vaadin/client/widgets/Escalator.java | 9 ++- .../EscalatorBasicClientFeaturesTest.java | 16 ++++++ .../escalator/EscalatorSpacerTest.java | 65 +++++++++++++++++++++- 4 files changed, 113 insertions(+), 3 deletions(-) (limited to 'client/src') diff --git a/client/src/com/vaadin/client/widget/escalator/RowContainer.java b/client/src/com/vaadin/client/widget/escalator/RowContainer.java index ea56c25062..e2baf9a03b 100644 --- a/client/src/com/vaadin/client/widget/escalator/RowContainer.java +++ b/client/src/com/vaadin/client/widget/escalator/RowContainer.java @@ -50,6 +50,10 @@ public interface RowContainer { *

* If a spacer is already registered with the given row index, that * spacer will be updated with the given height. + *

+ * Note: The row index for a spacer will change if rows are + * inserted or removed above the current position. Spacers will also be + * removed alongside their associated rows * * @param rowIndex * the row index for the spacer to modify. The affected @@ -59,6 +63,8 @@ public interface RowContainer { * negative, the affected spacer (if exists) will be removed * @throws IllegalArgumentException * if {@code rowIndex} is not a valid row index + * @see #insertRows(int, int) + * @see #removeRows(int, int) */ void setSpacer(int rowIndex, double height) throws IllegalArgumentException; @@ -88,6 +94,26 @@ public interface RowContainer { */ SpacerUpdater getSpacerUpdater(); + /** + * {@inheritDoc} + *

+ * Any spacers underneath {@code index} will be offset and "pushed" + * down. This also modifies the row index they are associated with. + */ + @Override + public void insertRows(int index, int numberOfRows) + throws IndexOutOfBoundsException, IllegalArgumentException; + + /** + * {@inheritDoc} + *

+ * Any spacers underneath {@code index} will be offset and "pulled" up. + * This also modifies the row index they are associated with. Any + * spacers in the removed range will also be closed and removed. + */ + @Override + public void removeRows(int index, int numberOfRows) + throws IndexOutOfBoundsException, IllegalArgumentException; } /** diff --git a/client/src/com/vaadin/client/widgets/Escalator.java b/client/src/com/vaadin/client/widgets/Escalator.java index b36b94c8f3..9d5526b14a 100644 --- a/client/src/com/vaadin/client/widgets/Escalator.java +++ b/client/src/com/vaadin/client/widgets/Escalator.java @@ -2925,6 +2925,12 @@ public class Escalator extends Widget implements RequiresResize, /* * Removing spacers as the very first step will correct the * scrollbars and row offsets right away. + * + * TODO: actually, it kinda sounds like a Grid feature that a spacer + * would be associated with a particular row. Maybe it would be + * better to have a spacer separate from rows, and simply collapse + * them if they happen to end up on top of each other. This would + * probably make supporting the -1 row pretty easy, too. */ spacerContainer.paintRemoveSpacers(removedRowsRange); @@ -3576,7 +3582,7 @@ public class Escalator extends Widget implements RequiresResize, // TODO getLogger().warning( - "[[spacers]] removing rows while shrinking the body " + "[[spacers]] removing spacers while shrinking the body " + "section"); final ListIterator iter = visualRowOrder @@ -4511,6 +4517,7 @@ public class Escalator extends Widget implements RequiresResize, SpacerImpl spacer = rowIndexToSpacer.remove(this.rowIndex); assert this == spacer : "trying to move an unexpected spacer."; this.rowIndex = rowIndex; + root.setPropertyInt(SPACER_LOGICAL_ROW_PROPERTY, rowIndex); rowIndexToSpacer.put(this.rowIndex, this); } } 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 c22da47d62..25c4c1ff67 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/EscalatorBasicClientFeaturesTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/EscalatorBasicClientFeaturesTest.java @@ -70,6 +70,7 @@ public abstract class EscalatorBasicClientFeaturesTest extends MultiBrowserTest 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 ROW_99 = "Row 99"; protected static final String SET_100PX = "Set 100px"; protected static final String REMOVE = "Remove"; @@ -245,6 +246,11 @@ public abstract class EscalatorBasicClientFeaturesTest extends MultiBrowserTest getVerticalScrollbar().scroll(px); } + protected long getScrollTop() { + return ((Long) executeScript("return arguments[0].scrollTop;", + getVerticalScrollbar())).longValue(); + } + private TestBenchElement getVerticalScrollbar() { return (TestBenchElement) getEscalator().findElement( By.className("v-escalator-scroller-vertical")); @@ -254,6 +260,11 @@ public abstract class EscalatorBasicClientFeaturesTest extends MultiBrowserTest getHorizontalScrollbar().scrollLeft(px); } + private long getScrollLeft() { + return ((Long) executeScript("return arguments[0].scrollLeft;", + getHorizontalScrollbar())).longValue(); + } + private TestBenchElement getHorizontalScrollbar() { return (TestBenchElement) getEscalator().findElement( By.className("v-escalator-scroller-horizontal")); @@ -271,6 +282,11 @@ public abstract class EscalatorBasicClientFeaturesTest extends MultiBrowserTest return getEscalator().findElements(By.className("v-escalator-spacer")); } + protected boolean spacersAreFoundInDom() { + List spacers = getSpacers(); + return spacers != null && !spacers.isEmpty(); + } + @SuppressWarnings("boxing") protected WebElement getSpacer(int logicalRowIndex) { List spacers = getSpacers(); 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 index 1985de1c82..85acdc29cc 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/escalator/EscalatorSpacerTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/escalator/EscalatorSpacerTest.java @@ -16,6 +16,7 @@ package com.vaadin.tests.components.grid.basicfeatures.escalator; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -28,6 +29,7 @@ import org.junit.Test; import org.openqa.selenium.WebElement; import com.vaadin.client.WidgetUtil; +import com.vaadin.testbench.elements.NotificationElement; import com.vaadin.tests.components.grid.basicfeatures.EscalatorBasicClientFeaturesTest; @SuppressWarnings("boxing") @@ -87,7 +89,8 @@ public class EscalatorSpacerTest extends EscalatorBasicClientFeaturesTest { @Test public void openVisibleSpacer() { - assertNull("No spacers should be shown at the start", getSpacer(1)); + assertFalse("No spacers should be shown at the start", + spacersAreFoundInDom()); selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX); assertNotNull("Spacer should be shown after setting it", getSpacer(1)); } @@ -117,7 +120,7 @@ public class EscalatorSpacerTest extends EscalatorBasicClientFeaturesTest { selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX); double oldTop = getElementTop(getSpacer(1)); selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, ADD_ONE_ROW_TO_BEGINNING); - double newTop = getElementTop(getSpacer(1)); + double newTop = getElementTop(getSpacer(2)); assertGreater("Spacer should've been pushed down", newTop, oldTop); } @@ -152,6 +155,64 @@ public class EscalatorSpacerTest extends EscalatorBasicClientFeaturesTest { spacerTop + 100, rowTop, WidgetUtil.PIXEL_EPSILON); } + @Test + public void addSpacerAtBottomThenScrollThere() { + selectMenuPath(FEATURES, SPACERS, ROW_99, SET_100PX); + scrollVerticallyTo(999999); + + assertFalse("Did not expect a notification", + $(NotificationElement.class).exists()); + } + + @Test + public void scrollToBottomThenAddSpacerThere() { + scrollVerticallyTo(999999); + long oldBottomScrollTop = getScrollTop(); + selectMenuPath(FEATURES, SPACERS, ROW_99, SET_100PX); + + assertEquals("Adding a spacer underneath the current viewport should " + + "not scroll anywhere", oldBottomScrollTop, getScrollTop()); + assertFalse("Got an unexpected notification", + $(NotificationElement.class).exists()); + + scrollVerticallyTo(999999); + + assertFalse("Got an unexpected notification", + $(NotificationElement.class).exists()); + assertGreater("Adding a spacer should've made the scrollbar scroll " + + "further", getScrollTop(), oldBottomScrollTop); + } + + @Test + public void removingRowAboveSpacerMovesSpacerUp() { + selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX); + WebElement spacer = getSpacer(1); + double originalElementTop = getElementTop(spacer); + + selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, + REMOVE_ONE_ROW_FROM_BEGINNING); + assertLessThan("spacer should've moved up", getElementTop(spacer), + originalElementTop); + assertNull("No spacer for row 1 should be found after removing the " + + "top row", getSpacer(1)); + } + + @Test + public void removingSpacedRowRemovesSpacer() { + selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX); + assertTrue("Spacer should've been found in the DOM", + spacersAreFoundInDom()); + + selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, + REMOVE_ONE_ROW_FROM_BEGINNING); + selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, + REMOVE_ONE_ROW_FROM_BEGINNING); + + assertFalse("No spacers should be in the DOM after removing " + + "associated spacer", spacersAreFoundInDom()); + + } + private static double getElementTop(WebElement element) { /* * we need to parse the style attribute, since using getCssValue gets a -- cgit v1.2.3 From b973c65eaff99ab2575854f12bb046e968baa3ff Mon Sep 17 00:00:00 2001 From: Henrik Paul Date: Tue, 3 Mar 2015 13:19:33 +0200 Subject: Escalator spacers are sized and placed like "fixed" (#16644) The width of a spacer is now 100% of the viewport (width of escalator minus possible scrollbars), and and are always horizontally frozen with the viewport. Change-Id: I6616747784cdb61551e144d941526dee815a7ef9 --- .../com/vaadin/client/widget/escalator/Spacer.java | 4 +- .../src/com/vaadin/client/widgets/Escalator.java | 115 +++++++++++++++++---- .../escalator/EscalatorSpacerTest.java | 111 +++++++++++++++----- 3 files changed, 184 insertions(+), 46 deletions(-) (limited to 'client/src') diff --git a/client/src/com/vaadin/client/widget/escalator/Spacer.java b/client/src/com/vaadin/client/widget/escalator/Spacer.java index 371d539f57..1e9985d6c1 100644 --- a/client/src/com/vaadin/client/widget/escalator/Spacer.java +++ b/client/src/com/vaadin/client/widget/escalator/Spacer.java @@ -16,10 +16,10 @@ package com.vaadin.client.widget.escalator; import com.google.gwt.dom.client.Element; -import com.vaadin.client.widget.escalator.RowContainer.BodyRowContainer; /** - * A representation of a spacer element in a {@link BodyRowContainer}. + * A representation of a spacer element in a + * {@link com.vaadin.client.widget.escalator.RowContainer.BodyRowContainer}. * * @since * @author Vaadin Ltd diff --git a/client/src/com/vaadin/client/widgets/Escalator.java b/client/src/com/vaadin/client/widgets/Escalator.java index 9d5526b14a..1d7d9a9910 100644 --- a/client/src/com/vaadin/client/widgets/Escalator.java +++ b/client/src/com/vaadin/client/widgets/Escalator.java @@ -1698,13 +1698,11 @@ public class Escalator extends Widget implements RequiresResize, public void setColumnFrozen(int column, boolean frozen) { final NodeList childRows = root.getRows(); - if (column == 0 && this instanceof BodyRowContainer) { - // TODO - getLogger().warning("[[spacers]] freezing columns"); - } - for (int row = 0; row < childRows.getLength(); row++) { final TableRowElement tr = childRows.getItem(row); + if (!rowCanBeFrozen(tr)) { + continue; + } TableCellElement cell = tr.getCells().getItem(column); if (frozen) { @@ -1723,19 +1721,31 @@ public class Escalator extends Widget implements RequiresResize, public void updateFreezePosition(int column, double scrollLeft) { final NodeList childRows = root.getRows(); - if (column == 0 && this instanceof BodyRowContainer) { - // TODO - getLogger().warning("[[spacers]] update freeze position"); - } - for (int row = 0; row < childRows.getLength(); row++) { final TableRowElement tr = childRows.getItem(row); - TableCellElement cell = tr.getCells().getItem(column); - position.set(cell, scrollLeft, 0); + if (rowCanBeFrozen(tr)) { + TableCellElement cell = tr.getCells().getItem(column); + position.set(cell, scrollLeft, 0); + } } } + /** + * Checks whether a row is an element, or contains such elements, that + * can be frozen. + *

+ * In practice, this applies for all header and footer rows. For body + * rows, it applies for all rows except spacer rows. + * + * @param tr + * the row element to check for if it is or has elements that + * can be frozen + * @return true iff this the given element, or any of its + * descendants, can be frozen + */ + abstract protected boolean rowCanBeFrozen(TableRowElement tr); + /** * Iterates through all the cells in a column and returns the width of * the widest element in this RowContainer. @@ -2231,6 +2241,12 @@ public class Escalator extends Widget implements RequiresResize, protected void paintInsertRows(int visualIndex, int numberOfRows) { paintInsertStaticRows(visualIndex, numberOfRows); } + + @Override + protected boolean rowCanBeFrozen(TableRowElement tr) { + assert root.isOrHasChild(tr) : "Row does not belong to this table section"; + return true; + } } private class HeaderRowContainer extends AbstractStaticRowContainer { @@ -3858,6 +3874,15 @@ public class Escalator extends Widget implements RequiresResize, return root.getChildCount() - spacerContainer.getSpacersInDom().size(); } + + @Override + protected boolean rowCanBeFrozen(TableRowElement tr) { + return visualRowOrder.contains(tr); + } + + public void reapplySpacerWidths() { + spacerContainer.reapplySpacerWidths(); + } } private class ColumnConfigurationImpl implements ColumnConfiguration { @@ -4376,18 +4401,13 @@ public class Escalator extends Widget implements RequiresResize, setPosition(getLeft() + x, getTop() + y); } - public double getLeft() { - // not implemented yet. - return 0; - } - public void setupDom(double height) { assert !domHasBeenSetup : "DOM can't be set up twice."; assert RootPanel.get().getElement().isOrHasChild(root) : "Root element should've been attached to the DOM by now."; domHasBeenSetup = true; + getRootElement().getStyle().setWidth(getInnerWidth(), Unit.PX); setHeight(height); - root.getStyle().setWidth(100, Unit.PCT); spacerElement.getStyle().setWidth(100, Unit.PCT); spacerElement.setColSpan(getColumnConfiguration() @@ -4508,6 +4528,10 @@ public class Escalator extends Widget implements RequiresResize, return positions.getTop(getRootElement()); } + public double getLeft() { + return positions.getLeft(getRootElement()); + } + /** * Sets a new row index for this spacer. Also updates the bookeeping * at {@link SpacerContainer#rowIndexToSpacer}. @@ -4526,6 +4550,23 @@ public class Escalator extends Widget implements RequiresResize, private SpacerUpdater spacerUpdater = SpacerUpdater.NULL; + private final ScrollHandler spacerScroller = new ScrollHandler() { + private double prevScrollX = 0; + + @Override + public void onScroll(ScrollEvent event) { + if (WidgetUtil.pixelValuesEqual(getScrollLeft(), prevScrollX)) { + return; + } + + prevScrollX = getScrollLeft(); + for (SpacerImpl spacer : rowIndexToSpacer.values()) { + spacer.setPosition(prevScrollX, spacer.getTop()); + } + } + }; + private HandlerRegistration spacerScrollerRegistration; + public void setSpacer(int rowIndex, double height) throws IllegalArgumentException { @@ -4546,6 +4587,13 @@ public class Escalator extends Widget implements RequiresResize, } } + public void reapplySpacerWidths() { + for (SpacerImpl spacer : rowIndexToSpacer.values()) { + spacer.getRootElement().getStyle() + .setWidth(getInnerWidth(), Unit.PX); + } + } + public void paintRemoveSpacers(Range removedRowsRange) { removeSpacers(removedRowsRange); shiftSpacersByRows(removedRowsRange.getStart(), @@ -4559,6 +4607,10 @@ public class Escalator extends Widget implements RequiresResize, .subMap(removedRange.getStart(), true, removedRange.getEnd(), false); + if (removedSpacers.isEmpty()) { + return; + } + for (SpacerImpl spacer : removedSpacers.values()) { /* * [[optimization]] TODO: Each invocation of the setHeight @@ -4572,6 +4624,12 @@ public class Escalator extends Widget implements RequiresResize, } removedSpacers.clear(); + + if (rowIndexToSpacer.isEmpty()) { + assert spacerScrollerRegistration != null : "Spacer scroller registration was null"; + spacerScrollerRegistration.removeHandler(); + spacerScrollerRegistration = null; + } } /** @@ -4846,10 +4904,14 @@ public class Escalator extends Widget implements RequiresResize, @SuppressWarnings("boxing") private void insertNewSpacer(int rowIndex, double height) { + if (spacerScrollerRegistration == null) { + spacerScrollerRegistration = addScrollHandler(spacerScroller); + } + SpacerImpl spacer = new SpacerImpl(rowIndex); rowIndexToSpacer.put(rowIndex, spacer); - spacer.setPosition(0, calculateSpacerTop(rowIndex)); + spacer.setPosition(getScrollLeft(), calculateSpacerTop(rowIndex)); TableRowElement spacerRoot = spacer.getRootElement(); spacerRoot.getStyle().setWidth( @@ -4949,7 +5011,7 @@ public class Escalator extends Widget implements RequiresResize, double diffPx) { for (SpacerImpl spacer : rowIndexToSpacer.tailMap(changedRowIndex, false).values()) { - spacer.setPosition(0, spacer.getTop() + diffPx); + spacer.setPositionDiff(0, diffPx); } } @@ -4982,11 +5044,13 @@ public class Escalator extends Widget implements RequiresResize, * A map containing cached values of an element's current top position. */ private final Map elementTopPositionMap = new HashMap(); + private final Map elementLeftPositionMap = 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)); + elementLeftPositionMap.put(e, Double.valueOf(x)); } public double getTop(final Element e) { @@ -4998,8 +5062,18 @@ public class Escalator extends Widget implements RequiresResize, return top.doubleValue(); } + public double getLeft(final Element e) { + Double left = elementLeftPositionMap.get(e); + if (left == null) { + throw new IllegalArgumentException("Element " + e + + " was not found in the position bookkeeping"); + } + return left.doubleValue(); + } + public void remove(Element e) { elementTopPositionMap.remove(e); + elementLeftPositionMap.remove(e); } } @@ -5621,6 +5695,7 @@ public class Escalator extends Widget implements RequiresResize, scroller.recalculateScrollbarsForVirtualViewport(); body.verifyEscalatorCount(); + body.reapplySpacerWidths(); Profiler.leave("Escalator.recalculateElementSizes"); } 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 index 85acdc29cc..5e56d9433a 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/escalator/EscalatorSpacerTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/escalator/EscalatorSpacerTest.java @@ -44,7 +44,15 @@ public class EscalatorSpacerTest extends EscalatorBasicClientFeaturesTest { private final static String TRANSLATE_VALUE_REGEX = "translate(?:3d|)" // "translate" or "translate3d" + "\\(" // literal "(" - + ".+?, " // the x argument, uninteresting + + "(" // start capturing the x argument + + "[0-9]+" // the integer part of the value + + "(?:" // start of the subpixel part of the value + + "\\.[0-9]" // if we have a period, there must be at least one number after it + + "[0-9]*" // any amount of accuracy afterwards is fine + + ")?" // the subpixel part is optional + + ")" + + "(?:px)?" // we don't care if the values are suffixed by "px" or not. + + ", " + "(" // start capturing the y argument + "[0-9]+" // the integer part of the value + "(?:" // start of the subpixel part of the value @@ -52,14 +60,14 @@ public class EscalatorSpacerTest extends EscalatorBasicClientFeaturesTest { + "[0-9]*" // any amount of accuracy afterwards is fine + ")?" // the subpixel part is optional + ")" - + "(?:px)?" // we don't care if the values are suffixed by "px" or not. - + "(?:, .*?)?" // the possible z argument, uninteresting (translate doesn't have one, translate3d does) - + "\\)" // literal ")" - + ";?"; // optional ending semicolon + + "(?:px)?" // we don't care if the values are suffixed by "px" or not. + + "(?:, .*?)?" // the possible z argument, uninteresting (translate doesn't have one, translate3d does) + + "\\)" // literal ")" + + ";?"; // optional ending semicolon // 40px; // 12.34px - private final static String TOP_VALUE_REGEX = + private final static String PIXEL_VALUE_REGEX = "(" // capture the pixel value + "[0-9]+" // the pixel argument + "(?:" // start of the subpixel part of the value @@ -75,11 +83,13 @@ public class EscalatorSpacerTest extends EscalatorBasicClientFeaturesTest { .compile("transform: (.*?);"); // also matches "-webkit-transform"; private final static Pattern TOP_CSS_PATTERN = Pattern .compile("top: (.*?);"); + private final static Pattern LEFT_CSS_PATTERN = Pattern + .compile("left: (.*?);"); private final static Pattern TRANSLATE_VALUE_PATTERN = Pattern .compile(TRANSLATE_VALUE_REGEX); - private final static Pattern TOP_VALUE_PATTERN = Pattern - .compile(TOP_VALUE_REGEX); + private final static Pattern PIXEL_VALUE_PATTERN = Pattern + .compile(PIXEL_VALUE_REGEX); @Before public void before() { @@ -213,7 +223,33 @@ public class EscalatorSpacerTest extends EscalatorBasicClientFeaturesTest { } - private static double getElementTop(WebElement element) { + @Test + public void spacersAreFixedInViewport_firstFreezeThenScroll() { + selectMenuPath(FEATURES, FROZEN_COLUMNS, FREEZE_1_COLUMN); + selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX); + assertEquals("Spacer's left position should've been 0 at the " + + "beginning", 0d, getElementLeft(getSpacer(1)), + WidgetUtil.PIXEL_EPSILON); + + int scrollTo = 10; + scrollHorizontallyTo(scrollTo); + assertEquals("Spacer's left position should've been " + scrollTo + + " after scrolling " + scrollTo + "px", scrollTo, + getElementLeft(getSpacer(1)), WidgetUtil.PIXEL_EPSILON); + } + + @Test + public void spacersAreFixedInViewport_firstScrollThenFreeze() { + selectMenuPath(FEATURES, FROZEN_COLUMNS, FREEZE_1_COLUMN); + int scrollTo = 10; + scrollHorizontallyTo(scrollTo); + selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX); + assertEquals("Spacer's left position should've been " + scrollTo + + " after scrolling " + scrollTo + "px", scrollTo, + getElementLeft(getSpacer(1)), WidgetUtil.PIXEL_EPSILON); + } + + private static double[] getElementDimensions(WebElement element) { /* * we need to parse the style attribute, since using getCssValue gets a * normalized value that is harder to parse. @@ -222,17 +258,34 @@ public class EscalatorSpacerTest extends EscalatorBasicClientFeaturesTest { String transform = getTransformFromStyle(style); if (transform != null) { - return getTranslateYValue(transform); + return getTranslateValues(transform); } + double[] result = new double[] { -1, -1 }; + String left = getLeftFromStyle(style); + if (left != null) { + result[1] = getPixelValue(left); + } String top = getTopFromStyle(style); if (top != null) { - return getTopValue(top); + result[0] = getPixelValue(top); + } + + if (result[0] != -1 && result[1] != -1) { + return result; + } else { + throw new IllegalArgumentException( + "Could not parse the top position from the CSS \"" + style + + "\""); } + } - throw new IllegalArgumentException( - "Could not parse the top position from the CSS \"" + style - + "\""); + private static double getElementTop(WebElement element) { + return getElementDimensions(element)[1]; + } + + private static double getElementLeft(WebElement element) { + return getElementDimensions(element)[0]; } private static String getTransformFromStyle(String style) { @@ -243,6 +296,10 @@ public class EscalatorSpacerTest extends EscalatorBasicClientFeaturesTest { return getFromStyle(TOP_CSS_PATTERN, style); } + private static String getLeftFromStyle(String style) { + return getFromStyle(LEFT_CSS_PATTERN, style); + } + private static String getFromStyle(Pattern pattern, String style) { Matcher matcher = pattern.matcher(style); if (matcher.find()) { @@ -254,19 +311,25 @@ public class EscalatorSpacerTest extends EscalatorBasicClientFeaturesTest { } } - private static double getTranslateYValue(String translate) { - return getValueFromCss(TRANSLATE_VALUE_PATTERN, translate); - } + /** + * @return {@code [0] == x}, {@code [1] == y} + */ + private static double[] getTranslateValues(String translate) { + Matcher matcher = TRANSLATE_VALUE_PATTERN.matcher(translate); + assertTrue("no matches for " + translate + " against " + + TRANSLATE_VALUE_PATTERN, matcher.find()); + assertEquals("wrong amout of groups matched in " + translate, 2, + matcher.groupCount()); - private static double getTopValue(String top) { - return getValueFromCss(TOP_VALUE_PATTERN, top); + return new double[] { Double.parseDouble(matcher.group(1)), + Double.parseDouble(matcher.group(2)) }; } - private static double getValueFromCss(Pattern pattern, String css) { - Matcher matcher = pattern.matcher(css); - assertTrue("no matches for " + css + " against " - + TRANSLATE_VALUE_PATTERN, matcher.find()); - assertEquals("wrong amount of groups matched in " + css, 1, + private static double getPixelValue(String top) { + Matcher matcher = PIXEL_VALUE_PATTERN.matcher(top); + assertTrue("no matches for " + top + " against " + PIXEL_VALUE_PATTERN, + matcher.find()); + assertEquals("wrong amount of groups matched in " + top, 1, matcher.groupCount()); return Double.parseDouble(matcher.group(1)); } -- cgit v1.2.3 From b4a5adca6c2d69bb521ae71fd584bef7a320e7d0 Mon Sep 17 00:00:00 2001 From: Henrik Paul Date: Tue, 3 Mar 2015 16:49:19 +0200 Subject: Adds support for -1 row index Escalator spacers (#16644) Change-Id: Iced2f089785983ce2ef1d2225517156cec4a7db4 --- .../vaadin/client/widget/escalator/RowContainer.java | 3 ++- client/src/com/vaadin/client/widgets/Escalator.java | 11 +++++++++-- .../EscalatorBasicClientFeaturesTest.java | 1 + .../basicfeatures/escalator/EscalatorSpacerTest.java | 20 +++++++++++++++++++- .../grid/EscalatorBasicClientFeaturesWidget.java | 1 + 5 files changed, 32 insertions(+), 4 deletions(-) (limited to 'client/src') diff --git a/client/src/com/vaadin/client/widget/escalator/RowContainer.java b/client/src/com/vaadin/client/widget/escalator/RowContainer.java index e2baf9a03b..b7aae1f4f3 100644 --- a/client/src/com/vaadin/client/widget/escalator/RowContainer.java +++ b/client/src/com/vaadin/client/widget/escalator/RowContainer.java @@ -57,7 +57,8 @@ public interface RowContainer { * * @param rowIndex * the row index for the spacer to modify. The affected - * spacer is underneath the given index + * spacer is underneath the given index. Use -1 to insert a + * spacer before the first row * @param height * the pixel height of the spacer. If {@code height} is * negative, the affected spacer (if exists) will be removed diff --git a/client/src/com/vaadin/client/widgets/Escalator.java b/client/src/com/vaadin/client/widgets/Escalator.java index 1d7d9a9910..9b11e7aec1 100644 --- a/client/src/com/vaadin/client/widgets/Escalator.java +++ b/client/src/com/vaadin/client/widgets/Escalator.java @@ -4453,8 +4453,15 @@ public class Escalator extends Widget implements RequiresResize, .getScrollSize() + heightDiff); } + /* + * Don't modify the scrollbars if we're expanding the -1 spacer + * while we're scrolled to the top. + */ + boolean minusOneSpacerException = spacerIsGrowing + && getRow() == -1 && body.getTopRowLogicalIndex() == 0; + boolean viewportNeedsScrolling = getRow() < body - .getTopRowLogicalIndex(); + .getTopRowLogicalIndex() && !minusOneSpacerException; if (viewportNeedsScrolling) { /* @@ -4570,7 +4577,7 @@ public class Escalator extends Widget implements RequiresResize, public void setSpacer(int rowIndex, double height) throws IllegalArgumentException { - if (rowIndex < 0 || rowIndex >= getBody().getRowCount()) { + if (rowIndex < -1 || rowIndex >= getBody().getRowCount()) { throw new IllegalArgumentException("invalid row index: " + rowIndex + ", while the body only has " + getBody().getRowCount() + " rows."); 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 25c4c1ff67..853489746f 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/EscalatorBasicClientFeaturesTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/EscalatorBasicClientFeaturesTest.java @@ -69,6 +69,7 @@ public abstract class EscalatorBasicClientFeaturesTest extends MultiBrowserTest 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_MINUS1 = "Row -1"; protected static final String ROW_1 = "Row 1"; protected static final String ROW_99 = "Row 99"; protected static final String SET_100PX = "Set 100px"; 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 index 5e56d9433a..da3472aebf 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/escalator/EscalatorSpacerTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/escalator/EscalatorSpacerTest.java @@ -223,7 +223,7 @@ public class EscalatorSpacerTest extends EscalatorBasicClientFeaturesTest { } - @Test + @Test public void spacersAreFixedInViewport_firstFreezeThenScroll() { selectMenuPath(FEATURES, FROZEN_COLUMNS, FREEZE_1_COLUMN); selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX); @@ -249,6 +249,24 @@ public class EscalatorSpacerTest extends EscalatorBasicClientFeaturesTest { getElementLeft(getSpacer(1)), WidgetUtil.PIXEL_EPSILON); } + @Test + public void addingMinusOneSpacerDoesNotScrollWhenScrolledAtTop() { + scrollVerticallyTo(5); + selectMenuPath(FEATURES, SPACERS, ROW_MINUS1, SET_100PX); + assertEquals( + "No scroll adjustment should've happened when adding the -1 spacer", + 5, getScrollTop()); + } + + @Test + public void removingMinusOneSpacerScrolls() { + scrollVerticallyTo(5); + selectMenuPath(FEATURES, SPACERS, ROW_MINUS1, SET_100PX); + selectMenuPath(FEATURES, SPACERS, ROW_MINUS1, REMOVE); + assertEquals("Scroll adjustment should've happened when removing the " + + "-1 spacer", 0, getScrollTop()); + } + private static double[] getElementDimensions(WebElement element) { /* * we need to parse the style attribute, since using getCssValue gets a 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 538f7a21a1..dc86b89167 100644 --- a/uitest/src/com/vaadin/tests/widgetset/client/grid/EscalatorBasicClientFeaturesWidget.java +++ b/uitest/src/com/vaadin/tests/widgetset/client/grid/EscalatorBasicClientFeaturesWidget.java @@ -645,6 +645,7 @@ public class EscalatorBasicClientFeaturesWidget extends } }, menupath); + createSpacersMenuForRow(-1, menupath); createSpacersMenuForRow(1, menupath); createSpacersMenuForRow(50, menupath); createSpacersMenuForRow(99, menupath); -- cgit v1.2.3 From de2172eda96ca3f34b2ab4e2e926b13957c113de Mon Sep 17 00:00:00 2001 From: Pekka Hyvönen Date: Tue, 3 Mar 2015 11:15:06 +0200 Subject: Limit dnd-reorder of columns inside spanned headers. (#16643) Doesn't take footers into account, yet. Change-Id: I9c62ca97bcbdb852f2fe7ad7ea2e7de0f0ed02f8 --- .../vaadin/client/ui/dd/DragAndDropHandler.java | 23 ++-- client/src/com/vaadin/client/widgets/Grid.java | 138 +++++++++++++-------- .../grid/basicfeatures/GridBasicFeaturesTest.java | 11 ++ .../grid/basicfeatures/GridColumnReorderTest.java | 118 ++++++++++++++++++ 4 files changed, 232 insertions(+), 58 deletions(-) (limited to 'client/src') diff --git a/client/src/com/vaadin/client/ui/dd/DragAndDropHandler.java b/client/src/com/vaadin/client/ui/dd/DragAndDropHandler.java index eec52d5def..0710606818 100644 --- a/client/src/com/vaadin/client/ui/dd/DragAndDropHandler.java +++ b/client/src/com/vaadin/client/ui/dd/DragAndDropHandler.java @@ -46,12 +46,15 @@ public class DragAndDropHandler { */ public interface DragAndDropCallback { /** - * Called when the drag has started. + * Called when the drag has started. The drag can be canceled by + * returning {@code false}. * * @param startEvent * the original event that started the drag + * @return {@code true} if the drag is OK to start, {@code false} to + * cancel */ - void onDragStart(NativeEvent startEvent); + boolean onDragStart(NativeEvent startEvent); /** * Called on drag. @@ -204,13 +207,15 @@ public class DragAndDropHandler { private void startDrag(NativeEvent startEvent, NativePreviewEvent triggerEvent, DragAndDropCallback callback) { - dragging = true; - // just capture something to prevent text selection in IE - Event.setCapture(RootPanel.getBodyElement()); - this.callback = callback; - dragHandlerRegistration = Event.addNativePreviewHandler(dragHandler); - callback.onDragStart(startEvent); - callback.onDragUpdate(triggerEvent); + if (callback.onDragStart(startEvent)) { + dragging = true; + // just capture something to prevent text selection in IE + Event.setCapture(RootPanel.getBodyElement()); + this.callback = callback; + dragHandlerRegistration = Event + .addNativePreviewHandler(dragHandler); + callback.onDragUpdate(triggerEvent); + } } private void stopDrag() { diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index c237a7ba2f..26e7ba4650 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -3001,19 +3001,38 @@ public class Grid extends ResizeComposite implements } private void resolveDragElementHorizontalPosition(final int clientX) { - int left = clientX - table.getAbsoluteLeft(); - left = Math.max(0, Math.min(left, table.getClientWidth())); + double left = clientX - table.getAbsoluteLeft(); final double frozenColumnsWidth = getFrozenColumnsWidth(); if (left < frozenColumnsWidth) { left = (int) frozenColumnsWidth; } + // do not show the drag element beyond a spanned header cell + // limitation + final Double leftBound = possibleDropPositions.firstKey(); + final Double rightBound = possibleDropPositions.lastKey(); + double scrollLeft = getScrollLeft(); + if (left + scrollLeft < leftBound) { + left = leftBound - scrollLeft + autoScrollX; + } else if (left + scrollLeft > rightBound) { + left = rightBound - scrollLeft + autoScrollX; + } + + // do not show the drag element beyond the grid + left = Math.max(0, Math.min(left, table.getClientWidth())); + left -= dragElement.getClientWidth() / 2; dragElement.getStyle().setLeft(left, Unit.PX); } @Override - public void onDragStart(NativeEvent startingEvent) { + public boolean onDragStart(NativeEvent startingEvent) { + calculatePossibleDropPositions(); + + if (possibleDropPositions.isEmpty()) { + return false; + } + initHeaderDragElementDOM(); // needs to clone focus and sorting indicators too (UX) dragElement = DOM.clone(eventCell.getElement(), true); @@ -3026,12 +3045,11 @@ public class Grid extends ResizeComposite implements // mark the floating cell, for styling & testing dragElement.addClassName("dragged-column-header"); - calculatePossibleDropPositions(); - // start the auto scroll handler autoScroller.setScrollAreaPX(60); autoScroller.start(startingEvent, ScrollAxis.HORIZONTAL, autoScrollerCallback); + return true; } @Override @@ -3147,55 +3165,82 @@ public class Grid extends ResizeComposite implements } } + @SuppressWarnings("boxing") private void calculatePossibleDropPositions() { possibleDropPositions.clear(); - if (!calculatePossibleDropPositionInsideSpannedHeader()) { - HashMap columnIndexToDropPositionMap = new HashMap(); + final int draggedColumnIndex = eventCell.getColumnIndex(); + HashSet unavailableColumnDropIndices = new HashSet(); - final int frozenColumns = getSelectionAndFrozenColumnCount(); - double position = getFrozenColumnsWidth(); - // add all columns except frozen columns - for (int i = frozenColumns; i < getColumnCount(); i++) { - columnIndexToDropPositionMap.put(i, position); - position += getColumn(i).getWidthActual(); - } - // add the right side of the last column as columns.size() - columnIndexToDropPositionMap.put(getColumnCount(), position); + final int frozenColumns = getSelectionAndFrozenColumnCount(); + /* + * If the dragged cell is adjacent to a spanned cell in any other + * header or footer row, then the drag is limited inside that + * spanned cell. The same rules apply: the cell can't be dropped + * inside another spanned cell. The left and right bounds keep track + * of the edges of the most limiting spanned cell. + */ + int leftBound = -1; + int rightBound = getColumnCount() + 1; - // can't drop inside a spanned header from outside it - // -> remove all column indexes that are inside a spanned cell - // in any header row + for (int r = 0; r < getHeaderRowCount(); r++) { + HeaderRow headerRow = getHeaderRow(r); + if (!headerRow.hasSpannedCells()) { + continue; + } for (int c = frozenColumns; c < getColumnCount(); c++) { - for (int r = 0; r < getHeaderRowCount(); r++) { - HeaderRow headerRow = getHeaderRow(r); - if (headerRow.hasSpannedCells()) { - HeaderCell cell = headerRow.getCell(getColumn(c)); - assert cell != null : "Somehow got a null cell for row:cell " - + r + ":" + c; - int colspan = cell.getColspan(); - while (colspan > 1) { - c++; - colspan--; - columnIndexToDropPositionMap.remove(Integer - .valueOf(c)); - } + HeaderCell cell = headerRow.getCell(getColumn(c)); + assert cell != null : "Somehow got a null cell for row:cell " + + r + ":" + c; + int colspan = cell.getColspan(); + if (colspan <= 1) { + continue; + } + final int spannedCellRightEdgeIndex = c + colspan; + if (c <= draggedColumnIndex + && spannedCellRightEdgeIndex > draggedColumnIndex) { + // the spanned cell overlaps the dragged cell + if (c <= draggedColumnIndex && c > leftBound) { + leftBound = c; + } + if (spannedCellRightEdgeIndex < rightBound) { + rightBound = spannedCellRightEdgeIndex; + } + c = spannedCellRightEdgeIndex - 1; + } + + else { // can't drop inside a spanned cell + while (colspan > 1) { + c++; + colspan--; + unavailableColumnDropIndices.add(c); } } - } - // finally lets flip the map, because while dragging we want the - // column index matching the X-coordinate - for (Entry entry : columnIndexToDropPositionMap - .entrySet()) { - possibleDropPositions.put(entry.getValue(), entry.getKey()); } } - } - private boolean calculatePossibleDropPositionInsideSpannedHeader() { - // TODO if the dragged column is inside a spanned header on any row, - // then dragging is limited to inside that spanned cell - return false; + if (leftBound == (rightBound - 1)) { + return; + } + + double position = getFrozenColumnsWidth(); + // iterate column indices and add possible drop positions + for (int i = frozenColumns; i < getColumnCount(); i++) { + if (!unavailableColumnDropIndices.contains(i)) { + if (leftBound != -1) { + if (i >= leftBound && i <= rightBound) { + possibleDropPositions.put(position, i); + } + } else { + possibleDropPositions.put(position, i); + } + } + position += getColumn(i).getWidthActual(); + } + if (leftBound == -1) { + // add the right side of the last column as columns.size() + possibleDropPositions.put(position, getColumnCount()); + } } }; @@ -5494,12 +5539,7 @@ public class Grid extends ResizeComposite implements if (eventCell.getElement().getColSpan() > 1) { return false; } - // for now prevent dragging of columns belonging to a spanned cell - for (int r = 0; r < getHeaderRowCount(); r++) { - if (getHeaderRow(r).getCell(eventCell.getColumn()).getColspan() > 1) { - return false; - } - } + if (event.getTypeInt() == Event.ONMOUSEDOWN && event.getButton() == NativeEvent.BUTTON_LEFT || event.getTypeInt() == Event.ONTOUCHSTART) { 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 13f32fd2a2..0e4f0272dd 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeaturesTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeaturesTest.java @@ -78,6 +78,11 @@ public abstract class GridBasicFeaturesTest extends MultiBrowserTest { getGridVerticalScrollbar()); } + protected void scrollGridHorizontallyTo(double px) { + executeScript("arguments[0].scrollLeft = " + px, + getGridHorizontalScrollbar()); + } + protected int getGridVerticalScrollPos() { return ((Number) executeScript("return arguments[0].scrollTop", getGridVerticalScrollbar())).intValue(); @@ -126,6 +131,12 @@ public abstract class GridBasicFeaturesTest extends MultiBrowserTest { By.xpath("//div[contains(@class, \"v-grid-scroller-vertical\")]")); } + protected WebElement getGridHorizontalScrollbar() { + return getDriver() + .findElement( + By.xpath("//div[contains(@class, \"v-grid-scroller-horizontal\")]")); + } + /** * Reloads the page without restartApplication. This occasionally breaks * stuff. diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderTest.java index 6e5eda43f1..39c6082bed 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderTest.java @@ -235,6 +235,124 @@ public class GridColumnReorderTest extends GridBasicClientFeaturesTest { assertColumnHeaderOrder(1, 2, 0, 3, 4); } + @Test + public void testColumnReorder_cellInsideASpannedHeader_cantBeDroppedOutsideSpannedArea() { + // given + toggleColumnReorder(); + selectMenuPath("Component", "Header", "Append row"); + selectMenuPath("Component", "Header", "Row 2", "Join columns 1, 2"); + assertColumnHeaderOrder(0, 1, 2, 3, 4); + + // when + dragAndDropColumnHeader(0, 2, 0, 20); + + // then + assertColumnHeaderOrder(0, 2, 1, 3, 4); + } + + @Test + public void testColumnReorder_cellInsideTwoCrossingSpanningHeaders_cantTouchThis() { + // given + toggleColumnReorder(); + selectMenuPath("Component", "Header", "Append row"); + selectMenuPath("Component", "Header", "Append row"); + selectMenuPath("Component", "Header", "Row 2", "Join column cells 0, 1"); + selectMenuPath("Component", "Header", "Row 3", "Join columns 1, 2"); + dragAndDropColumnHeader(0, 3, 0, 10); + assertColumnHeaderOrder(3, 0, 1, 2, 4); + + // when + dragAndDropColumnHeader(0, 2, 0, 10); + + // then + assertColumnHeaderOrder(3, 0, 1, 2, 4); + } + + @Test + public void testColumnReorder_cellsInsideSpannedHeaderAndBlockedByOtherSpannedCells_cantTouchThose() { + // given + toggleColumnReorder(); + selectMenuPath("Component", "Header", "Append row"); + selectMenuPath("Component", "Header", "Append row"); + selectMenuPath("Component", "Header", "Row 2", "Join column cells 0, 1"); + selectMenuPath("Component", "Header", "Row 3", "Join columns 1, 2"); + dragAndDropColumnHeader(0, 3, 0, 10); + assertColumnHeaderOrder(3, 0, 1, 2, 4); + + // when then + dragAndDropColumnHeader(0, 1, 3, 10); + assertColumnHeaderOrder(3, 0, 1, 2, 4); + + dragAndDropColumnHeader(1, 2, 1, 10); + assertColumnHeaderOrder(3, 0, 1, 2, 4); + + dragAndDropColumnHeader(2, 1, 3, -10); + assertColumnHeaderOrder(3, 0, 1, 2, 4); + } + + @Test + public void testColumnReorder_cellsInsideSpannedHeaderAndBlockedByOtherSpannedCells_reorderingLimited() { + // given + toggleColumnReorder(); + selectMenuPath("Component", "Header", "Append row"); + selectMenuPath("Component", "Header", "Append row"); + selectMenuPath("Component", "Header", "Row 2", "Join columns 3, 4, 5"); + dragAndDropColumnHeader(0, 0, 4, 100); + selectMenuPath("Component", "Header", "Row 3", "Join columns 1, 2"); + scrollGridHorizontallyTo(0); + assertColumnHeaderOrder(1, 2, 3, 4, 5); + + // when then + dragAndDropColumnHeader(0, 1, 4, 10); + scrollGridHorizontallyTo(0); + assertColumnHeaderOrder(1, 2, 3, 4, 5); + + dragAndDropColumnHeader(0, 2, 4, 10); + scrollGridHorizontallyTo(0); + assertColumnHeaderOrder(1, 2, 3, 4, 5); + + dragAndDropColumnHeader(0, 3, 4, 80); + scrollGridHorizontallyTo(0); + assertColumnHeaderOrder(1, 2, 3, 5, 4); + + dragAndDropColumnHeader(0, 4, 2, 100); + scrollGridHorizontallyTo(0); + assertColumnHeaderOrder(1, 2, 3, 4, 5); + + dragAndDropColumnHeader(2, 3, 4, 80); + scrollGridHorizontallyTo(0); + assertColumnHeaderOrder(1, 2, 3, 5, 4); + + dragAndDropColumnHeader(2, 4, 2, 100); + scrollGridHorizontallyTo(0); + assertColumnHeaderOrder(1, 2, 3, 4, 5); + } + + @Test + public void testColumnReorder_cellsInsideTwoAdjacentSpannedHeaders_reorderingLimited() { + // given + toggleColumnReorder(); + selectMenuPath("Component", "Header", "Append row"); + selectMenuPath("Component", "Header", "Append row"); + selectMenuPath("Component", "Header", "Row 2", "Join columns 3, 4, 5"); + dragAndDropColumnHeader(0, 0, 4, 100); + scrollGridHorizontallyTo(0); + dragAndDropColumnHeader(0, 1, 4, 80); + scrollGridHorizontallyTo(0); + selectMenuPath("Component", "Header", "Row 3", "Join columns 1, 2"); + assertColumnHeaderOrder(1, 3, 4, 5, 2); + + // when then + dragAndDropColumnHeader(0, 1, 3, 80); + assertColumnHeaderOrder(1, 4, 3, 5, 2); + + dragAndDropColumnHeader(0, 2, 4, 10); + assertColumnHeaderOrder(1, 4, 3, 5, 2); + + dragAndDropColumnHeader(0, 2, 0, 10); + assertColumnHeaderOrder(1, 3, 4, 5, 2); + } + private void toggleColumnReorder() { selectMenuPath("Component", "State", "Column Reordering"); } -- cgit v1.2.3 From 3abaa644d90c6460e6514b2e8207c178db01b52d Mon Sep 17 00:00:00 2001 From: Pekka Hyvönen Date: Tue, 3 Mar 2015 14:26:44 +0200 Subject: Take footer into account in column dnd-reorder (#16643) Change-Id: I0358d1b18f65d40325b6c1bc3f3e4d3b9249fb5f --- client/src/com/vaadin/client/widgets/Grid.java | 14 ++-- .../grid/basicfeatures/GridColumnReorderTest.java | 75 +++++++++++++++++++++- 2 files changed, 82 insertions(+), 7 deletions(-) (limited to 'client/src') diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index 26e7ba4650..432228be1e 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -139,6 +139,8 @@ import com.vaadin.client.widget.grid.sort.SortHandler; import com.vaadin.client.widget.grid.sort.SortOrder; import com.vaadin.client.widgets.Escalator.AbstractRowContainer; import com.vaadin.client.widgets.Grid.Editor.State; +import com.vaadin.client.widgets.Grid.StaticSection.StaticCell; +import com.vaadin.client.widgets.Grid.StaticSection.StaticRow; import com.vaadin.shared.data.sort.SortDirection; import com.vaadin.shared.ui.grid.GridConstants; import com.vaadin.shared.ui.grid.GridStaticCellType; @@ -3183,15 +3185,15 @@ public class Grid extends ResizeComposite implements int leftBound = -1; int rightBound = getColumnCount() + 1; - for (int r = 0; r < getHeaderRowCount(); r++) { - HeaderRow headerRow = getHeaderRow(r); - if (!headerRow.hasSpannedCells()) { + final List> rows = new ArrayList>(); + rows.addAll(header.getRows()); + rows.addAll(footer.getRows()); + for (StaticRow row : rows) { + if (!row.hasSpannedCells()) { continue; } for (int c = frozenColumns; c < getColumnCount(); c++) { - HeaderCell cell = headerRow.getCell(getColumn(c)); - assert cell != null : "Somehow got a null cell for row:cell " - + r + ":" + c; + StaticCell cell = row.getCell(getColumn(c)); int colspan = cell.getColspan(); if (colspan <= 1) { continue; diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderTest.java index 39c6082bed..a7fc3f435d 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderTest.java @@ -236,7 +236,7 @@ public class GridColumnReorderTest extends GridBasicClientFeaturesTest { } @Test - public void testColumnReorder_cellInsideASpannedHeader_cantBeDroppedOutsideSpannedArea() { + public void testColumnReorder_cellInsideSpannedHeader_cantBeDroppedOutsideSpannedArea() { // given toggleColumnReorder(); selectMenuPath("Component", "Header", "Append row"); @@ -353,6 +353,79 @@ public class GridColumnReorderTest extends GridBasicClientFeaturesTest { assertColumnHeaderOrder(1, 3, 4, 5, 2); } + @Test + public void testColumnReorder_footerHasSpannedCells_cantDropInside() { + // given + toggleColumnReorder(); + selectMenuPath("Component", "Footer", "Append row"); + selectMenuPath("Component", "Footer", "Row 1", "Join columns 1, 2"); + assertColumnHeaderOrder(0, 1, 2, 3, 4); + + // when + dragAndDropColumnHeader(0, 3, 1, 80); + + // then + assertColumnHeaderOrder(0, 3, 1, 2, 4); + } + + @Test + public void testColumnReorder_cellInsideASpannedFooter_cantBeDroppedOutsideSpannedArea() { + // given + toggleColumnReorder(); + selectMenuPath("Component", "Footer", "Append row"); + selectMenuPath("Component", "Footer", "Row 1", "Join columns 1, 2"); + assertColumnHeaderOrder(0, 1, 2, 3, 4); + + // when + dragAndDropColumnHeader(0, 2, 0, 20); + + // then + assertColumnHeaderOrder(0, 2, 1, 3, 4); + } + + @Test + public void testColumnReorder_cellInsideTwoCrossingSpanningFooters_cantTouchThis() { + // given + toggleColumnReorder(); + selectMenuPath("Component", "Footer", "Append row"); + selectMenuPath("Component", "Footer", "Append row"); + selectMenuPath("Component", "Footer", "Row 1", "Join column cells 0, 1"); + selectMenuPath("Component", "Footer", "Row 2", "Join columns 1, 2"); + dragAndDropColumnHeader(0, 3, 0, 10); + assertColumnHeaderOrder(3, 0, 1, 2, 4); + + // when + dragAndDropColumnHeader(0, 2, 0, 10); + + // then + assertColumnHeaderOrder(3, 0, 1, 2, 4); + } + + @Test + public void testColumnReorder_cellsInsideTwoAdjacentSpannedHeaderAndFooter_reorderingLimited() { + // given + toggleColumnReorder(); + selectMenuPath("Component", "Header", "Append row"); + selectMenuPath("Component", "Footer", "Append row"); + selectMenuPath("Component", "Header", "Row 2", "Join columns 3, 4, 5"); + dragAndDropColumnHeader(0, 0, 4, 80); + scrollGridHorizontallyTo(0); + dragAndDropColumnHeader(0, 1, 4, 80); + scrollGridHorizontallyTo(0); + selectMenuPath("Component", "Footer", "Row 1", "Join columns 1, 2"); + assertColumnHeaderOrder(1, 3, 4, 5, 2); + + // when then + dragAndDropColumnHeader(0, 1, 3, 80); + assertColumnHeaderOrder(1, 4, 3, 5, 2); + + dragAndDropColumnHeader(0, 2, 4, 10); + assertColumnHeaderOrder(1, 4, 3, 5, 2); + + dragAndDropColumnHeader(0, 2, 0, 10); + assertColumnHeaderOrder(1, 3, 4, 5, 2); + } + private void toggleColumnReorder() { selectMenuPath("Component", "State", "Column Reordering"); } -- cgit v1.2.3 From d6dbed15d626d0ae22cc711cfb08514be1c9a0db Mon Sep 17 00:00:00 2001 From: Henrik Paul Date: Wed, 4 Mar 2015 12:42:38 +0200 Subject: Fixes a bug with escalator.scrollToRow and spacers (#16644) Change-Id: I9e148cf81d393cc489c20bf0265f4a3bc6fed069 --- .../src/com/vaadin/client/widgets/Escalator.java | 6 ++--- server/src/com/vaadin/ui/Grid.java | 4 ++-- .../EscalatorBasicClientFeaturesTest.java | 26 +++++++++++++++------- .../escalator/EscalatorSpacerTest.java | 18 +++++++++++++++ .../grid/EscalatorBasicClientFeaturesWidget.java | 14 ++++++++++++ 5 files changed, 55 insertions(+), 13 deletions(-) (limited to 'client/src') diff --git a/client/src/com/vaadin/client/widgets/Escalator.java b/client/src/com/vaadin/client/widgets/Escalator.java index efa777111a..679b452fde 100644 --- a/client/src/com/vaadin/client/widgets/Escalator.java +++ b/client/src/com/vaadin/client/widgets/Escalator.java @@ -1150,9 +1150,9 @@ public class Escalator extends Widget implements RequiresResize, public void scrollToRow(final int rowIndex, final ScrollDestination destination, final double padding) { - getLogger().warning("[[spacers]] scrollToRow"); - - final double targetStartPx = body.getDefaultRowHeight() * rowIndex; + final double targetStartPx = (body.getDefaultRowHeight() * rowIndex) + + body.spacerContainer + .getSpacerHeightsSumUntilIndex(rowIndex); final double targetEndPx = targetStartPx + body.getDefaultRowHeight(); diff --git a/server/src/com/vaadin/ui/Grid.java b/server/src/com/vaadin/ui/Grid.java index 6ab6c6b1a4..cf0e54156a 100644 --- a/server/src/com/vaadin/ui/Grid.java +++ b/server/src/com/vaadin/ui/Grid.java @@ -51,7 +51,6 @@ import com.vaadin.data.RpcDataProviderExtension.DataProviderKeyMapper; import com.vaadin.data.Validator.InvalidValueException; import com.vaadin.data.fieldgroup.DefaultFieldGroupFieldFactory; import com.vaadin.data.fieldgroup.FieldGroup; -import com.vaadin.data.fieldgroup.FieldGroup.BindException; import com.vaadin.data.fieldgroup.FieldGroup.CommitException; import com.vaadin.data.fieldgroup.FieldGroupFieldFactory; import com.vaadin.data.sort.Sort; @@ -2647,7 +2646,8 @@ public class Grid extends AbstractComponent implements SelectionNotifier, * Getting a field before the editor has been opened depends on special * support from the {@link FieldGroup} in use. Using this method with a * user-provided FieldGroup might cause - * {@link BindException} to be thrown. + * {@link com.vaadin.data.fieldgroup.FieldGroup.BindException + * BindException} to be thrown. * * @return the bound field; or null if the respective * column is not editable 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 61c75b6162..04c0933866 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/EscalatorBasicClientFeaturesTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/EscalatorBasicClientFeaturesTest.java @@ -33,6 +33,10 @@ import com.vaadin.tests.tb3.MultiBrowserTest; @TestCategory("escalator") public abstract class EscalatorBasicClientFeaturesTest extends MultiBrowserTest { + + private static final String LOGICAL_ROW_ATTRIBUTE_NAME = "vLogicalRow"; + private static final String SPACER_CSS_CLASS = "v-escalator-spacer"; + protected static final String COLUMNS_AND_ROWS = "Columns and Rows"; protected static final String COLUMNS = "Columns"; @@ -52,6 +56,8 @@ public abstract class EscalatorBasicClientFeaturesTest extends MultiBrowserTest protected static final String BODY_ROWS = "Body Rows"; protected static final String FOOTER_ROWS = "Footer Rows"; + protected static final String SCROLL_TO = "Scroll to..."; + protected static final String REMOVE_ALL_INSERT_SCROLL = "Remove all, insert 30 and scroll 40px"; protected static final String GENERAL = "General"; @@ -69,12 +75,15 @@ 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 SET_100PX = "Set 100px"; protected static final String SPACERS = "Spacers"; + protected static final String REMOVE = "Remove"; + protected static final String ROW_MINUS1 = "Row -1"; protected static final String ROW_1 = "Row 1"; + protected static final String ROW_25 = "Row 25"; + protected static final String ROW_75 = "Row 75"; protected static final String ROW_99 = "Row 99"; - protected static final String SET_100PX = "Set 100px"; - protected static final String REMOVE = "Remove"; @Override protected Class getUIClass() { @@ -173,15 +182,16 @@ public abstract class EscalatorBasicClientFeaturesTest extends MultiBrowserTest private TestBenchElement getRow(String sectionTag, int row) { TestBenchElement escalator = getEscalator(); WebElement tableSection = escalator.findElement(By.tagName(sectionTag)); - By xpath; + String xpathExpression = "tr[not(@class='" + SPACER_CSS_CLASS + "')]"; if (row >= 0) { int fromFirst = row + 1; - xpath = By.xpath("tr[" + fromFirst + "]"); + xpathExpression += "[" + fromFirst + "]"; } else { int fromLast = Math.abs(row + 1); - xpath = By.xpath("tr[last() - " + fromLast + "]"); + xpathExpression += "[last() - " + fromLast + "]"; } + By xpath = By.xpath(xpathExpression); if (tableSection != null && ((TestBenchElement) tableSection).isElementPresent(xpath)) { return (TestBenchElement) tableSection.findElement(xpath); @@ -281,7 +291,7 @@ public abstract class EscalatorBasicClientFeaturesTest extends MultiBrowserTest } private List getSpacers() { - return getEscalator().findElements(By.className("v-escalator-spacer")); + return getEscalator().findElements(By.className(SPACER_CSS_CLASS)); } protected boolean spacersAreFoundInDom() { @@ -295,8 +305,8 @@ public abstract class EscalatorBasicClientFeaturesTest extends MultiBrowserTest 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]", + Boolean isInDom = (Boolean) executeScript("return arguments[0]['" + + LOGICAL_ROW_ATTRIBUTE_NAME + "'] === arguments[1]", spacer, logicalRowIndex); if (isInDom) { return spacer; 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 index da3472aebf..c3468b373e 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/escalator/EscalatorSpacerTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/escalator/EscalatorSpacerTest.java @@ -267,6 +267,24 @@ public class EscalatorSpacerTest extends EscalatorBasicClientFeaturesTest { + "-1 spacer", 0, getScrollTop()); } + @Test + public void scrollToRowWorksProperlyWithSpacers() throws Exception { + selectMenuPath(FEATURES, SPACERS, ROW_MINUS1, SET_100PX); + selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX); + + /* + * we check for row -2 instead of -1, because escalator has the one row + * buffered underneath the footer + */ + selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, SCROLL_TO, ROW_75); + Thread.sleep(500); + assertEquals("Row 75: 0,75", getBodyCell(-2, 0).getText()); + + selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, SCROLL_TO, ROW_25); + Thread.sleep(500); + assertEquals("Row 25: 0,25", getBodyCell(0, 0).getText()); + } + private static double[] getElementDimensions(WebElement element) { /* * we need to parse the style attribute, since using getCssValue gets a 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 dc86b89167..0d4aa305d9 100644 --- a/uitest/src/com/vaadin/tests/widgetset/client/grid/EscalatorBasicClientFeaturesWidget.java +++ b/uitest/src/com/vaadin/tests/widgetset/client/grid/EscalatorBasicClientFeaturesWidget.java @@ -16,6 +16,7 @@ import com.vaadin.client.widget.escalator.RowContainer.BodyRowContainer; import com.vaadin.client.widget.escalator.Spacer; import com.vaadin.client.widget.escalator.SpacerUpdater; import com.vaadin.client.widgets.Escalator; +import com.vaadin.shared.ui.grid.ScrollDestination; public class EscalatorBasicClientFeaturesWidget extends PureGWTTestApplication { @@ -571,6 +572,19 @@ public class EscalatorBasicClientFeaturesWidget extends escalator.setScrollTop(40); } }, menupath); + + String[] scrollToRowMenuPath = new String[menupath.length + 1]; + System.arraycopy(menupath, 0, scrollToRowMenuPath, 0, menupath.length); + scrollToRowMenuPath[scrollToRowMenuPath.length - 1] = "Scroll to..."; + for (int i = 0; i < 100; i += 25) { + final int rowIndex = i; + addMenuCommand("Row " + i, new ScheduledCommand() { + @Override + public void execute() { + escalator.scrollToRow(rowIndex, ScrollDestination.ANY, 0); + } + }, scrollToRowMenuPath); + } } private void createRowsMenu(final RowContainer container, String[] menupath) { -- cgit v1.2.3 From 3c84a54591d792b69a4d6d736714941edb1d818a Mon Sep 17 00:00:00 2001 From: Pekka Hyvönen Date: Thu, 5 Mar 2015 11:10:06 +0200 Subject: Dnd-reorder of spanned cells in grid header. (#16643) Change-Id: I6f2decbbfb393922c4852f5b05ac0d10589ee512 --- client/src/com/vaadin/client/widgets/Grid.java | 101 +++++++++---- .../grid/basicfeatures/GridColumnReorderTest.java | 168 +++++++++++++++++++++ 2 files changed, 238 insertions(+), 31 deletions(-) (limited to 'client/src') diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index 432228be1e..07c888e936 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -3064,20 +3064,32 @@ public class Grid extends ResizeComposite implements @Override public void onDrop() { final int draggedColumnIndex = eventCell.getColumnIndex(); + final int colspan = header.getRow(eventCell.getRowIndex()) + .getCell(eventCell.getColumn()).getColspan(); if (latestColumnDropIndex != draggedColumnIndex - && latestColumnDropIndex != (draggedColumnIndex + 1)) { + && latestColumnDropIndex != (draggedColumnIndex + colspan)) { List> columns = getColumns(); - List> reordered = new ArrayList>( - columns); - Column moved = reordered.remove(draggedColumnIndex); + List> reordered = new ArrayList>(); if (draggedColumnIndex < latestColumnDropIndex) { - latestColumnDropIndex--; + reordered.addAll(columns.subList(0, draggedColumnIndex)); + reordered.addAll(columns.subList(draggedColumnIndex + + colspan, latestColumnDropIndex)); + reordered.addAll(columns.subList(draggedColumnIndex, + draggedColumnIndex + colspan)); + reordered.addAll(columns.subList(latestColumnDropIndex, + columns.size())); + } else { + reordered.addAll(columns.subList(0, latestColumnDropIndex)); + reordered.addAll(columns.subList(draggedColumnIndex, + draggedColumnIndex + colspan)); + reordered.addAll(columns.subList(latestColumnDropIndex, + draggedColumnIndex)); + reordered.addAll(columns.subList(draggedColumnIndex + + colspan, columns.size())); } - reordered.add(latestColumnDropIndex, moved); reordered.remove(selectionColumn); // since setColumnOrder will // add it anyway! - @SuppressWarnings("unchecked") Column[] array = reordered.toArray(new Column[reordered .size()]); setColumnOrder(array); @@ -3096,13 +3108,15 @@ public class Grid extends ResizeComposite implements .findRowContainer(focusedCell.getElement()); if (focusedCellColumnIndex == draggedColumnIndex) { // move with the dragged column + final int adjustedDropIndex = latestColumnDropIndex > draggedColumnIndex ? latestColumnDropIndex - 1 + : latestColumnDropIndex; cellFocusHandler.setCellFocus(focusedRowIndex, - latestColumnDropIndex, rowContainer); + adjustedDropIndex, rowContainer); } else if (latestColumnDropIndex <= focusedCellColumnIndex && draggedColumnIndex > focusedCellColumnIndex) { cellFocusHandler.setCellFocus(focusedRowIndex, focusedCellColumnIndex + 1, rowContainer); - } else if (latestColumnDropIndex >= focusedCellColumnIndex + } else if (latestColumnDropIndex > focusedCellColumnIndex && draggedColumnIndex < focusedCellColumnIndex) { cellFocusHandler.setCellFocus(focusedRowIndex, focusedCellColumnIndex - 1, rowContainer); @@ -3171,12 +3185,17 @@ public class Grid extends ResizeComposite implements private void calculatePossibleDropPositions() { possibleDropPositions.clear(); - final int draggedColumnIndex = eventCell.getColumnIndex(); - HashSet unavailableColumnDropIndices = new HashSet(); - + final int draggedCellIndex = eventCell.getColumnIndex(); + final StaticRow draggedCellRow = header.getRow(eventCell + .getRowIndex()); + final int draggedCellRightIndex = draggedCellIndex + + draggedCellRow.getCell(eventCell.getColumn()) + .getColspan(); final int frozenColumns = getSelectionAndFrozenColumnCount(); + final Range draggedCellRange = Range.between(draggedCellIndex, + draggedCellRightIndex); /* - * If the dragged cell is adjacent to a spanned cell in any other + * If the dragged cell intersects with a spanned cell in any other * header or footer row, then the drag is limited inside that * spanned cell. The same rules apply: the cell can't be dropped * inside another spanned cell. The left and right bounds keep track @@ -3185,6 +3204,7 @@ public class Grid extends ResizeComposite implements int leftBound = -1; int rightBound = getColumnCount() + 1; + final HashSet unavailableColumnDropIndices = new HashSet(); final List> rows = new ArrayList>(); rows.addAll(header.getRows()); rows.addAll(footer.getRows()); @@ -3192,30 +3212,52 @@ public class Grid extends ResizeComposite implements if (!row.hasSpannedCells()) { continue; } - for (int c = frozenColumns; c < getColumnCount(); c++) { - StaticCell cell = row.getCell(getColumn(c)); + final boolean isDraggedCellRow = row.equals(draggedCellRow); + for (int cellColumnIndex = frozenColumns; cellColumnIndex < getColumnCount(); cellColumnIndex++) { + StaticCell cell = row.getCell(getColumn(cellColumnIndex)); int colspan = cell.getColspan(); if (colspan <= 1) { continue; } - final int spannedCellRightEdgeIndex = c + colspan; - if (c <= draggedColumnIndex - && spannedCellRightEdgeIndex > draggedColumnIndex) { - // the spanned cell overlaps the dragged cell - if (c <= draggedColumnIndex && c > leftBound) { - leftBound = c; + final int cellColumnRightIndex = cellColumnIndex + colspan; + final Range cellRange = Range.between(cellColumnIndex, + cellColumnRightIndex); + final boolean intersects = draggedCellRange + .intersects(cellRange); + if (intersects && !isDraggedCellRow) { + // if the currently iterated cell is inside or same as + // the dragged cell, then it doesn't restrict the drag + if (cellRange.isSubsetOf(draggedCellRange)) { + cellColumnIndex = cellColumnRightIndex - 1; + continue; + } + /* + * if the dragged cell is a spanned cell and it crosses + * with the currently iterated cell without sharing + * either start or end then not possible to drag the + * cell. + */ + if (!draggedCellRange.isSubsetOf(cellRange)) { + return; + } + // the spanned cell overlaps the dragged cell (but is + // not the dragged cell) + if (cellColumnIndex <= draggedCellIndex + && cellColumnIndex > leftBound) { + leftBound = cellColumnIndex; } - if (spannedCellRightEdgeIndex < rightBound) { - rightBound = spannedCellRightEdgeIndex; + if (cellColumnRightIndex < rightBound) { + rightBound = cellColumnRightIndex; } - c = spannedCellRightEdgeIndex - 1; + cellColumnIndex = cellColumnRightIndex - 1; } - else { // can't drop inside a spanned cell + else { // can't drop inside a spanned cell, or this is the + // dragged cell while (colspan > 1) { - c++; + cellColumnIndex++; colspan--; - unavailableColumnDropIndices.add(c); + unavailableColumnDropIndices.add(cellColumnIndex); } } } @@ -3244,6 +3286,7 @@ public class Grid extends ResizeComposite implements possibleDropPositions.put(position, getColumnCount()); } } + }; /** @@ -5537,10 +5580,6 @@ public class Grid extends ResizeComposite implements .getFrozenColumnCount()) { return false; } - // for now prevent dragging of spanned cells - if (eventCell.getElement().getColSpan() > 1) { - return false; - } if (event.getTypeInt() == Event.ONMOUSEDOWN && event.getButton() == NativeEvent.BUTTON_LEFT diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderTest.java index a7fc3f435d..d4892fb52a 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderTest.java @@ -426,6 +426,174 @@ public class GridColumnReorderTest extends GridBasicClientFeaturesTest { assertColumnHeaderOrder(1, 3, 4, 5, 2); } + @Test + public void testColumnReorder_draggingASpannedCell_dragWorksNormally() { + // given + toggleColumnReorder(); + selectMenuPath("Component", "Header", "Append row"); + selectMenuPath("Component", "Header", "Row 2", "Join columns 1, 2"); + assertColumnHeaderOrder(0, 1, 2, 3, 4); + + // when + dragAndDropColumnHeader(1, 1, 4, 10); + scrollGridHorizontallyTo(0); + + // then + assertColumnHeaderOrder(0, 3, 1, 2, 4); + } + + @Test + public void testColumnReorder_twoEqualSpannedCells_bothCanBeDragged() { + // given + toggleColumnReorder(); + selectMenuPath("Component", "Header", "Append row"); + selectMenuPath("Component", "Header", "Row 2", "Join columns 1, 2"); + selectMenuPath("Component", "Header", "Append row"); + selectMenuPath("Component", "Header", "Row 3", "Join columns 1, 2"); + assertColumnHeaderOrder(0, 1, 2, 3, 4); + + // when + dragAndDropColumnHeader(1, 1, 4, 10); + scrollGridHorizontallyTo(0); + + // then + assertColumnHeaderOrder(0, 3, 1, 2, 4); + + // when + dragAndDropColumnHeader(2, 3, 0, 10); + + // then + assertColumnHeaderOrder(1, 2, 0, 3, 4); + } + + @Test + public void testColumReorder_twoCrossingSpanningHeaders_neitherCanBeDragged() { + // given + toggleColumnReorder(); + selectMenuPath("Component", "Header", "Append row"); + selectMenuPath("Component", "Header", "Row 2", "Join columns 1, 2"); + selectMenuPath("Component", "Header", "Append row"); + selectMenuPath("Component", "Header", "Row 3", "Join column cells 0, 1"); + assertColumnHeaderOrder(0, 1, 2, 3, 4); + + // when + dragAndDropColumnHeader(1, 1, 4, 10); + + // then + assertColumnHeaderOrder(0, 1, 2, 3, 4); + + // when + dragAndDropColumnHeader(2, 0, 3, 100); + + // then + assertColumnHeaderOrder(0, 1, 2, 3, 4); + } + + @Test + public void testColumnReorder_spannedCellHasAnotherSpannedCellInside_canBeDraggedNormally() { + // given + toggleColumnReorder(); + selectMenuPath("Component", "Header", "Append row"); + selectMenuPath("Component", "Header", "Row 2", "Join columns 3, 4, 5"); + dragAndDropColumnHeader(1, 3, 1, 10); + scrollGridHorizontallyTo(0); + selectMenuPath("Component", "Header", "Append row"); + selectMenuPath("Component", "Header", "Row 3", "Join columns 1, 2"); + assertColumnHeaderOrder(0, 3, 4, 5, 1); + + // when + dragAndDropColumnHeader(1, 1, 0, 10); + + // then + assertColumnHeaderOrder(3, 4, 5, 0, 1); + } + + @Test + public void testColumnReorder_spannedCellInsideAnotherSpanned_canBeDraggedWithBoundaries() { + // given + toggleColumnReorder(); + selectMenuPath("Component", "Header", "Append row"); + selectMenuPath("Component", "Header", "Row 2", "Join columns 3, 4, 5"); + dragAndDropColumnHeader(1, 3, 1, 10); + scrollGridHorizontallyTo(0); + selectMenuPath("Component", "Header", "Append row"); + selectMenuPath("Component", "Header", "Row 3", "Join columns 1, 2"); + assertColumnHeaderOrder(0, 3, 4, 5, 1); + + // when + dragAndDropColumnHeader(2, 1, 3, 100); + scrollGridHorizontallyTo(0); + + // then + assertColumnHeaderOrder(0, 5, 3, 4, 1); + + // when + dragAndDropColumnHeader(2, 2, 0, 10); + scrollGridHorizontallyTo(0); + + // then + assertColumnHeaderOrder(0, 3, 4, 5, 1); + } + + @Test + public void testColumnReorder_cellInsideAndNextToSpannedCells_canBeDraggedWithBoundaries() { + // given + toggleColumnReorder(); + selectMenuPath("Component", "Header", "Append row"); + selectMenuPath("Component", "Header", "Row 2", "Join columns 3, 4, 5"); + dragAndDropColumnHeader(1, 3, 1, 10); + scrollGridHorizontallyTo(0); + selectMenuPath("Component", "Header", "Append row"); + selectMenuPath("Component", "Header", "Row 3", "Join columns 1, 2"); + assertColumnHeaderOrder(0, 3, 4, 5, 1); + + // when + dragAndDropColumnHeader(2, 3, 0, 10); + scrollGridHorizontallyTo(0); + + // then + assertColumnHeaderOrder(0, 5, 3, 4, 1); + + // when + dragAndDropColumnHeader(2, 1, 4, 10); + scrollGridHorizontallyTo(0); + + // then + assertColumnHeaderOrder(0, 3, 4, 5, 1); + } + + @Test + public void testColumnReorder_multipleSpannedCells_dragWorksNormally() { + toggleColumnReorder(); + selectMenuPath("Component", "Header", "Append row"); + selectMenuPath("Component", "Header", "Row 2", "Join columns 3, 4, 5"); + selectMenuPath("Component", "Header", "Append row"); + selectMenuPath("Component", "Header", "Row 3", "Join columns 1, 2"); + assertColumnHeaderOrder(0, 1, 2, 3, 4); + + // when + dragAndDropColumnHeader(1, 3, 2, -10); + scrollGridHorizontallyTo(0); + + // then + assertColumnHeaderOrder(0, 3, 4, 5, 1); + + // when + scrollGridHorizontallyTo(100); + dragAndDropColumnHeader(2, 4, 2, 10); + scrollGridHorizontallyTo(0); + + // then + assertColumnHeaderOrder(0, 1, 2, 3, 4); + + // when + dragAndDropColumnHeader(0, 0, 3, 10); + scrollGridHorizontallyTo(0); + + // then + assertColumnHeaderOrder(1, 2, 0, 3, 4); + } + private void toggleColumnReorder() { selectMenuPath("Component", "State", "Column Reordering"); } -- cgit v1.2.3 From f31a927acb4168497f6f25ab68120dd9fa03e636 Mon Sep 17 00:00:00 2001 From: Henrik Paul Date: Thu, 5 Mar 2015 11:09:26 +0200 Subject: Double-opening Grid details row doesn't change height (#16644) The bug caused an arbitrary height to be set for the details row if an already-open details was reopened. Change-Id: I1d764511d2829416491eb92abf8565288252f953 --- client/src/com/vaadin/client/widgets/Grid.java | 58 ++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 4 deletions(-) (limited to 'client/src') diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index 2725f84fe6..aa768f9f21 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -1782,6 +1782,12 @@ public class Grid extends ResizeComposite implements private static final String CUSTOM_STYLE_PROPERTY_NAME = "customStyle"; + /** + * An initial height that is given to new details rows before rendering the + * appropriate widget that we then can be measure + * + * @see GridSpacerUpdater + */ private static final double DETAILS_ROW_INITIAL_HEIGHT = 50; private EventCellReference eventCell = new EventCellReference(this); @@ -2929,8 +2935,9 @@ public class Grid extends ResizeComposite implements private boolean enabled = true; private DetailsGenerator detailsGenerator = DetailsGenerator.NULL; - private GridSpacerUpdater gridSpacerUpdater = new GridSpacerUpdater(); + /** A set keeping track of the indices of all currently open details */ + private Set visibleDetails = new HashSet(); /** * Enumeration for easy setting of selection mode. @@ -6341,17 +6348,60 @@ public class Grid extends ResizeComposite implements /** * Shows or hides the details for a specific row. + *

+ * This method does nothing if trying to set show already-visible details, + * or hide already-hidden details. * * @since - * @param row + * @param rowIndex * the index of the affected row * @param visible * true to show the details, or false * to hide them + * @see #isDetailsVisible(int) */ public void setDetailsVisible(int rowIndex, boolean visible) { - double height = visible ? DETAILS_ROW_INITIAL_HEIGHT : -1; - escalator.getBody().setSpacer(rowIndex, height); + Integer rowIndexInteger = Integer.valueOf(rowIndex); + + /* + * We want to prevent opening a details row twice, so any subsequent + * openings (or closings) of details is a NOOP. + * + * When a details row is opened, it is given an arbitrary height + * (because Escalator requires a height upon opening). Only when it's + * opened, Escalator will ask the generator to generate a widget, which + * we then can measure. When measured, we correct the initial height by + * the original height. + * + * Without this check, we would override the measured height, and revert + * back to the initial, arbitrary, height which would most probably be + * wrong. + * + * see GridSpacerUpdater.init for implementation details. + */ + + if (visible && !isDetailsVisible(rowIndex)) { + escalator.getBody().setSpacer(rowIndex, DETAILS_ROW_INITIAL_HEIGHT); + visibleDetails.add(rowIndexInteger); + } + + else if (!visible && isDetailsVisible(rowIndex)) { + escalator.getBody().setSpacer(rowIndex, -1); + visibleDetails.remove(rowIndexInteger); + } + } + + /** + * Check whether the details for a row is visible or not. + * + * @since + * @param rowIndex + * the index of the row for which to check details + * @return true iff the details for the given row is visible + * @see #setDetailsVisible(int, boolean) + */ + public boolean isDetailsVisible(int rowIndex) { + return visibleDetails.contains(Integer.valueOf(rowIndex)); } /** -- cgit v1.2.3 From f091f9ff68e1d3b1c06c8e78b33cd07ccf480f9f Mon Sep 17 00:00:00 2001 From: Pekka Hyvönen Date: Thu, 12 Mar 2015 11:19:43 +0200 Subject: Hide columns with client side API in Grid (#17023) For now, the width for spanned header/footer cells is broken after hiding their columns. Change-Id: I0833fb570fcad731776a88a183b3df45cbc19557 --- client/src/com/vaadin/client/widgets/Grid.java | 131 +++++++++++++++------ .../grid/basicfeatures/GridColumnHidingTest.java | 88 ++++++++++++++ .../client/grid/GridBasicClientFeaturesWidget.java | 6 + 3 files changed, 192 insertions(+), 33 deletions(-) create mode 100644 uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnHidingTest.java (limited to 'client/src') diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index 7b336ff5ee..c7ec9cd062 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -2477,7 +2477,7 @@ public class Grid extends ResizeComposite implements private boolean columnsAreGuaranteedToBeWiderThanGrid() { double freeSpace = escalator.getInnerWidth(); - for (Column column : getColumns()) { + for (Column column : getVisibleColumns()) { if (column.getWidth() >= 0) { freeSpace -= column.getWidth(); } else if (column.getMinimumWidth() >= 0) { @@ -2493,7 +2493,7 @@ public class Grid extends ResizeComposite implements /* Step 1: Apply all column widths as they are. */ Map selfWidths = new LinkedHashMap(); - List> columns = getColumns(); + List> columns = getVisibleColumns(); for (int index = 0; index < columns.size(); index++) { selfWidths.put(index, columns.get(index).getWidth()); } @@ -2534,6 +2534,7 @@ public class Grid extends ResizeComposite implements final Set> columnsToExpand = new HashSet>(); List> nonFixedColumns = new ArrayList>(); Map columnSizes = new HashMap(); + final List> visibleColumns = getVisibleColumns(); /* * Set all fixed widths and also calculate the size-to-fit widths @@ -2542,7 +2543,7 @@ public class Grid extends ResizeComposite implements * This way we know with how many pixels we have left to expand the * rest. */ - for (Column column : getColumns()) { + for (Column column : visibleColumns) { final double widthAsIs = column.getWidth(); final boolean isFixedWidth = widthAsIs >= 0; final double widthFixed = Math.max(widthAsIs, @@ -2551,11 +2552,11 @@ public class Grid extends ResizeComposite implements && column.getExpandRatio() == -1; if (isFixedWidth) { - columnSizes.put(indexOfColumn(column), widthFixed); + columnSizes.put(visibleColumns.indexOf(column), widthFixed); reservedPixels += widthFixed; } else { nonFixedColumns.add(column); - columnSizes.put(indexOfColumn(column), -1.0d); + columnSizes.put(visibleColumns.indexOf(column), -1.0d); } } @@ -2572,7 +2573,7 @@ public class Grid extends ResizeComposite implements columnsToExpand.add(column); } reservedPixels += newWidth; - columnSizes.put(indexOfColumn(column), newWidth); + columnSizes.put(visibleColumns.indexOf(column), newWidth); } /* @@ -2600,8 +2601,8 @@ public class Grid extends ResizeComposite implements final Column column = i.next(); final int expandRatio = getExpandRatio(column, defaultExpandRatios); - final double autoWidth = columnSizes - .get(indexOfColumn(column)); + final int columnIndex = visibleColumns.indexOf(column); + final double autoWidth = columnSizes.get(columnIndex); final double maxWidth = getMaxWidth(column); double expandedWidth = autoWidth + widthPerRatio * expandRatio; @@ -2611,7 +2612,7 @@ public class Grid extends ResizeComposite implements totalRatios -= expandRatio; aColumnHasMaxedOut = true; pixelsToDistribute -= maxWidth - autoWidth; - columnSizes.put(indexOfColumn(column), maxWidth); + columnSizes.put(columnIndex, maxWidth); } } } while (aColumnHasMaxedOut); @@ -2647,13 +2648,14 @@ public class Grid extends ResizeComposite implements for (Column column : columnsToExpand) { final int expandRatio = getExpandRatio(column, defaultExpandRatios); - final double autoWidth = columnSizes.get(indexOfColumn(column)); + final int columnIndex = visibleColumns.indexOf(column); + final double autoWidth = columnSizes.get(columnIndex); double totalWidth = autoWidth + widthPerRatio * expandRatio; if (leftOver > 0) { totalWidth += 1; leftOver--; } - columnSizes.put(indexOfColumn(column), totalWidth); + columnSizes.put(columnIndex, totalWidth); totalRatios -= expandRatio; } @@ -2674,7 +2676,7 @@ public class Grid extends ResizeComposite implements * remove those pixels from other columns */ double pixelsToRemoveFromOtherColumns = 0; - for (Column column : getColumns()) { + for (Column column : visibleColumns) { /* * We can't iterate over columnsToExpand, even though that * would be convenient. This is because some column without @@ -2683,11 +2685,11 @@ public class Grid extends ResizeComposite implements */ double minWidth = getMinWidth(column); - double currentWidth = columnSizes - .get(indexOfColumn(column)); + final int columnIndex = visibleColumns.indexOf(column); + double currentWidth = columnSizes.get(columnIndex); boolean hasAutoWidth = column.getWidth() < 0; if (hasAutoWidth && currentWidth < minWidth) { - columnSizes.put(indexOfColumn(column), minWidth); + columnSizes.put(columnIndex, minWidth); pixelsToRemoveFromOtherColumns += (minWidth - currentWidth); minWidthsCausedReflows = true; @@ -2713,7 +2715,7 @@ public class Grid extends ResizeComposite implements for (Column column : columnsToExpand) { final double pixelsToRemove = pixelsToRemovePerRatio * getExpandRatio(column, defaultExpandRatios); - int colIndex = indexOfColumn(column); + int colIndex = visibleColumns.indexOf(column); columnSizes.put(colIndex, columnSizes.get(colIndex) - pixelsToRemove); } @@ -3379,6 +3381,8 @@ public class Grid extends ResizeComposite implements private boolean editable = true; + private boolean hidden = false; + private String headerCaption = ""; private double minimumWidthPx = GridConstants.DEFAULT_MIN_WIDTH; @@ -3549,6 +3553,9 @@ public class Grid extends ResizeComposite implements * This action is done "finally", once the current execution loop * returns. This is done to reduce overhead of unintentionally always * recalculate all columns, when modifying several columns at once. + *

+ * If the column is currently {@link #isHidden() hidden}, then this set + * width has effect only once the column has been made visible again. * * @param pixels * the width in pixels or negative for auto sizing @@ -3556,14 +3563,17 @@ public class Grid extends ResizeComposite implements public Column setWidth(double pixels) { if (!WidgetUtil.pixelValuesEqual(widthUser, pixels)) { widthUser = pixels; - scheduleColumnWidthRecalculator(); + if (!isHidden()) { + scheduleColumnWidthRecalculator(); + } } return this; } void doSetWidth(double pixels) { + assert !isHidden() : "applying width for a hidden column"; if (grid != null) { - int index = grid.columns.indexOf(this); + int index = grid.getVisibleColumns().indexOf(this); ColumnConfiguration conf = grid.escalator .getColumnConfiguration(); conf.setColumnWidth(index, pixels); @@ -3575,6 +3585,9 @@ public class Grid extends ResizeComposite implements *

* Note: If a negative value was given to * {@link #setWidth(double)}, that same negative value is returned here. + *

+ * Note: Returns the value, even if the column is currently + * {@link #isHidden() hidden}. * * @return pixel width of the column, or a negative number if the column * width has been automatically calculated. @@ -3589,13 +3602,18 @@ public class Grid extends ResizeComposite implements * Returns the effective pixel width of the column. *

* This differs from {@link #getWidth()} only when the column has been - * automatically resized. + * automatically resized, or when the column is currently + * {@link #isHidden() hidden}, when the value is 0. * * @return pixel width of the column. */ public double getWidthActual() { + if (isHidden()) { + return 0; + } return grid.escalator.getColumnConfiguration() - .getColumnWidthActual(grid.columns.indexOf(this)); + .getColumnWidthActual( + grid.getVisibleColumns().indexOf(this)); } void reapplyWidth() { @@ -3632,6 +3650,41 @@ public class Grid extends ResizeComposite implements return sortable; } + /** + * Hides or shows the column. By default columns are visible before + * explicitly hiding them. + * + * @since + * @param hidden + * true to hide the column, false + * to show + */ + public void setHidden(boolean hidden) { + if (this.hidden != hidden) { + if (hidden) { + grid.escalator.getColumnConfiguration().removeColumns( + grid.getVisibleColumns().indexOf(this), 1); + this.hidden = hidden; + } else { + this.hidden = hidden; + grid.escalator.getColumnConfiguration().insertColumns( + grid.getVisibleColumns().indexOf(this), 1); + } + scheduleColumnWidthRecalculator(); + } + } + + /** + * Is this column hidden. Default is {@code false}. + * + * @since + * @return true if the column is currently hidden, + * false otherwise + */ + public boolean isHidden() { + return hidden; + } + @Override public String toString() { String details = ""; @@ -4577,7 +4630,8 @@ public class Grid extends ResizeComposite implements int columnIndex = columns.indexOf(column); // Remove from column configuration - escalator.getColumnConfiguration().removeColumns(columnIndex, 1); + escalator.getColumnConfiguration().removeColumns( + getVisibleColumns().indexOf(column), 1); updateFrozenColumns(); @@ -4592,6 +4646,8 @@ public class Grid extends ResizeComposite implements /** * Returns the amount of columns in the grid. + *

+ * NOTE: this includes the hidden columns in the count. * * @return The number of columns in the grid */ @@ -4600,7 +4656,9 @@ public class Grid extends ResizeComposite implements } /** - * Returns a list of columns in the grid. + * Returns a list columns in the grid, including hidden columns. + *

+ * For currently visible columns, use {@link #getVisibleColumns()}. * * @return A unmodifiable list of the columns in the grid */ @@ -4609,6 +4667,24 @@ public class Grid extends ResizeComposite implements .unmodifiableList(new ArrayList>(columns)); } + /** + * Returns a list of the currently visible columns in the grid. + *

+ * No {@link Column#isHidden() hidden} columns included. + * + * @since + * @return A unmodifiable list of the currently visible columns in the grid + */ + public List> getVisibleColumns() { + ArrayList> visible = new ArrayList>(); + for (Column c : columns) { + if (!c.isHidden()) { + visible.add(c); + } + } + return Collections.unmodifiableList(visible); + } + /** * Returns a column by its index in the grid. * @@ -4625,17 +4701,6 @@ public class Grid extends ResizeComposite implements return columns.get(index); } - /** - * Returns current index of given column - * - * @param column - * column in grid - * @return column index, or -1 if not in this Grid - */ - protected int indexOfColumn(Column column) { - return columns.indexOf(column); - } - /** * Returns the header section of this grid. The default header contains a * single row displaying the column captions. diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnHidingTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnHidingTest.java new file mode 100644 index 0000000000..b4593dac28 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnHidingTest.java @@ -0,0 +1,88 @@ +/* + * 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 org.junit.Before; +import org.junit.Test; + +import com.vaadin.testbench.parallel.TestCategory; + +@TestCategory("grid") +public class GridColumnHidingTest extends GridBasicClientFeaturesTest { + + @Before + public void before() { + openTestURL(); + } + + @Test + public void testColumnHiding_hidingColumnsFromAPI_works() { + selectMenuPath("Component", "State", "Width", "1000px"); + assertColumnHeaderOrder(0, 1, 2, 3, 4, 5, 6); + + toggleHideColumn(0); + assertColumnHeaderOrder(1, 2, 3, 4, 5, 6); + + toggleHideColumn(1); + toggleHideColumn(2); + toggleHideColumn(3); + assertColumnHeaderOrder(4, 5, 6, 7, 8); + } + + @Test + public void testColumnHiding_unhidingColumnsFromAPI_works() { + selectMenuPath("Component", "State", "Width", "1000px"); + assertColumnHeaderOrder(0, 1, 2, 3, 4, 5, 6); + + toggleHideColumn(0); + assertColumnHeaderOrder(1, 2, 3, 4, 5, 6); + + toggleHideColumn(0); + assertColumnHeaderOrder(0, 1, 2, 3, 4, 5, 6); + + toggleHideColumn(1); + toggleHideColumn(2); + toggleHideColumn(3); + assertColumnHeaderOrder(0, 4, 5, 6, 7, 8); + + toggleHideColumn(1); + toggleHideColumn(2); + assertColumnHeaderOrder(0, 1, 2, 4, 5, 6); + } + + @Test + public void testColumnHiding_hidingUnhidingFromAPI_works() { + selectMenuPath("Component", "State", "Width", "1000px"); + assertColumnHeaderOrder(0, 1, 2, 3, 4, 5, 6); + + toggleHideColumn(2); + assertColumnHeaderOrder(0, 1, 3, 4, 5, 6); + + toggleHideColumn(2); + assertColumnHeaderOrder(0, 1, 2, 3, 4, 5, 6); + + toggleHideColumn(2); + assertColumnHeaderOrder(0, 1, 3, 4, 5, 6); + + toggleHideColumn(2); + assertColumnHeaderOrder(0, 1, 2, 3, 4, 5, 6); + } + + private void toggleHideColumn(int columnIndex) { + selectMenuPath("Component", "Columns", "Column " + columnIndex, + "Hidden"); + } +} 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 9e77438e36..5ec8058ae9 100644 --- a/uitest/src/com/vaadin/tests/widgetset/client/grid/GridBasicClientFeaturesWidget.java +++ b/uitest/src/com/vaadin/tests/widgetset/client/grid/GridBasicClientFeaturesWidget.java @@ -734,6 +734,12 @@ public class GridBasicClientFeaturesWidget extends column.setSortable(!column.isSortable()); } }, "Component", "Columns", "Column " + i); + addMenuCommand("Hidden", new ScheduledCommand() { + @Override + public void execute() { + column.setHidden(!column.isHidden()); + } + }, "Component", "Columns", "Column " + i); addMenuCommand("auto", new ScheduledCommand() { @Override -- cgit v1.2.3 From 54d6480a3cbd6d721dc119a7f6c5c029b7dcefea Mon Sep 17 00:00:00 2001 From: Pekka Hyvönen Date: Fri, 13 Mar 2015 12:20:30 +0200 Subject: Client side event for grid's columns visibility change (#17023) Change-Id: I1965ca6c298366d89b1940a992788d042cf7a4aa --- .../widget/grid/events/ColumnReorderEvent.java | 3 +- .../grid/events/ColumnVisibilityChangeEvent.java | 93 ++++++++++++++++++++++ .../grid/events/ColumnVisibilityChangeHandler.java | 39 +++++++++ client/src/com/vaadin/client/widgets/Grid.java | 18 +++++ .../grid/basicfeatures/GridColumnHidingTest.java | 46 +++++++++++ .../client/grid/GridBasicClientFeaturesWidget.java | 42 ++++++++++ 6 files changed, 239 insertions(+), 2 deletions(-) create mode 100644 client/src/com/vaadin/client/widget/grid/events/ColumnVisibilityChangeEvent.java create mode 100644 client/src/com/vaadin/client/widget/grid/events/ColumnVisibilityChangeHandler.java (limited to 'client/src') diff --git a/client/src/com/vaadin/client/widget/grid/events/ColumnReorderEvent.java b/client/src/com/vaadin/client/widget/grid/events/ColumnReorderEvent.java index 4890ed063d..c72da0c48e 100644 --- a/client/src/com/vaadin/client/widget/grid/events/ColumnReorderEvent.java +++ b/client/src/com/vaadin/client/widget/grid/events/ColumnReorderEvent.java @@ -18,8 +18,7 @@ package com.vaadin.client.widget.grid.events; import com.google.gwt.event.shared.GwtEvent; /** - * An event for notifying that the columns in the Grid's columns have been - * reordered. + * An event for notifying that the columns in the Grid have been reordered. * * @param * The row type of the grid. The row type is the POJO type from where diff --git a/client/src/com/vaadin/client/widget/grid/events/ColumnVisibilityChangeEvent.java b/client/src/com/vaadin/client/widget/grid/events/ColumnVisibilityChangeEvent.java new file mode 100644 index 0000000000..10bfbfad68 --- /dev/null +++ b/client/src/com/vaadin/client/widget/grid/events/ColumnVisibilityChangeEvent.java @@ -0,0 +1,93 @@ +/* + * 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.events; + +import com.google.gwt.event.shared.GwtEvent; +import com.vaadin.client.widgets.Grid.Column; + +/** + * An event for notifying that the columns in the Grid's have changed + * visibility. + * + * @param + * The row type of the grid. The row type is the POJO type from where + * the data is retrieved into the column cells. + * @since + * @author Vaadin Ltd + */ +public class ColumnVisibilityChangeEvent extends + GwtEvent> { + + private final static Type> TYPE = new Type>(); + + public static final Type> getType() { + return TYPE; + } + + private final Column column; + + private final boolean userOriginated; + + private final boolean hidden; + + public ColumnVisibilityChangeEvent(Column column, boolean hidden, + boolean userOriginated) { + this.column = column; + this.hidden = hidden; + this.userOriginated = userOriginated; + } + + /** + * Returns the column where the visibility change occurred. + * + * @return the column where the visibility change occurred. + */ + public Column getColumn() { + return column; + } + + /** + * Is the column hidden or visible. + * + * @return true if the column was hidden false if + * it was set visible + */ + public boolean isHidden() { + return hidden; + } + + /** + * Is the visibility change triggered by user. + * + * @return true if the change was triggered by user, + * false if not + */ + public boolean isUserOriginated() { + return userOriginated; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public com.google.gwt.event.shared.GwtEvent.Type> getAssociatedType() { + return (Type) TYPE; + } + + @Override + protected void dispatch(ColumnVisibilityChangeHandler handler) { + handler.onVisibilityChange(this); + } + +} diff --git a/client/src/com/vaadin/client/widget/grid/events/ColumnVisibilityChangeHandler.java b/client/src/com/vaadin/client/widget/grid/events/ColumnVisibilityChangeHandler.java new file mode 100644 index 0000000000..10a7660954 --- /dev/null +++ b/client/src/com/vaadin/client/widget/grid/events/ColumnVisibilityChangeHandler.java @@ -0,0 +1,39 @@ +/* + * 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.events; + +import com.google.gwt.event.shared.EventHandler; + +/** + * Handler for a Grid column visibility change event, called when the Grid's + * columns have changed visibility to hidden or visible. + * + * @param The row type of the grid. The row type is the POJO type from where + * the data is retrieved into the column cells. + * @since + * @author Vaadin Ltd + */ +public interface ColumnVisibilityChangeHandler extends EventHandler { + + /** + * A column visibility change event, fired by Grid when a column in the Grid + * has changed visibility. + * + * @param event + * column visibility change event + */ + public void onVisibilityChange(ColumnVisibilityChangeEvent event); +} diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index c7ec9cd062..7e08348da0 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -107,6 +107,8 @@ import com.vaadin.client.widget.grid.events.BodyKeyPressHandler; import com.vaadin.client.widget.grid.events.BodyKeyUpHandler; import com.vaadin.client.widget.grid.events.ColumnReorderEvent; import com.vaadin.client.widget.grid.events.ColumnReorderHandler; +import com.vaadin.client.widget.grid.events.ColumnVisibilityChangeEvent; +import com.vaadin.client.widget.grid.events.ColumnVisibilityChangeHandler; import com.vaadin.client.widget.grid.events.FooterClickHandler; import com.vaadin.client.widget.grid.events.FooterDoubleClickHandler; import com.vaadin.client.widget.grid.events.FooterKeyDownHandler; @@ -3671,6 +3673,8 @@ public class Grid extends ResizeComposite implements grid.getVisibleColumns().indexOf(this), 1); } scheduleColumnWidthRecalculator(); + this.grid.fireEvent(new ColumnVisibilityChangeEvent(this, + hidden, false)); } } @@ -6417,6 +6421,20 @@ public class Grid extends ResizeComposite implements return addHandler(handler, ColumnReorderEvent.getType()); } + /** + * Register a column visibility change handler to this Grid. The event for + * this handler is fired when the Grid's columns change visibility. + * + * @since + * @param handler + * the handler for the event + * @return the registration for the event + */ + public HandlerRegistration addColumnVisibilityChangeHandler( + ColumnVisibilityChangeHandler handler) { + return addHandler(handler, ColumnVisibilityChangeEvent.getType()); + } + /** * Apply sorting to data source. */ diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnHidingTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnHidingTest.java index b4593dac28..6d38c25fb6 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnHidingTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnHidingTest.java @@ -15,8 +15,13 @@ */ package com.vaadin.tests.components.grid.basicfeatures; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + import org.junit.Before; import org.junit.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; import com.vaadin.testbench.parallel.TestCategory; @@ -81,6 +86,47 @@ public class GridColumnHidingTest extends GridBasicClientFeaturesTest { assertColumnHeaderOrder(0, 1, 2, 3, 4, 5, 6); } + @Test + public void testColumnHiding_onVisibilityChange_triggersClientSideEvent() { + assertColumnHeaderOrder(0, 1, 2, 3, 4); + selectMenuPath("Component", "Internals", "Listeners", + "Add Column Visibility Change listener"); + + toggleHideColumn(2); + assertColumnHeaderOrder(0, 1, 3, 4); + + WebElement webElement = findElement(By.id("columnvisibility")); + int counter = Integer.parseInt(webElement.getAttribute("counter")); + int columnIndex = Integer.parseInt(webElement + .getAttribute("columnindex")); + boolean userOriginated = Boolean.parseBoolean(webElement + .getAttribute("useroriginated")); + boolean hidden = Boolean.parseBoolean(webElement + .getAttribute("ishidden")); + + assertNotNull("no event fired", webElement); + assertEquals(1, counter); + assertEquals(2, columnIndex); + assertEquals(false, userOriginated); + assertEquals(true, hidden); + + toggleHideColumn(2); + assertColumnHeaderOrder(0, 1, 2, 3, 4); + + webElement = findElement(By.id("columnvisibility")); + counter = Integer.parseInt(webElement.getAttribute("counter")); + columnIndex = Integer.parseInt(webElement.getAttribute("columnIndex")); + userOriginated = Boolean.parseBoolean(webElement + .getAttribute("userOriginated")); + hidden = Boolean.parseBoolean(webElement.getAttribute("ishidden")); + + assertNotNull("no event fired", webElement); + assertEquals(2, counter); + assertEquals(2, columnIndex); + assertEquals(false, userOriginated); + assertEquals(false, hidden); + } + private void toggleHideColumn(int columnIndex) { selectMenuPath("Component", "Columns", "Column " + columnIndex, "Hidden"); 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 5ec8058ae9..9131a2bdbe 100644 --- a/uitest/src/com/vaadin/tests/widgetset/client/grid/GridBasicClientFeaturesWidget.java +++ b/uitest/src/com/vaadin/tests/widgetset/client/grid/GridBasicClientFeaturesWidget.java @@ -57,6 +57,8 @@ import com.vaadin.client.widget.grid.events.BodyKeyPressHandler; import com.vaadin.client.widget.grid.events.BodyKeyUpHandler; import com.vaadin.client.widget.grid.events.ColumnReorderEvent; import com.vaadin.client.widget.grid.events.ColumnReorderHandler; +import com.vaadin.client.widget.grid.events.ColumnVisibilityChangeEvent; +import com.vaadin.client.widget.grid.events.ColumnVisibilityChangeHandler; import com.vaadin.client.widget.grid.events.FooterKeyDownHandler; import com.vaadin.client.widget.grid.events.FooterKeyPressHandler; import com.vaadin.client.widget.grid.events.FooterKeyUpHandler; @@ -471,6 +473,46 @@ public class GridBasicClientFeaturesWidget extends }); } }, listenersPath); + addMenuCommand("Add Column Visibility Change listener", + new ScheduledCommand() { + private HandlerRegistration columnVisibilityHandler = null; + + @Override + public void execute() { + if (columnVisibilityHandler != null) { + return; + } + final Label columnOrderLabel = new Label(); + columnOrderLabel.getElement().setId("columnvisibility"); + addLineEnd(columnOrderLabel, 250); + ColumnVisibilityChangeHandler handler = new ColumnVisibilityChangeHandler>() { + + private int eventIndex = 0; + + @Override + public void onVisibilityChange( + ColumnVisibilityChangeEvent> event) { + columnOrderLabel.getElement().setAttribute( + "counter", "" + (++eventIndex)); + columnOrderLabel.getElement().setAttribute( + "useroriginated", + (Boolean.toString(event + .isUserOriginated()))); + columnOrderLabel.getElement().setAttribute( + "ishidden", + (Boolean.toString(event.isHidden()))); + columnOrderLabel.getElement().setAttribute( + "columnindex", + "" + + grid.getColumns().indexOf( + event.getColumn())); + } + }; + + columnVisibilityHandler = grid + .addColumnVisibilityChangeHandler(handler); + } + }, listenersPath); } private void createStateMenu() { -- cgit v1.2.3 From 8ece5b005a8edb5c50d06c0ac9758044f7e8b877 Mon Sep 17 00:00:00 2001 From: Pekka Hyvönen Date: Fri, 13 Mar 2015 14:39:23 +0200 Subject: Client side API for setting a grid column hideable (#17023) Change-Id: I389d603210744ea02846a00ab893188b7953145f --- client/src/com/vaadin/client/widgets/Grid.java | 34 ++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) (limited to 'client/src') diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index 7e08348da0..f7744d52ae 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -3385,6 +3385,8 @@ public class Grid extends ResizeComposite implements private boolean hidden = false; + private boolean hideable = false; + private String headerCaption = ""; private double minimumWidthPx = GridConstants.DEFAULT_MIN_WIDTH; @@ -3689,6 +3691,38 @@ public class Grid extends ResizeComposite implements return hidden; } + /** + * Set whether it is possible for the user to hide this column or not. + * Default is {@code false}. + *

+ * Note: it is still possible to hide the column + * programmatically using {@link #setHidden(boolean)}. + * + * @since + * @param hideable + * true if the user can hide this column, + * false if not + */ + public void setHideable(boolean hideable) { + this.hideable = hideable; + // TODO update whether sidebar/popup can be opened + } + + /** + * Is it possible for the the user to hide this column. Default is + * {@code false}. + *

+ * Note: the column can be programmatically hidden using + * {@link #setHidden(boolean)} regardless of the returned value. + * + * @since + * @return true if the user can hide the column, + * false if not + */ + public boolean isHideable() { + return hideable; + } + @Override public String toString() { String details = ""; -- cgit v1.2.3 From 84c143dd76ed1d27d03c0d695e7218b477d008fe Mon Sep 17 00:00:00 2001 From: Henrik Paul Date: Mon, 9 Mar 2015 14:31:37 +0200 Subject: Server side Grid can open details on the client side (#16644) Change-Id: Ibff5a83b3a09c7c530926dadae9138ba3823f27a --- .../vaadin/client/connectors/GridConnector.java | 26 ++++++- .../client/connectors/RpcDataSourceConnector.java | 53 +++++++++++-- .../client/widget/grid/DetailsGenerator.java | 1 + client/src/com/vaadin/client/widgets/Grid.java | 7 ++ .../com/vaadin/data/RpcDataProviderExtension.java | 72 ++++++++++++++++- server/src/com/vaadin/ui/Grid.java | 91 ++++++++++++++++++++++ .../src/com/vaadin/shared/ui/grid/GridState.java | 10 +++ .../grid/basicfeatures/GridBasicFeatures.java | 14 ++++ .../server/GridDetailsServerTest.java | 76 ++++++++++++++++++ 9 files changed, 341 insertions(+), 9 deletions(-) create mode 100644 uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridDetailsServerTest.java (limited to 'client/src') diff --git a/client/src/com/vaadin/client/connectors/GridConnector.java b/client/src/com/vaadin/client/connectors/GridConnector.java index 55f07ecf85..f476982c15 100644 --- a/client/src/com/vaadin/client/connectors/GridConnector.java +++ b/client/src/com/vaadin/client/connectors/GridConnector.java @@ -31,6 +31,7 @@ import java.util.logging.Logger; import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.Widget; import com.vaadin.client.ComponentConnector; import com.vaadin.client.ConnectorHierarchyChangeEvent; @@ -45,6 +46,7 @@ import com.vaadin.client.ui.AbstractHasComponentsConnector; import com.vaadin.client.ui.SimpleManagedLayout; 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.RowReference; import com.vaadin.client.widget.grid.RowStyleGenerator; @@ -101,7 +103,7 @@ import elemental.json.JsonValue; */ @Connect(com.vaadin.ui.Grid.class) public class GridConnector extends AbstractHasComponentsConnector implements - SimpleManagedLayout { + SimpleManagedLayout, RpcDataSourceConnector.DetailsListener { private static final class CustomCellStyleGenerator implements CellStyleGenerator { @@ -360,6 +362,14 @@ public class GridConnector extends AbstractHasComponentsConnector implements } } + private class CustomDetailsGenerator implements DetailsGenerator { + @Override + public Widget getDetails(int rowIndex) { + // TODO + return new Label("[todo]"); + } + } + /** * Maps a generated column id to a grid column instance */ @@ -501,7 +511,11 @@ public class GridConnector extends AbstractHasComponentsConnector implements }); getWidget().setEditorHandler(new CustomEditorHandler()); + + getWidget().setDetailsGenerator(new CustomDetailsGenerator()); + getLayoutManager().registerDependency(this, getWidget().getElement()); + layout(); } @@ -994,4 +1008,14 @@ public class GridConnector extends AbstractHasComponentsConnector implements public void layout() { getWidget().onResize(); } + + @Override + public void reapplyDetailsVisibility(int rowIndex, JsonObject row) { + if (row.hasKey(GridState.JSONKEY_DETAILS_VISIBLE) + && row.getBoolean(GridState.JSONKEY_DETAILS_VISIBLE)) { + getWidget().setDetailsVisible(rowIndex, true); + } else { + getWidget().setDetailsVisible(rowIndex, false); + } + } } diff --git a/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java b/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java index f8d6ebcb62..ae4249de78 100644 --- a/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java +++ b/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java @@ -17,6 +17,7 @@ package com.vaadin.client.connectors; import java.util.ArrayList; +import java.util.List; import com.vaadin.client.ServerConnector; import com.vaadin.client.data.AbstractRemoteDataSource; @@ -43,6 +44,28 @@ import elemental.json.JsonObject; @Connect(com.vaadin.data.RpcDataProviderExtension.class) public class RpcDataSourceConnector extends AbstractExtensionConnector { + /** + * A callback interface to let {@link GridConnector} know that detail + * visibilities might have changed. + * + * @since + * @author Vaadin Ltd + */ + interface DetailsListener { + + /** + * A request to verify (and correct) the visibility for a row, given + * updated metadata. + * + * @param rowIndex + * the index of the row that should be checked + * @param row + * the row object to check visibility for + * @see GridState#JSONKEY_DETAILS_VISIBLE + */ + void reapplyDetailsVisibility(int rowIndex, JsonObject row); + } + public class RpcDataSource extends AbstractRemoteDataSource { protected RpcDataSource() { @@ -56,27 +79,28 @@ public class RpcDataSourceConnector extends AbstractExtensionConnector { rows.add(rowObject); } - dataSource.setRowData(firstRow, rows); + RpcDataSource.this.setRowData(firstRow, rows); } @Override public void removeRowData(int firstRow, int count) { - dataSource.removeRowData(firstRow, count); + RpcDataSource.this.removeRowData(firstRow, count); } @Override public void insertRowData(int firstRow, int count) { - dataSource.insertRowData(firstRow, count); + RpcDataSource.this.insertRowData(firstRow, count); } @Override public void resetDataAndSize(int size) { - dataSource.resetDataAndSize(size); + RpcDataSource.this.resetDataAndSize(size); } }); } private DataRequestRpc rpcProxy = getRpcProxy(DataRequestRpc.class); + private DetailsListener detailsListener; @Override protected void requestRows(int firstRowIndex, int numberOfRows, @@ -170,7 +194,24 @@ public class RpcDataSourceConnector extends AbstractExtensionConnector { if (!handle.isPinned()) { rpcProxy.setPinned(key, false); } + } + + void setDetailsListener(DetailsListener detailsListener) { + this.detailsListener = detailsListener; + } + @Override + protected void setRowData(int firstRowIndex, List rowData) { + super.setRowData(firstRowIndex, rowData); + + /* + * Intercepting details information from the data source, rerouting + * them back to the GridConnector (as a details listener) + */ + for (int i = 0; i < rowData.size(); i++) { + detailsListener.reapplyDetailsVisibility(firstRowIndex + i, + rowData.get(i)); + } } } @@ -178,6 +219,8 @@ public class RpcDataSourceConnector extends AbstractExtensionConnector { @Override protected void extend(ServerConnector target) { - ((GridConnector) target).setDataSource(dataSource); + GridConnector gridConnector = (GridConnector) target; + dataSource.setDetailsListener(gridConnector); + gridConnector.setDataSource(dataSource); } } diff --git a/client/src/com/vaadin/client/widget/grid/DetailsGenerator.java b/client/src/com/vaadin/client/widget/grid/DetailsGenerator.java index 264aa4e614..309e3f1ea3 100644 --- a/client/src/com/vaadin/client/widget/grid/DetailsGenerator.java +++ b/client/src/com/vaadin/client/widget/grid/DetailsGenerator.java @@ -25,6 +25,7 @@ import com.google.gwt.user.client.ui.Widget; */ public interface DetailsGenerator { + /** A details generator that provides no details */ public static final DetailsGenerator NULL = new DetailsGenerator() { @Override public Widget getDetails(int rowIndex) { diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index b3906591c0..f4aaf798b7 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -6326,10 +6326,17 @@ public class Grid extends ResizeComposite implements * @since * @param detailsGenerator * the details generator to set + * @throws IllegalArgumentException + * if detailsGenerator is null; */ public void setDetailsGenerator(DetailsGenerator detailsGenerator) throws IllegalArgumentException { + if (detailsGenerator == null) { + throw new IllegalArgumentException( + "Details generator may not be null"); + } + this.detailsGenerator = detailsGenerator; // this will refresh all visible spacers diff --git a/server/src/com/vaadin/data/RpcDataProviderExtension.java b/server/src/com/vaadin/data/RpcDataProviderExtension.java index 991cb0537d..cf2284a62e 100644 --- a/server/src/com/vaadin/data/RpcDataProviderExtension.java +++ b/server/src/com/vaadin/data/RpcDataProviderExtension.java @@ -95,7 +95,7 @@ public class RpcDataProviderExtension extends AbstractExtension { // private implementation } - void setActiveRange(Range newActiveRange) { + public void setActiveRange(Range newActiveRange) { final Range[] removed = activeRange.partitionWith(newActiveRange); final Range[] added = newActiveRange.partitionWith(activeRange); @@ -163,7 +163,7 @@ public class RpcDataProviderExtension extends AbstractExtension { return String.valueOf(rollingIndex++); } - String getKey(Object itemId) { + public String getKey(Object itemId) { String key = itemIdToKey.get(itemId); if (key == null) { key = nextKey(); @@ -240,6 +240,20 @@ public class RpcDataProviderExtension extends AbstractExtension { return itemIds; } + /** + * Gets the row index for a given item. + * + * @since + * @param itemId + * the item id of the item for which to get the item + * @return the index of the item, or -1 if no such item could be found + */ + @SuppressWarnings("boxing") + public int getIndex(Object itemId) { + Integer integer = indexToItemId.inverse().get(itemId); + return integer != null ? integer : -1; + } + /** * Pin an item id to be cached indefinitely. *

@@ -304,7 +318,7 @@ public class RpcDataProviderExtension extends AbstractExtension { return pinnedItemIds.contains(itemId); } - Object itemIdAtIndex(int index) { + private Object itemIdAtIndex(int index) { return indexToItemId.get(Integer.valueOf(index)); } } @@ -727,6 +741,12 @@ public class RpcDataProviderExtension extends AbstractExtension { /** Size possibly changed with a bare ItemSetChangeEvent */ private boolean bareItemSetTriggeredSizeChange = false; + /** + * This map represents all the details that are user-defined as visible. + * This does not reflect the status in the DOM. + */ + private Set visibleDetails = new HashSet(); + /** * Creates a new data provider using the given container. * @@ -859,6 +879,10 @@ public class RpcDataProviderExtension extends AbstractExtension { rowObject.put(GridState.JSONKEY_DATA, rowData); rowObject.put(GridState.JSONKEY_ROWKEY, keyMapper.getKey(itemId)); + if (visibleDetails.contains(itemId)) { + rowObject.put(GridState.JSONKEY_DETAILS_VISIBLE, true); + } + rowReference.set(itemId); CellStyleGenerator cellStyleGenerator = grid.getCellStyleGenerator(); @@ -1116,4 +1140,46 @@ public class RpcDataProviderExtension extends AbstractExtension { return Logger.getLogger(RpcDataProviderExtension.class.getName()); } + /** + * Marks a row's details to be visible or hidden. + *

+ * If that row is currently in the client side's cache, this information + * will be sent over to the client. + * + * @since + * @param itemId + * the id of the item of which to change the details visibility + * @param visible + * true to show the details, false to + * hide + */ + public void setDetailsVisible(Object itemId, boolean visible) { + final boolean modified; + if (visible) { + modified = visibleDetails.add(itemId); + } else { + modified = visibleDetails.remove(itemId); + } + + int rowIndex = keyMapper.getIndex(itemId); + boolean modifiedRowIsActive = activeRowHandler.activeRange + .contains(rowIndex); + if (modified && modifiedRowIsActive) { + updateRowData(itemId); + } + } + + /** + * Checks whether the details for a row is marked as visible. + * + * @since + * @param itemId + * the id of the item of which to check the visibility + * @return true iff the detials are visible for the item. This + * might return true even if the row is not currently + * visible in the DOM + */ + public boolean isDetailsVisible(Object itemId) { + return visibleDetails.contains(itemId); + } } diff --git a/server/src/com/vaadin/ui/Grid.java b/server/src/com/vaadin/ui/Grid.java index 22ef0333c2..b56bb0d036 100644 --- a/server/src/com/vaadin/ui/Grid.java +++ b/server/src/com/vaadin/ui/Grid.java @@ -164,6 +164,34 @@ import elemental.json.JsonValue; public class Grid extends AbstractComponent implements SelectionNotifier, SortNotifier, SelectiveRenderer, ItemClickNotifier { + /** + * A callback interface for generating details for a particular row in Grid. + * + * @since + * @author Vaadin Ltd + */ + public interface DetailsGenerator extends Serializable { + + /** A details generator that provides no details */ + public DetailsGenerator NULL = new DetailsGenerator() { + @Override + public Component getDetails(RowReference rowReference) { + return null; + } + }; + + /** + * This method is called for whenever a new details row needs to be + * generated. + * + * @param rowReference + * the reference for the row for which to generate details + * @return the details for the given row, or null to leave + * the details empty. + */ + Component getDetails(RowReference rowReference); + } + /** * Custom field group that allows finding property types before an item has * been bound. @@ -2888,6 +2916,8 @@ public class Grid extends AbstractComponent implements SelectionNotifier, private EditorErrorHandler editorErrorHandler = new DefaultEditorErrorHandler(); + private DetailsGenerator detailsGenerator = DetailsGenerator.NULL; + private static final Method SELECTION_CHANGE_METHOD = ReflectTools .findMethod(SelectionListener.class, "select", SelectionEvent.class); @@ -5093,4 +5123,65 @@ public class Grid extends AbstractComponent implements SelectionNotifier, public void recalculateColumnWidths() { getRpcProxy(GridClientRpc.class).recalculateColumnWidths(); } + + /** + * 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 + * @throws IllegalArgumentException + * if detailsGenerator is null; + */ + public void setDetailsGenerator(DetailsGenerator detailsGenerator) + throws IllegalArgumentException { + if (detailsGenerator == null) { + throw new IllegalArgumentException( + "Details generator may not be null"); + } else if (detailsGenerator == this.detailsGenerator) { + return; + } + + this.detailsGenerator = detailsGenerator; + + getLogger().warning("[[details]] update details on generator swap"); + } + + /** + * 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 item. + * + * @since + * @param itemId + * the id of the item for which to set details visibility + * @param visible + * true to show the details, or false + * to hide them + */ + public void setDetailsVisible(Object itemId, boolean visible) { + datasourceExtension.setDetailsVisible(itemId, visible); + } + + /** + * Checks whether details are visible for the given item. + * + * @since + * @param itemId + * the id of the item for which to check details visibility + * @return true iff the details are visible + */ + public boolean isDetailsVisible(Object itemId) { + return datasourceExtension.isDetailsVisible(itemId); + } } diff --git a/shared/src/com/vaadin/shared/ui/grid/GridState.java b/shared/src/com/vaadin/shared/ui/grid/GridState.java index 7018df1413..81e1827420 100644 --- a/shared/src/com/vaadin/shared/ui/grid/GridState.java +++ b/shared/src/com/vaadin/shared/ui/grid/GridState.java @@ -102,6 +102,16 @@ public class GridState extends AbstractComponentState { */ public static final String JSONKEY_CELLSTYLES = "cs"; + /** + * The key that tells whether details are visible for the row + * + * @see com.vaadin.ui.Grid#setDetailsGenerator(com.vaadin.ui.Grid.DetailsGenerator) + * @see com.vaadin.ui.Grid#setDetailsVisible(Object, boolean) + * @see com.vaadin.shared.data.DataProviderRpc#setRowData(int, + * elemental.json.JsonArray) + * */ + public static final String JSONKEY_DETAILS_VISIBLE = "dv"; + /** * Columns in grid. */ diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java index e5a46894b8..f0c4b3d9c0 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java @@ -248,6 +248,8 @@ public class GridBasicFeatures extends AbstractComponentTest { addFilterActions(); + createDetailsActions(); + this.grid = grid; return grid; } @@ -1051,6 +1053,18 @@ public class GridBasicFeatures extends AbstractComponentTest { }, null); } + private void createDetailsActions() { + createBooleanAction("firstItemId", "Details", false, + new Command() { + @Override + @SuppressWarnings("boxing") + public void execute(Grid g, Boolean visible, Object data) { + g.setDetailsVisible(g.getContainerDataSource() + .firstItemId(), visible); + } + }); + } + @Override protected Integer getTicketNumber() { return 12829; diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridDetailsServerTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridDetailsServerTest.java new file mode 100644 index 0000000000..01d2ba55eb --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridDetailsServerTest.java @@ -0,0 +1,76 @@ +/* + * 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.server; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; + +import org.junit.Before; +import org.junit.Test; +import org.openqa.selenium.NoSuchElementException; + +import com.vaadin.testbench.annotations.RunLocally; +import com.vaadin.testbench.parallel.Browser; +import com.vaadin.tests.components.grid.basicfeatures.GridBasicFeaturesTest; + +@RunLocally(Browser.PHANTOMJS) +public class GridDetailsServerTest extends GridBasicFeaturesTest { + private static final String[] FIRST_ITEM_DETAILS = new String[] { + "Component", "Details", "firstItemId" }; + + @Before + public void setUp() { + openTestURL(); + } + + @Test + public void openVisibleDetails() { + try { + getGridElement().getDetails(0); + fail("Expected NoSuchElementException"); + } catch (NoSuchElementException ignore) { + // expected + } + selectMenuPath(FIRST_ITEM_DETAILS); + assertNotNull("details should've opened", getGridElement() + .getDetails(0)); + } + + @Test(expected = NoSuchElementException.class) + public void closeVisibleDetails() { + selectMenuPath(FIRST_ITEM_DETAILS); + selectMenuPath(FIRST_ITEM_DETAILS); + getGridElement().getDetails(0); + } + + @Test + public void openDetailsOutsideOfActiveRange() { + getGridElement().scroll(10000); + selectMenuPath(FIRST_ITEM_DETAILS); + getGridElement().scroll(0); + assertNotNull("details should've been opened", getGridElement() + .getDetails(0)); + } + + @Test(expected = NoSuchElementException.class) + public void closeDetailsOutsideOfActiveRange() { + selectMenuPath(FIRST_ITEM_DETAILS); + getGridElement().scroll(10000); + selectMenuPath(FIRST_ITEM_DETAILS); + getGridElement().scroll(0); + getGridElement().getDetails(0); + } +} -- cgit v1.2.3 From 93e879afdf90c1593c7b99528b0e2e2220d7fab7 Mon Sep 17 00:00:00 2001 From: Pekka Hyvönen Date: Mon, 16 Mar 2015 15:26:56 +0200 Subject: Display button for opening sidebar in Grid when hidable columns #17023 Change-Id: I9dc1d5d8ede4984c2dd6f5fcc932bb987ce95dcb --- WebContent/VAADIN/themes/base/grid/grid.scss | 27 ++++ client/src/com/vaadin/client/widgets/Grid.java | 155 ++++++++++++++++++++- .../grid/basicfeatures/GridColumnHidingTest.java | 84 +++++++++++ .../client/grid/GridBasicClientFeaturesWidget.java | 7 +- 4 files changed, 265 insertions(+), 8 deletions(-) (limited to 'client/src') diff --git a/WebContent/VAADIN/themes/base/grid/grid.scss b/WebContent/VAADIN/themes/base/grid/grid.scss index 20f8478885..eb6e0112ad 100644 --- a/WebContent/VAADIN/themes/base/grid/grid.scss +++ b/WebContent/VAADIN/themes/base/grid/grid.scss @@ -52,6 +52,8 @@ $v-grid-editor-background-color: $v-grid-row-background-color !default; .#{$primaryStyleName}-tablewrapper { border: $v-grid-border; } + + // Column drag and drop elements .#{$primaryStyleName} .header-drag-table { border-spacing: 0; @@ -76,6 +78,31 @@ $v-grid-editor-background-color: $v-grid-row-background-color !default; } } } + + // Sidebar + + .#{$primaryStyleName}-sidebar { + position: absolute; + top: 1px; + right : 0; + + background-color: $v-grid-header-background-color; + border-left: $v-grid-header-border; + border-bottom: $v-grid-header-border; + z-index: 5; + + .#{$primaryStyleName}-sidebar-button { + height: $v-grid-header-row-height; + + &:after { + content: "\f0c9"; + font-family: FontAwesome, sans-serif; + font-size: $v-grid-header-font-size; + line-height: $v-grid-header-row-height; + padding: 0 $v-grid-cell-padding-horizontal; + } + } + } // Common cell styles diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index f7744d52ae..e84ea7d1f5 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -61,9 +61,12 @@ import com.google.gwt.user.client.Event.NativePreviewHandler; import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.CheckBox; +import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.HasEnabled; +import com.google.gwt.user.client.ui.HasHorizontalAlignment.HorizontalAlignmentConstant; import com.google.gwt.user.client.ui.HasWidgets; import com.google.gwt.user.client.ui.ResizeComposite; +import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; import com.vaadin.client.BrowserInfo; import com.vaadin.client.DeferredWorker; @@ -74,6 +77,7 @@ import com.vaadin.client.renderers.ComplexRenderer; import com.vaadin.client.renderers.Renderer; import com.vaadin.client.renderers.WidgetRenderer; import com.vaadin.client.ui.SubPartAware; +import com.vaadin.client.ui.VButton; import com.vaadin.client.ui.dd.DragAndDropHandler; import com.vaadin.client.ui.dd.DragAndDropHandler.DragAndDropCallback; import com.vaadin.client.widget.escalator.Cell; @@ -2784,6 +2788,92 @@ public class Grid extends ResizeComposite implements } } + /** + * Sidebar displaying toggles for hidable columns and additional custom + * widgets. + * + * @since + */ + public static class Sidebar extends Composite { + + private final ClickHandler openCloseButtonHandler = new ClickHandler() { + + @Override + public void onClick(ClickEvent event) { + if (!open) { + open(); + } else { + close(); + } + } + }; + + private final VerticalPanel rootContainer; + + private final VButton openCloseButton; + + private boolean open; + + public Sidebar() { + rootContainer = new VerticalPanel(); + initWidget(rootContainer); + + openCloseButton = new VButton(); + openCloseButton.addClickHandler(openCloseButtonHandler); + + rootContainer.add(openCloseButton); + rootContainer + .setCellHorizontalAlignment( + openCloseButton, + HorizontalAlignmentConstant + .endOf(com.google.gwt.i18n.client.HasDirection.Direction.LTR)); + } + + /** + * Opens the sidebar if not yet opened. + * + * @since + */ + public void open() { + if (!open) { + addStyleName("opened"); + open = true; + } + } + + /** + * Closes the sidebar if not yet closed. + * + * @since + */ + public void close() { + if (open) { + removeStyleName("opened"); + open = false; + } + } + + /** + * Returns whether the sidebar is open or not. + *

+ * Note: The sidebar can be in "open state" but not actually + * visible inside grid. See {@link #isVisibleInGrid()}. + * + * @since + * @return true if open, false if not + */ + public boolean isOpen() { + return open; + } + + @Override + public void setStylePrimaryName(String styleName) { + super.setStylePrimaryName(styleName); + openCloseButton.setStylePrimaryName(styleName + "-button"); + } + + } + /** * Escalator used internally by grid to render the rows */ @@ -2793,6 +2883,8 @@ public class Grid extends ResizeComposite implements private final Footer footer = GWT.create(Footer.class); + private final Sidebar sidebar = GWT.create(Sidebar.class); + /** * List of columns in the grid. Order defines the visible order. */ @@ -3385,7 +3477,7 @@ public class Grid extends ResizeComposite implements private boolean hidden = false; - private boolean hideable = false; + private boolean hidable = false; private String headerCaption = ""; @@ -3699,13 +3791,13 @@ public class Grid extends ResizeComposite implements * programmatically using {@link #setHidden(boolean)}. * * @since - * @param hideable + * @param hidable * true if the user can hide this column, * false if not */ - public void setHideable(boolean hideable) { - this.hideable = hideable; - // TODO update whether sidebar/popup can be opened + public void setHidable(boolean hidable) { + this.hidable = hidable; + grid.updateSideBarVisibility(); } /** @@ -3719,8 +3811,8 @@ public class Grid extends ResizeComposite implements * @return true if the user can hide the column, * false if not */ - public boolean isHideable() { - return hideable; + public boolean isHidable() { + return hidable; } @Override @@ -4444,6 +4536,7 @@ public class Grid extends ResizeComposite implements super.setStylePrimaryName(style); escalator.setStylePrimaryName(style); editor.setStylePrimaryName(style); + sidebar.setStylePrimaryName(style + "-sidebar"); String rowStyle = getStylePrimaryName() + "-row"; rowHasDataStyleName = rowStyle + "-has-data"; @@ -6945,4 +7038,52 @@ public class Grid extends ResizeComposite implements public void recalculateColumnWidths() { autoColumnWidthsRecalculator.schedule(); } + + /** + * Setter for displaying the grid's {@link Sidebar}. The sidebar is visible + * automatically when there are {@link Column#setHidable(boolean) hidable + * columns}. + *

+ * Setting the sidebar visible doens't open it - it only shows the button + * for opening it. For opening and closing the sidebar use + * {@link Sidebar#open()} and {@link Sidebar#close()}. + * + * @since + * @param visible + * true for showing the sidebar, false + * for removing it + */ + public void setSidebarVisible(boolean visible) { + if ((sidebar.getParent() != null) != visible) { + if (visible) { + getElement().appendChild(sidebar.getElement()); + setParent(sidebar, this); + } else { + sidebar.getElement().removeFromParent(); + sidebar.removeFromParent(); + } + } + } + + /** + * Returns the sidebar for this grid. + * + * @since + * @return + */ + public Sidebar getSidebar() { + return sidebar; + } + + private void updateSideBarVisibility() { + boolean visible = false; + for (Column c : getColumns()) { + if (c.isHidable()) { + visible = true; + break; + } + } + setSidebarVisible(visible); + } + } diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnHidingTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnHidingTest.java index 6d38c25fb6..ba4468da2c 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnHidingTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnHidingTest.java @@ -16,7 +16,12 @@ package com.vaadin.tests.components.grid.basicfeatures; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.List; import org.junit.Before; import org.junit.Test; @@ -127,6 +132,85 @@ public class GridColumnHidingTest extends GridBasicClientFeaturesTest { assertEquals(false, hidden); } + @Test + public void testColumnHidability_onTriggerColumnHidability_showsSidebarButton() { + WebElement sidebar = getSidebar(); + assertNull(sidebar); + + toggleHidableColumn(0); + + sidebar = getSidebar(); + assertNotNull(sidebar); + } + + @Test + public void testColumnHidability_triggeringColumnHidabilityWithSeveralColumns_showsAndHidesSiderbarButton() { + verifySidebarNotVisible(); + + toggleHidableColumn(3); + toggleHidableColumn(4); + + verifySidebarVisible(); + + toggleHidableColumn(3); + + verifySidebarVisible(); + + toggleHidableColumn(4); + + verifySidebarNotVisible(); + } + + @Test + public void testColumnHidability_clickingSidebarButton_opensClosesSidebar() { + toggleHidableColumn(0); + verifySidebarClosed(); + + getSidebarOpenButton().click(); + + verifySidebarOpened(); + + getSidebarOpenButton().click(); + + verifySidebarClosed(); + } + + private void verifySidebarOpened() { + WebElement sidebar = getSidebar(); + assertTrue(sidebar.getAttribute("class").contains("opened")); + } + + private void verifySidebarClosed() { + WebElement sidebar = getSidebar(); + assertFalse(sidebar.getAttribute("class").contains("opened")); + } + + private void verifySidebarNotVisible() { + WebElement sidebar = getSidebar(); + assertNull(sidebar); + } + + private void verifySidebarVisible() { + WebElement sidebar = getSidebar(); + assertNotNull(sidebar); + } + + private WebElement getSidebar() { + List elements = findElements(By.className("v-grid-sidebar")); + return elements.isEmpty() ? null : elements.get(0); + } + + private WebElement getSidebarOpenButton() { + List elements = findElements(By + .className("v-grid-sidebar-button")); + return elements.isEmpty() ? null : elements.get(0); + } + + private void toggleHidableColumn(int columnIndex) { + selectMenuPath("Component", "Columns", "Column " + columnIndex, + "Hidable"); + } + private void toggleHideColumn(int columnIndex) { selectMenuPath("Component", "Columns", "Column " + columnIndex, "Hidden"); 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 9131a2bdbe..1db3eebea6 100644 --- a/uitest/src/com/vaadin/tests/widgetset/client/grid/GridBasicClientFeaturesWidget.java +++ b/uitest/src/com/vaadin/tests/widgetset/client/grid/GridBasicClientFeaturesWidget.java @@ -782,7 +782,12 @@ public class GridBasicClientFeaturesWidget extends column.setHidden(!column.isHidden()); } }, "Component", "Columns", "Column " + i); - + addMenuCommand("Hidable", new ScheduledCommand() { + @Override + public void execute() { + column.setHidable(!column.isHidable()); + } + }, "Component", "Columns", "Column " + i); addMenuCommand("auto", new ScheduledCommand() { @Override public void execute() { -- cgit v1.2.3 From a1619ee73dc18eecda22056541826a3c8bb65a5c Mon Sep 17 00:00:00 2001 From: Henrik Paul Date: Tue, 10 Mar 2015 17:02:02 +0200 Subject: Grid's Details can now be Components (#16644) Change-Id: If67dd2e86cf41c57f208a3691e2cb7a5a29c133c --- .../vaadin/client/connectors/GridConnector.java | 154 +++++++++++++- .../client/data/AbstractRemoteDataSource.java | 1 + .../com/vaadin/data/RpcDataProviderExtension.java | 31 ++- server/src/com/vaadin/ui/Grid.java | 230 ++++++++++++++++++++- .../component/grid/DataProviderExtension.java | 2 +- .../shared/ui/grid/ConnectorIndexChange.java | 143 +++++++++++++ .../com/vaadin/shared/ui/grid/GridClientRpc.java | 15 ++ .../com/vaadin/shared/ui/grid/GridServerRpc.java | 17 ++ .../grid/basicfeatures/GridBasicFeatures.java | 74 +++++++ .../server/GridDetailsServerTest.java | 100 ++++++++- 10 files changed, 754 insertions(+), 13 deletions(-) create mode 100644 shared/src/com/vaadin/shared/ui/grid/ConnectorIndexChange.java (limited to 'client/src') diff --git a/client/src/com/vaadin/client/connectors/GridConnector.java b/client/src/com/vaadin/client/connectors/GridConnector.java index f476982c15..70ad2504d8 100644 --- a/client/src/com/vaadin/client/connectors/GridConnector.java +++ b/client/src/com/vaadin/client/connectors/GridConnector.java @@ -29,12 +29,14 @@ import java.util.Set; import java.util.logging.Logger; import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.RepeatingCommand; import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.dom.client.NativeEvent; -import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.ui.Widget; import com.vaadin.client.ComponentConnector; import com.vaadin.client.ConnectorHierarchyChangeEvent; +import com.vaadin.client.DeferredWorker; import com.vaadin.client.MouseEventDetailsBuilder; import com.vaadin.client.annotations.OnStateChange; import com.vaadin.client.communication.StateChangeEvent; @@ -72,8 +74,10 @@ import com.vaadin.client.widgets.Grid.FooterCell; import com.vaadin.client.widgets.Grid.FooterRow; import com.vaadin.client.widgets.Grid.HeaderCell; import com.vaadin.client.widgets.Grid.HeaderRow; +import com.vaadin.shared.Connector; import com.vaadin.shared.data.sort.SortDirection; import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.grid.ConnectorIndexChange; import com.vaadin.shared.ui.grid.EditorClientRpc; import com.vaadin.shared.ui.grid.EditorServerRpc; import com.vaadin.shared.ui.grid.GridClientRpc; @@ -103,7 +107,8 @@ import elemental.json.JsonValue; */ @Connect(com.vaadin.ui.Grid.class) public class GridConnector extends AbstractHasComponentsConnector implements - SimpleManagedLayout, RpcDataSourceConnector.DetailsListener { + SimpleManagedLayout, RpcDataSourceConnector.DetailsListener, + DeferredWorker { private static final class CustomCellStyleGenerator implements CellStyleGenerator { @@ -362,11 +367,119 @@ public class GridConnector extends AbstractHasComponentsConnector implements } } - private class CustomDetailsGenerator implements DetailsGenerator { + private static class CustomDetailsGenerator implements DetailsGenerator { + + private final Map indexToDetailsMap = new HashMap(); + @Override + @SuppressWarnings("boxing") public Widget getDetails(int rowIndex) { - // TODO - return new Label("[todo]"); + ComponentConnector componentConnector = indexToDetailsMap + .get(rowIndex); + if (componentConnector != null) { + return componentConnector.getWidget(); + } else { + return null; + } + } + + public void setDetailsConnectorChanges(Set changes) { + /* + * To avoid overwriting connectors while moving them about, we'll + * take all the affected connectors, first all remove those that are + * removed or moved, then we add back those that are moved or added. + */ + + /* Remove moved/removed connectors from bookkeeping */ + for (ConnectorIndexChange change : changes) { + Integer oldIndex = change.getOldIndex(); + Connector removedConnector = indexToDetailsMap.remove(oldIndex); + + Connector connector = change.getConnector(); + assert removedConnector == null || connector == null + || removedConnector.equals(connector) : "Index " + + oldIndex + " points to " + removedConnector + + " while " + connector + " was expected"; + } + + /* Add moved/added connectors to bookkeeping */ + for (ConnectorIndexChange change : changes) { + Integer newIndex = change.getNewIndex(); + ComponentConnector connector = (ComponentConnector) change + .getConnector(); + + if (connector != null) { + assert newIndex != null : "An existing connector has a missing new index."; + + ComponentConnector prevConnector = indexToDetailsMap.put( + newIndex, connector); + + assert prevConnector == null : "Connector collision at index " + + newIndex + + " between old " + + prevConnector + + " and new " + connector; + } + } + } + } + + @SuppressWarnings("boxing") + private class DetailsConnectorFetcher implements DeferredWorker { + + /** A flag making sure that we don't call scheduleFinally many times. */ + private boolean fetcherHasBeenCalled = false; + + /** A rolling counter for unique values. */ + private int detailsFetchCounter = 0; + + /** A collection that tracks the amount of requests currently underway. */ + private Set pendingFetches = new HashSet(5); + + private final ScheduledCommand lazyDetailsFetcher = new ScheduledCommand() { + @Override + public void execute() { + int currentFetchId = detailsFetchCounter++; + pendingFetches.add(currentFetchId); + getRpcProxy(GridServerRpc.class).sendDetailsComponents( + currentFetchId); + fetcherHasBeenCalled = false; + + assert assertRequestDoesNotTimeout(currentFetchId); + } + }; + + public void schedule() { + if (!fetcherHasBeenCalled) { + Scheduler.get().scheduleFinally(lazyDetailsFetcher); + fetcherHasBeenCalled = true; + } + } + + public void responseReceived(int fetchId) { + boolean success = pendingFetches.remove(fetchId); + assert success : "Received a response with an unidentified fetch id"; + } + + @Override + public boolean isWorkPending() { + return fetcherHasBeenCalled || !pendingFetches.isEmpty(); + } + + private boolean assertRequestDoesNotTimeout(final int fetchId) { + /* + * This method will not be compiled without asserts enabled. This + * only makes sure that any request does not time out. + * + * TODO Should this be an explicit check? Is it worth the overhead? + */ + new Timer() { + @Override + public void run() { + assert !pendingFetches.contains(fetchId); + } + }.schedule(1000); + return true; } } @@ -417,6 +530,10 @@ public class GridConnector extends AbstractHasComponentsConnector implements private String lastKnownTheme = null; + private final CustomDetailsGenerator customDetailsGenerator = new CustomDetailsGenerator(); + + private final DetailsConnectorFetcher detailsConnectorFetcher = new DetailsConnectorFetcher(); + @Override @SuppressWarnings("unchecked") public Grid getWidget() { @@ -469,6 +586,24 @@ public class GridConnector extends AbstractHasComponentsConnector implements public void recalculateColumnWidths() { getWidget().recalculateColumnWidths(); } + + @Override + public void setDetailsConnectorChanges( + Set connectorChanges, int fetchId) { + customDetailsGenerator + .setDetailsConnectorChanges(connectorChanges); + + // refresh moved/added details rows + for (ConnectorIndexChange change : connectorChanges) { + Integer newIndex = change.getNewIndex(); + if (newIndex != null) { + int index = newIndex.intValue(); + getWidget().setDetailsVisible(index, false); + getWidget().setDetailsVisible(index, true); + } + } + detailsConnectorFetcher.responseReceived(fetchId); + } }); getWidget().addSelectionHandler(internalSelectionChangeHandler); @@ -512,7 +647,7 @@ public class GridConnector extends AbstractHasComponentsConnector implements getWidget().setEditorHandler(new CustomEditorHandler()); - getWidget().setDetailsGenerator(new CustomDetailsGenerator()); + getWidget().setDetailsGenerator(customDetailsGenerator); getLayoutManager().registerDependency(this, getWidget().getElement()); @@ -1017,5 +1152,12 @@ public class GridConnector extends AbstractHasComponentsConnector implements } else { getWidget().setDetailsVisible(rowIndex, false); } + + detailsConnectorFetcher.schedule(); + } + + @Override + public boolean isWorkPending() { + return detailsConnectorFetcher.isWorkPending(); } } diff --git a/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java b/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java index 1de271c646..0ac4c33c83 100644 --- a/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java +++ b/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java @@ -570,6 +570,7 @@ public abstract class AbstractRemoteDataSource implements DataSource { Profiler.leave("AbstractRemoteDataSource.insertRowData"); } + @SuppressWarnings("boxing") private void moveRowFromIndexToIndex(int oldIndex, int newIndex) { T row = indexToRowMap.remove(oldIndex); if (indexToRowMap.containsKey(newIndex)) { diff --git a/server/src/com/vaadin/data/RpcDataProviderExtension.java b/server/src/com/vaadin/data/RpcDataProviderExtension.java index cf2284a62e..62b8214cbd 100644 --- a/server/src/com/vaadin/data/RpcDataProviderExtension.java +++ b/server/src/com/vaadin/data/RpcDataProviderExtension.java @@ -51,6 +51,7 @@ import com.vaadin.ui.Grid; import com.vaadin.ui.Grid.CellReference; import com.vaadin.ui.Grid.CellStyleGenerator; import com.vaadin.ui.Grid.Column; +import com.vaadin.ui.Grid.DetailComponentManager; import com.vaadin.ui.Grid.RowReference; import com.vaadin.ui.Grid.RowStyleGenerator; import com.vaadin.ui.renderers.Renderer; @@ -113,6 +114,7 @@ public class RpcDataProviderExtension extends AbstractExtension { final Object itemId = indexToItemId.get(ii); if (!isPinned(itemId)) { + detailComponentManager.destroyDetails(itemId); itemIdToKey.remove(itemId); indexToItemId.remove(ii); } @@ -154,6 +156,7 @@ public class RpcDataProviderExtension extends AbstractExtension { } indexToItemId.forcePut(index, itemId); + detailComponentManager.createDetails(itemId, index); } index++; } @@ -747,14 +750,18 @@ public class RpcDataProviderExtension extends AbstractExtension { */ private Set visibleDetails = new HashSet(); + private DetailComponentManager detailComponentManager; + /** * Creates a new data provider using the given container. * * @param container * the container to make available */ - public RpcDataProviderExtension(Indexed container) { + public RpcDataProviderExtension(Indexed container, + DetailComponentManager detailComponentManager) { this.container = container; + this.detailComponentManager = detailComponentManager; rpc = getRpcProxy(DataProviderRpc.class); registerRpc(new DataRequestRpc() { @@ -1018,6 +1025,10 @@ public class RpcDataProviderExtension extends AbstractExtension { JsonArray rowArray = Json.createArray(); rowArray.set(0, row); rpc.setRowData(index, rowArray); + + if (isDetailsVisible(itemId)) { + detailComponentManager.createDetails(itemId, index); + } } } @@ -1155,10 +1166,28 @@ public class RpcDataProviderExtension extends AbstractExtension { */ public void setDetailsVisible(Object itemId, boolean visible) { final boolean modified; + if (visible) { modified = visibleDetails.add(itemId); + + /* + * We don't want to create the component here, since the component + * might be out of view, and thus we don't know where the details + * should end up on the client side. This is also a great thing to + * optimize away, so that in case a lot of things would be opened at + * once, a huge chunk of data doesn't get sent over immediately. + */ + } else { modified = visibleDetails.remove(itemId); + + /* + * Here we can try to destroy the component no matter what. The + * component has been removed and should be detached from the + * component hierarchy. The details row will be closed on the client + * side automatically. + */ + detailComponentManager.destroyDetails(itemId); } int rowIndex = keyMapper.getIndex(itemId); diff --git a/server/src/com/vaadin/ui/Grid.java b/server/src/com/vaadin/ui/Grid.java index b56bb0d036..da5cedd999 100644 --- a/server/src/com/vaadin/ui/Grid.java +++ b/server/src/com/vaadin/ui/Grid.java @@ -36,6 +36,8 @@ import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; +import com.google.gwt.thirdparty.guava.common.collect.BiMap; +import com.google.gwt.thirdparty.guava.common.collect.HashBiMap; import com.google.gwt.thirdparty.guava.common.collect.Sets; import com.google.gwt.thirdparty.guava.common.collect.Sets.SetView; import com.vaadin.data.Container; @@ -75,6 +77,7 @@ import com.vaadin.server.KeyMapper; import com.vaadin.server.VaadinSession; import com.vaadin.shared.MouseEventDetails; import com.vaadin.shared.data.sort.SortDirection; +import com.vaadin.shared.ui.grid.ConnectorIndexChange; import com.vaadin.shared.ui.grid.EditorClientRpc; import com.vaadin.shared.ui.grid.EditorServerRpc; import com.vaadin.shared.ui.grid.GridClientRpc; @@ -183,6 +186,11 @@ public class Grid extends AbstractComponent implements SelectionNotifier, /** * This method is called for whenever a new details row needs to be * generated. + *

+ * Note: If a component gets generated, it may not be manually + * attached anywhere, nor may it be a reused instance – each + * invocation of this method should produce a unique and isolated + * component instance. * * @param rowReference * the reference for the row for which to generate details @@ -2809,6 +2817,208 @@ public class Grid extends AbstractComponent implements SelectionNotifier, } } + /** + * A class that makes detail component related internal communication + * possible between {@link RpcDataProviderExtension} and grid. + * + * @since + * @author Vaadin Ltd + */ + public final class DetailComponentManager implements Serializable { + /** + * This map represents all the components that have been requested for + * each item id. + *

+ * Normally this map is consistent with what is displayed in the + * component hierarchy (and thus the DOM). The only time this map is out + * of sync with the DOM is between the any calls to + * {@link #createDetails(Object, int)} or + * {@link #destroyDetails(Object)}, and + * {@link GridClientRpc#setDetailsConnectorChanges(Set)}. + *

+ * This is easily checked: if {@link #unattachedComponents} is + * {@link Collection#isEmpty() empty}, then this field is consistent + * with the connector hierarchy. + */ + private final Map visibleDetailsComponents = new HashMap(); + + /** A lookup map for which row contains which details component. */ + private BiMap rowIndexToDetails = HashBiMap + .create(); + + /** + * A copy of {@link #rowIndexToDetails} from its last stable state. Used + * for creating a diff against {@link #rowIndexToDetails}. + * + * @see #getAndResetConnectorChanges() + */ + private BiMap prevRowIndexToDetails = HashBiMap + .create(); + + /** + * A set keeping track on components that have been created, but not + * attached. They should be attached at some later point in time. + *

+ * This isn't strictly requried, but it's a handy explicit log. You + * could find out the same thing by taking out all the other components + * and checking whether Grid is their parent or not. + */ + private final Set unattachedComponents = new HashSet(); + + /** + * Creates a details component by the request of the client side, with + * the help of the user-defined {@link DetailsGenerator}. + *

+ * Also keeps internal bookkeeping up to date. + * + * @param itemId + * the item id for which to create the details component. + * Assumed not null and that a component is not + * currently present for this item previously + * @param rowIndex + * the row index for {@code itemId} + * @throws IllegalStateException + * if the current details generator provides a component + * that was manually attached, or if the same instance has + * already been provided + */ + public void createDetails(Object itemId, int rowIndex) + throws IllegalStateException { + assert itemId != null : "itemId was null"; + Integer newRowIndex = Integer.valueOf(rowIndex); + + assert !visibleDetailsComponents.containsKey(itemId) : "itemId already has a component. Should be destroyed first."; + + RowReference rowReference = new RowReference(Grid.this); + rowReference.set(itemId); + + Component details = getDetailsGenerator().getDetails(rowReference); + if (details != null) { + + if (details.getParent() != null) { + String generatorName = getDetailsGenerator().getClass() + .getName(); + throw new IllegalStateException(generatorName + + " generated a details component that already " + + "was attached. (itemId: " + itemId + ", row: " + + rowIndex + ", component: " + details); + } + + if (rowIndexToDetails.containsValue(details)) { + String generatorName = getDetailsGenerator().getClass() + .getName(); + throw new IllegalStateException(generatorName + + " provided a details component that already " + + "exists in Grid. (itemId: " + itemId + ", row: " + + rowIndex + ", component: " + details); + } + + visibleDetailsComponents.put(itemId, details); + rowIndexToDetails.put(newRowIndex, details); + unattachedComponents.add(details); + } + + /* + * Don't attach the components here. It's done by + * GridServerRpc.sendDetailsComponents in a separate roundtrip. + */ + } + + /** + * Destroys correctly a details component, by the request of the client + * side. + *

+ * Also keeps internal bookkeeping up to date. + * + * @param itemId + * the item id for which to destroy the details component + */ + public void destroyDetails(Object itemId) { + Component removedComponent = visibleDetailsComponents + .remove(itemId); + if (removedComponent == null) { + return; + } + + rowIndexToDetails.inverse().remove(removedComponent); + + removedComponent.setParent(null); + markAsDirty(); + } + + /** + * Gets all details components that are currently attached to the grid. + *

+ * Used internally by the Grid object. + * + * @return all details components that are currently attached to the + * grid + */ + Collection getComponents() { + Set components = new HashSet( + visibleDetailsComponents.values()); + components.removeAll(unattachedComponents); + return components; + } + + /** + * Gets information on how the connectors have changed. + *

+ * This method only returns the changes that have been made between two + * calls of this method. I.e. Calling this method once will reset the + * state for the next state. + *

+ * Used internally by the Grid object. + * + * @return information on how the connectors have changed + */ + Set getAndResetConnectorChanges() { + Set changes = new HashSet(); + + // populate diff with added/changed + for (Entry entry : rowIndexToDetails.entrySet()) { + Component component = entry.getValue(); + assert component != null : "rowIndexToDetails contains a null component"; + + Integer newIndex = entry.getKey(); + Integer oldIndex = prevRowIndexToDetails.inverse().get( + component); + + /* + * only attach components. Detaching already happened in + * destroyDetails. + */ + if (newIndex != null && oldIndex == null) { + assert unattachedComponents.contains(component) : "unattachedComponents does not contain component for index " + + newIndex + " (" + component + ")"; + component.setParent(Grid.this); + unattachedComponents.remove(component); + } + + if (!SharedUtil.equals(oldIndex, newIndex)) { + changes.add(new ConnectorIndexChange(component, oldIndex, + newIndex)); + } + } + + // populate diff with removed + for (Entry entry : prevRowIndexToDetails + .entrySet()) { + Integer oldIndex = entry.getKey(); + Component component = entry.getValue(); + Integer newIndex = rowIndexToDetails.inverse().get(component); + if (newIndex == null) { + changes.add(new ConnectorIndexChange(null, oldIndex, null)); + } + } + + // reset diff map + prevRowIndexToDetails = HashBiMap.create(rowIndexToDetails); + + return changes; + } + } + /** * The data source attached to the grid */ @@ -2916,8 +3126,15 @@ public class Grid extends AbstractComponent implements SelectionNotifier, private EditorErrorHandler editorErrorHandler = new DefaultEditorErrorHandler(); + /** + * The user-defined details generator. + * + * @see #setDetailsGenerator(DetailsGenerator) + */ private DetailsGenerator detailsGenerator = DetailsGenerator.NULL; + private final DetailComponentManager detailComponentManager = new DetailComponentManager(); + private static final Method SELECTION_CHANGE_METHOD = ReflectTools .findMethod(SelectionListener.class, "select", SelectionEvent.class); @@ -3118,6 +3335,13 @@ public class Grid extends AbstractComponent implements SelectionNotifier, fireEvent(new ItemClickEvent(Grid.this, item, itemId, propertyId, details)); } + + @Override + public void sendDetailsComponents(int fetchId) { + getRpcProxy(GridClientRpc.class).setDetailsConnectorChanges( + detailComponentManager.getAndResetConnectorChanges(), + fetchId); + } }); registerRpc(new EditorServerRpc() { @@ -3278,7 +3502,8 @@ public class Grid extends AbstractComponent implements SelectionNotifier, sortOrder.clear(); } - datasourceExtension = new RpcDataProviderExtension(container); + datasourceExtension = new RpcDataProviderExtension(container, + detailComponentManager); datasourceExtension.extend(this, columnKeys); /* @@ -4607,6 +4832,9 @@ public class Grid extends AbstractComponent implements SelectionNotifier, } componentList.addAll(getEditorFields()); + + componentList.addAll(detailComponentManager.getComponents()); + return componentList.iterator(); } diff --git a/server/tests/src/com/vaadin/tests/server/component/grid/DataProviderExtension.java b/server/tests/src/com/vaadin/tests/server/component/grid/DataProviderExtension.java index 9ecf131c5b..54f5dcdbc7 100644 --- a/server/tests/src/com/vaadin/tests/server/component/grid/DataProviderExtension.java +++ b/server/tests/src/com/vaadin/tests/server/component/grid/DataProviderExtension.java @@ -47,7 +47,7 @@ public class DataProviderExtension { container = new IndexedContainer(); populate(container); - dataProvider = new RpcDataProviderExtension(container); + dataProvider = new RpcDataProviderExtension(container, null); keyMapper = dataProvider.getKeyMapper(); } diff --git a/shared/src/com/vaadin/shared/ui/grid/ConnectorIndexChange.java b/shared/src/com/vaadin/shared/ui/grid/ConnectorIndexChange.java new file mode 100644 index 0000000000..16be92007e --- /dev/null +++ b/shared/src/com/vaadin/shared/ui/grid/ConnectorIndexChange.java @@ -0,0 +1,143 @@ +/* + * 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.shared.ui.grid; + +import java.io.Serializable; + +import com.vaadin.shared.Connector; + +/** + * A description of an indexing modification for a connector. This is used by + * Grid by internal bookkeeping updates. + * + * @since + * @author Vaadin Ltd + */ +public class ConnectorIndexChange implements Serializable { + + private Connector connector; + private Integer oldIndex; + private Integer newIndex; + + /** Create a new connector index change */ + public ConnectorIndexChange() { + } + + /** + * Convenience constructor for setting all the fields in one line. + *

+ * Calling this constructor will also assert that the state of the pojo is + * consistent by internal assumptions. + * + * @param connector + * the changed connector + * @param oldIndex + * the old index + * @param newIndex + * the new index + */ + public ConnectorIndexChange(Connector connector, Integer oldIndex, + Integer newIndex) { + this.connector = connector; + this.oldIndex = oldIndex; + this.newIndex = newIndex; + + assert assertStateIsOk(); + } + + private boolean assertStateIsOk() { + assert (connector != null && newIndex != null) + || (connector == null && oldIndex != null && newIndex == null) : "connector: " + + nullityString(connector) + + ", oldIndex: " + + nullityString(oldIndex) + + ", newIndex: " + + nullityString(newIndex); + return true; + } + + private static String nullityString(Object object) { + return object == null ? "null" : "non-null"; + } + + /** + * Gets the old index for the connector. + *

+ * If null, the connector is recently added. This means that + * {@link #getConnector()} is expected not to return null. + * + * @return the old index for the connector + */ + public Integer getOldIndex() { + assert assertStateIsOk(); + return oldIndex; + } + + /** + * Gets the new index for the connector. + *

+ * If null, the connector should be removed. This means that + * {@link #getConnector()} is expected to return null as well. + * + * @return the new index for the connector + */ + public Integer getNewIndex() { + assert assertStateIsOk(); + return newIndex; + } + + /** + * Gets the changed connector. + * + * @return the changed connector. Might be null + */ + public Connector getConnector() { + assert assertStateIsOk(); + return connector; + } + + /** + * Sets the changed connector. + * + * @param connector + * the changed connector. May be null + */ + public void setConnector(Connector connector) { + this.connector = connector; + } + + /** + * Sets the old index + * + * @param oldIndex + * the old index. May be null if a new connector is + * being inserted + */ + public void setOldIndex(Integer oldIndex) { + this.oldIndex = oldIndex; + } + + /** + * Sets the new index + * + * @param newIndex + * the new index. May be null if a connector is + * being removed + */ + public void setNewIndex(Integer newIndex) { + this.newIndex = newIndex; + } +} diff --git a/shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java b/shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java index 4ba081b5df..672c83ff53 100644 --- a/shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java +++ b/shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java @@ -15,6 +15,8 @@ */ package com.vaadin.shared.ui.grid; +import java.util.Set; + import com.vaadin.shared.communication.ClientRpc; /** @@ -55,4 +57,17 @@ public interface GridClientRpc extends ClientRpc { */ public void recalculateColumnWidths(); + /** + * Informs the GridConnector on how the indexing of details connectors has + * changed. + * + * @since + * @param connectorChanges + * the indexing changes of details connectors + * @param fetchId + * the id of the request for fetching the changes + */ + public void setDetailsConnectorChanges( + Set connectorChanges, int fetchId); + } diff --git a/shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java b/shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java index c90a016383..a2ef7d0bb7 100644 --- a/shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java +++ b/shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java @@ -47,4 +47,21 @@ public interface GridServerRpc extends ServerRpc { * mouse event details */ void itemClick(String rowKey, String columnId, MouseEventDetails details); + + /** + * This is a trigger for Grid to send whatever has changed regarding the + * details components. + *

+ * The components can't be sent eagerly, since they are generated as a side + * effect in + * {@link com.vaadin.data.RpcDataProviderExtension#beforeClientResponse(boolean)} + * , and that is too late to change the hierarchy. So we need this + * round-trip to work around that limitation. + * + * @since + * @param fetchId + * an unique identifier for the request + * @see com.vaadin.ui.Grid#setDetailsVisible(Object, boolean) + */ + void sendDetailsComponents(int fetchId); } diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java index f0c4b3d9c0..08f0d7d5d2 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java @@ -46,6 +46,7 @@ import com.vaadin.tests.components.AbstractComponentTest; import com.vaadin.ui.Button; import com.vaadin.ui.Button.ClickEvent; import com.vaadin.ui.Button.ClickListener; +import com.vaadin.ui.Component; import com.vaadin.ui.Grid; import com.vaadin.ui.Grid.CellReference; import com.vaadin.ui.Grid.CellStyleGenerator; @@ -58,6 +59,8 @@ import com.vaadin.ui.Grid.RowReference; import com.vaadin.ui.Grid.RowStyleGenerator; import com.vaadin.ui.Grid.SelectionMode; import com.vaadin.ui.Grid.SelectionModel; +import com.vaadin.ui.Label; +import com.vaadin.ui.Panel; import com.vaadin.ui.renderers.DateRenderer; import com.vaadin.ui.renderers.HtmlRenderer; import com.vaadin.ui.renderers.NumberRenderer; @@ -109,6 +112,8 @@ public class GridBasicFeatures extends AbstractComponentTest { } }; + private Panel detailsPanel; + @Override @SuppressWarnings("unchecked") protected Grid constructComponent() { @@ -1054,6 +1059,64 @@ public class GridBasicFeatures extends AbstractComponentTest { } private void createDetailsActions() { + createClickAction("custom details generator", "Details", + new Command() { + @Override + public void execute(Grid c, Void value, Object data) { + grid.setDetailsGenerator(new Grid.DetailsGenerator() { + private int seq = 0; + + @Override + public Component getDetails( + RowReference rowReference) { + return new Label("You are watching item id " + + rowReference.getItemId() + " (" + + (seq++) + ")"); + } + }); + } + }, null); + createClickAction("hierarchy details generator", "Details", + new Command() { + @Override + public void execute(Grid c, Void value, Object data) { + grid.setDetailsGenerator(new Grid.DetailsGenerator() { + @Override + public Component getDetails( + RowReference rowReference) { + detailsPanel = new Panel(); + detailsPanel.setContent(new Label("One")); + return detailsPanel; + } + }); + } + }, null); + + createClickAction("change hierarchy in generator", "Details", + new Command() { + @Override + public void execute(Grid c, Void value, Object data) { + Label label = (Label) detailsPanel.getContent(); + if (label.getValue().equals("One")) { + detailsPanel.setContent(new Label("Two")); + } else { + detailsPanel.setContent(new Label("One")); + } + } + }, null); + + createClickAction("toggle firstItemId", "Details", + new Command() { + @Override + public void execute(Grid g, Void value, Object data) { + Object firstItemId = g.getContainerDataSource() + .firstItemId(); + boolean toggle = g.isDetailsVisible(firstItemId); + g.setDetailsVisible(firstItemId, !toggle); + g.setDetailsVisible(firstItemId, toggle); + } + }, null); + createBooleanAction("firstItemId", "Details", false, new Command() { @Override @@ -1063,6 +1126,17 @@ public class GridBasicFeatures extends AbstractComponentTest { .firstItemId(), visible); } }); + + createBooleanAction("lastItemId-5", "Details", false, + new Command() { + @Override + @SuppressWarnings("boxing") + public void execute(Grid g, Boolean visible, Object data) { + Object fifthLastItemId = g.getContainerDataSource() + .getItemIds(ROWS - 6, 1).get(0); + g.setDetailsVisible(fifthLastItemId, visible); + } + }); } @Override diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridDetailsServerTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridDetailsServerTest.java index 01d2ba55eb..e9e32cb1ca 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridDetailsServerTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridDetailsServerTest.java @@ -15,21 +15,43 @@ */ package com.vaadin.tests.components.grid.basicfeatures.server; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; +import org.openqa.selenium.By; import org.openqa.selenium.NoSuchElementException; -import com.vaadin.testbench.annotations.RunLocally; -import com.vaadin.testbench.parallel.Browser; +import com.vaadin.testbench.TestBenchElement; +import com.vaadin.tests.components.grid.basicfeatures.GridBasicFeatures; import com.vaadin.tests.components.grid.basicfeatures.GridBasicFeaturesTest; -@RunLocally(Browser.PHANTOMJS) public class GridDetailsServerTest extends GridBasicFeaturesTest { + /** + * The reason to why last item details wasn't selected is that since it will + * exist only after the viewport has been scrolled into view, we wouldn't be + * able to scroll that particular details row into view, making tests + * awkward with two scroll commands back to back. + */ + private static final int ALMOST_LAST_ITEM_INDEX = GridBasicFeatures.ROWS - 5; + private static final String[] ALMOST_LAST_ITEM_DETAILS = new String[] { + "Component", "Details", "lastItemId-5" }; + private static final String[] FIRST_ITEM_DETAILS = new String[] { "Component", "Details", "firstItemId" }; + private static final String[] TOGGLE_FIRST_ITEM_DETAILS = new String[] { + "Component", "Details", "toggle firstItemId" }; + private static final String[] CUSTOM_DETAILS_GENERATOR = new String[] { + "Component", "Details", "custom details generator" }; + private static final String[] HIERARCHY_DETAILS_GENERATOR = new String[] { + "Component", "Details", "hierarchy details generator" }; + private static final String[] CHANGE_HIERARCHY = new String[] { + "Component", "Details", "change hierarchy in generator" }; @Before public void setUp() { @@ -53,7 +75,9 @@ public class GridDetailsServerTest extends GridBasicFeaturesTest { public void closeVisibleDetails() { selectMenuPath(FIRST_ITEM_DETAILS); selectMenuPath(FIRST_ITEM_DETAILS); - getGridElement().getDetails(0); + + // this will throw before assertNull + assertNull(getGridElement().getDetails(0)); } @Test @@ -73,4 +97,72 @@ public class GridDetailsServerTest extends GridBasicFeaturesTest { getGridElement().scroll(0); getGridElement().getDetails(0); } + + @Test + public void componentIsVisibleClientSide() { + selectMenuPath(CUSTOM_DETAILS_GENERATOR); + selectMenuPath(FIRST_ITEM_DETAILS); + + TestBenchElement details = getGridElement().getDetails(0); + assertNotNull("No widget detected inside details", + details.findElement(By.className("v-widget"))); + } + + @Test + public void togglingAVisibleDetailsRowWithSeparateRoundtrips() { + selectMenuPath(CUSTOM_DETAILS_GENERATOR); + selectMenuPath(FIRST_ITEM_DETAILS); // open + selectMenuPath(FIRST_ITEM_DETAILS); // close + selectMenuPath(FIRST_ITEM_DETAILS); // open + + TestBenchElement details = getGridElement().getDetails(0); + assertNotNull("No widget detected inside details", + details.findElement(By.className("v-widget"))); + } + + @Test + public void togglingAVisibleDetailsRowWithOneRoundtrip() { + selectMenuPath(CUSTOM_DETAILS_GENERATOR); + selectMenuPath(FIRST_ITEM_DETAILS); // open + + assertTrue("Unexpected generator content", + getGridElement().getDetails(0).getText().endsWith("(0)")); + selectMenuPath(TOGGLE_FIRST_ITEM_DETAILS); + assertTrue("New component was not displayed in the client", + getGridElement().getDetails(0).getText().endsWith("(1)")); + } + + @Test + @Ignore("This will be patched with https://dev.vaadin.com/review/#/c/7917/") + public void almosLastItemIdIsRendered() { + selectMenuPath(CUSTOM_DETAILS_GENERATOR); + selectMenuPath(ALMOST_LAST_ITEM_DETAILS); + scrollGridVerticallyTo(100000); + + TestBenchElement details = getGridElement().getDetails( + ALMOST_LAST_ITEM_INDEX); + assertNotNull(details); + assertTrue("Unexpected details content", + details.getText().endsWith(ALMOST_LAST_ITEM_INDEX + " (0)")); + } + + @Test + public void hierarchyChangesWorkInDetails() { + selectMenuPath(HIERARCHY_DETAILS_GENERATOR); + selectMenuPath(FIRST_ITEM_DETAILS); + assertEquals("One", getGridElement().getDetails(0).getText()); + selectMenuPath(CHANGE_HIERARCHY); + assertEquals("Two", getGridElement().getDetails(0).getText()); + } + + @Test + @Ignore("This will be patched with https://dev.vaadin.com/review/#/c/7917/") + public void hierarchyChangesWorkInDetailsWhileOutOfView() { + selectMenuPath(HIERARCHY_DETAILS_GENERATOR); + selectMenuPath(FIRST_ITEM_DETAILS); + scrollGridVerticallyTo(10000); + selectMenuPath(CHANGE_HIERARCHY); + scrollGridVerticallyTo(0); + assertEquals("Two", getGridElement().getDetails(0).getText()); + } } -- cgit v1.2.3 From 5c2da23e72e17d04e3cafc67ff1166dc313b9712 Mon Sep 17 00:00:00 2001 From: Henrik Paul Date: Wed, 18 Mar 2015 10:16:47 +0200 Subject: Fixes a bug when scrolling a Grid with details open (#16644) If the a row with an open details row was pushed out of the active row range, the component would be removed from the connector hierarchy on the server side but not on the client side. Vaadin gave a warning for this. This patch makes sure that the widget is properly deregistered when it gets outside of the active range pre-emptively. Change-Id: I2145e82a990ded31e4426e85e59edad9d4d4194f --- .../vaadin/client/connectors/GridConnector.java | 40 ++++++++++++++-------- .../client/connectors/RpcDataSourceConnector.java | 15 +++++++- .../client/data/AbstractRemoteDataSource.java | 14 ++++++++ 3 files changed, 53 insertions(+), 16 deletions(-) (limited to 'client/src') diff --git a/client/src/com/vaadin/client/connectors/GridConnector.java b/client/src/com/vaadin/client/connectors/GridConnector.java index 70ad2504d8..1787dc5c97 100644 --- a/client/src/com/vaadin/client/connectors/GridConnector.java +++ b/client/src/com/vaadin/client/connectors/GridConnector.java @@ -29,7 +29,6 @@ import java.util.Set; import java.util.logging.Logger; import com.google.gwt.core.client.Scheduler; -import com.google.gwt.core.client.Scheduler.RepeatingCommand; import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.user.client.Timer; @@ -40,6 +39,7 @@ import com.vaadin.client.DeferredWorker; import com.vaadin.client.MouseEventDetailsBuilder; import com.vaadin.client.annotations.OnStateChange; import com.vaadin.client.communication.StateChangeEvent; +import com.vaadin.client.connectors.RpcDataSourceConnector.DetailsListener; import com.vaadin.client.connectors.RpcDataSourceConnector.RpcDataSource; import com.vaadin.client.data.DataSource.RowHandle; import com.vaadin.client.renderers.Renderer; @@ -107,8 +107,7 @@ import elemental.json.JsonValue; */ @Connect(com.vaadin.ui.Grid.class) public class GridConnector extends AbstractHasComponentsConnector implements - SimpleManagedLayout, RpcDataSourceConnector.DetailsListener, - DeferredWorker { + SimpleManagedLayout, DeferredWorker { private static final class CustomCellStyleGenerator implements CellStyleGenerator { @@ -534,6 +533,25 @@ public class GridConnector extends AbstractHasComponentsConnector implements private final DetailsConnectorFetcher detailsConnectorFetcher = new DetailsConnectorFetcher(); + private final DetailsListener detailsListener = new DetailsListener() { + @Override + public void reapplyDetailsVisibility(int rowIndex, JsonObject row) { + if (row.hasKey(GridState.JSONKEY_DETAILS_VISIBLE) + && row.getBoolean(GridState.JSONKEY_DETAILS_VISIBLE)) { + getWidget().setDetailsVisible(rowIndex, true); + } else { + getWidget().setDetailsVisible(rowIndex, false); + } + + detailsConnectorFetcher.schedule(); + } + + @Override + public void closeDetails(int rowIndex) { + getWidget().setDetailsVisible(rowIndex, false); + } + }; + @Override @SuppressWarnings("unchecked") public Grid getWidget() { @@ -1144,20 +1162,12 @@ public class GridConnector extends AbstractHasComponentsConnector implements getWidget().onResize(); } - @Override - public void reapplyDetailsVisibility(int rowIndex, JsonObject row) { - if (row.hasKey(GridState.JSONKEY_DETAILS_VISIBLE) - && row.getBoolean(GridState.JSONKEY_DETAILS_VISIBLE)) { - getWidget().setDetailsVisible(rowIndex, true); - } else { - getWidget().setDetailsVisible(rowIndex, false); - } - - detailsConnectorFetcher.schedule(); - } - @Override public boolean isWorkPending() { return detailsConnectorFetcher.isWorkPending(); } + + public DetailsListener getDetailsListener() { + return detailsListener; + } } diff --git a/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java b/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java index ae4249de78..e8c7ee5286 100644 --- a/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java +++ b/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java @@ -64,6 +64,14 @@ public class RpcDataSourceConnector extends AbstractExtensionConnector { * @see GridState#JSONKEY_DETAILS_VISIBLE */ void reapplyDetailsVisibility(int rowIndex, JsonObject row); + + /** + * Closes details for a row. + * + * @param rowIndex + * the index of the row for which to close details + */ + void closeDetails(int rowIndex); } public class RpcDataSource extends AbstractRemoteDataSource { @@ -213,6 +221,11 @@ public class RpcDataSourceConnector extends AbstractExtensionConnector { rowData.get(i)); } } + + @Override + protected void onDropFromCache(int rowIndex) { + detailsListener.closeDetails(rowIndex); + } } private final RpcDataSource dataSource = new RpcDataSource(); @@ -220,7 +233,7 @@ public class RpcDataSourceConnector extends AbstractExtensionConnector { @Override protected void extend(ServerConnector target) { GridConnector gridConnector = (GridConnector) target; - dataSource.setDetailsListener(gridConnector); + dataSource.setDetailsListener(gridConnector.getDetailsListener()); gridConnector.setDataSource(dataSource); } } diff --git a/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java b/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java index 0ac4c33c83..152b66f2ca 100644 --- a/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java +++ b/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java @@ -332,9 +332,23 @@ public abstract class AbstractRemoteDataSource implements DataSource { for (int i = range.getStart(); i < range.getEnd(); i++) { T removed = indexToRowMap.remove(Integer.valueOf(i)); keyToIndexMap.remove(getRowKey(removed)); + + onDropFromCache(i); } } + /** + * A hook that can be overridden to do something whenever a row is dropped + * from the cache. + * + * @since + * @param rowIndex + * the index of the dropped row + */ + protected void onDropFromCache(int rowIndex) { + // noop + } + private void handleMissingRows(Range range) { if (range.isEmpty()) { return; -- cgit v1.2.3 From b06b1d68469e49e7784de342f0dcf9de64b35f5a Mon Sep 17 00:00:00 2001 From: Henrik Paul Date: Mon, 16 Mar 2015 11:45:22 +0200 Subject: Adds details generator swap support for Grid (#16644) Change-Id: I741970a7bcebd27d3aa28d608d767b4b4f063ae8 --- .../vaadin/client/connectors/GridConnector.java | 32 ++-- client/src/com/vaadin/client/widgets/Grid.java | 5 +- .../com/vaadin/data/RpcDataProviderExtension.java | 7 + server/src/com/vaadin/ui/Grid.java | 61 ++++++-- .../shared/ui/grid/ConnectorIndexChange.java | 143 ------------------ .../shared/ui/grid/DetailsConnectorChange.java | 147 +++++++++++++++++++ .../com/vaadin/shared/ui/grid/GridClientRpc.java | 5 +- .../grid/basicfeatures/GridBasicFeatures.java | 131 ++++++++++------- .../server/GridDetailsServerTest.java | 162 +++++++++++++++------ 9 files changed, 429 insertions(+), 264 deletions(-) delete mode 100644 shared/src/com/vaadin/shared/ui/grid/ConnectorIndexChange.java create mode 100644 shared/src/com/vaadin/shared/ui/grid/DetailsConnectorChange.java (limited to 'client/src') diff --git a/client/src/com/vaadin/client/connectors/GridConnector.java b/client/src/com/vaadin/client/connectors/GridConnector.java index 1787dc5c97..e6b9c89483 100644 --- a/client/src/com/vaadin/client/connectors/GridConnector.java +++ b/client/src/com/vaadin/client/connectors/GridConnector.java @@ -77,7 +77,7 @@ import com.vaadin.client.widgets.Grid.HeaderRow; import com.vaadin.shared.Connector; import com.vaadin.shared.data.sort.SortDirection; import com.vaadin.shared.ui.Connect; -import com.vaadin.shared.ui.grid.ConnectorIndexChange; +import com.vaadin.shared.ui.grid.DetailsConnectorChange; import com.vaadin.shared.ui.grid.EditorClientRpc; import com.vaadin.shared.ui.grid.EditorServerRpc; import com.vaadin.shared.ui.grid.GridClientRpc; @@ -382,7 +382,8 @@ public class GridConnector extends AbstractHasComponentsConnector implements } } - public void setDetailsConnectorChanges(Set changes) { + public void setDetailsConnectorChanges( + Set changes) { /* * To avoid overwriting connectors while moving them about, we'll * take all the affected connectors, first all remove those that are @@ -390,7 +391,7 @@ public class GridConnector extends AbstractHasComponentsConnector implements */ /* Remove moved/removed connectors from bookkeeping */ - for (ConnectorIndexChange change : changes) { + for (DetailsConnectorChange change : changes) { Integer oldIndex = change.getOldIndex(); Connector removedConnector = indexToDetailsMap.remove(oldIndex); @@ -402,7 +403,7 @@ public class GridConnector extends AbstractHasComponentsConnector implements } /* Add moved/added connectors to bookkeeping */ - for (ConnectorIndexChange change : changes) { + for (DetailsConnectorChange change : changes) { Integer newIndex = change.getNewIndex(); ComponentConnector connector = (ComponentConnector) change .getConnector(); @@ -456,8 +457,11 @@ public class GridConnector extends AbstractHasComponentsConnector implements } public void responseReceived(int fetchId) { - boolean success = pendingFetches.remove(fetchId); - assert success : "Received a response with an unidentified fetch id"; + /* Ignore negative fetchIds (they're pushed, not fetched) */ + if (fetchId >= 0) { + boolean success = pendingFetches.remove(fetchId); + assert success : "Received a response with an unidentified fetch id"; + } } @Override @@ -607,18 +611,20 @@ public class GridConnector extends AbstractHasComponentsConnector implements @Override public void setDetailsConnectorChanges( - Set connectorChanges, int fetchId) { + Set connectorChanges, int fetchId) { customDetailsGenerator .setDetailsConnectorChanges(connectorChanges); // refresh moved/added details rows - for (ConnectorIndexChange change : connectorChanges) { - Integer newIndex = change.getNewIndex(); - if (newIndex != null) { - int index = newIndex.intValue(); - getWidget().setDetailsVisible(index, false); - getWidget().setDetailsVisible(index, true); + for (DetailsConnectorChange change : connectorChanges) { + Integer index = change.getNewIndex(); + if (index == null) { + index = change.getOldIndex(); } + + int i = index.intValue(); + getWidget().setDetailsVisible(i, false); + getWidget().setDetailsVisible(i, true); } detailsConnectorFetcher.responseReceived(fetchId); } diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index f4aaf798b7..8243782c4e 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -6387,12 +6387,13 @@ public class Grid extends ResizeComposite implements * see GridSpacerUpdater.init for implementation details. */ - if (visible && !isDetailsVisible(rowIndex)) { + boolean isVisible = isDetailsVisible(rowIndex); + if (visible && !isVisible) { escalator.getBody().setSpacer(rowIndex, DETAILS_ROW_INITIAL_HEIGHT); visibleDetails.add(rowIndexInteger); } - else if (!visible && isDetailsVisible(rowIndex)) { + else if (!visible && isVisible) { escalator.getBody().setSpacer(rowIndex, -1); visibleDetails.remove(rowIndexInteger); } diff --git a/server/src/com/vaadin/data/RpcDataProviderExtension.java b/server/src/com/vaadin/data/RpcDataProviderExtension.java index 620933c379..97d141cd6e 100644 --- a/server/src/com/vaadin/data/RpcDataProviderExtension.java +++ b/server/src/com/vaadin/data/RpcDataProviderExtension.java @@ -30,6 +30,7 @@ import java.util.logging.Logger; import com.google.gwt.thirdparty.guava.common.collect.BiMap; import com.google.gwt.thirdparty.guava.common.collect.HashBiMap; +import com.google.gwt.thirdparty.guava.common.collect.ImmutableSet; import com.vaadin.data.Container.Indexed; import com.vaadin.data.Container.Indexed.ItemAddEvent; import com.vaadin.data.Container.Indexed.ItemRemoveEvent; @@ -1214,4 +1215,10 @@ public class RpcDataProviderExtension extends AbstractExtension { public boolean isDetailsVisible(Object itemId) { return visibleDetails.contains(itemId); } + + public void refreshDetails() { + for (Object itemId : ImmutableSet.copyOf(visibleDetails)) { + detailComponentManager.refresh(itemId); + } + } } diff --git a/server/src/com/vaadin/ui/Grid.java b/server/src/com/vaadin/ui/Grid.java index da5cedd999..ec1dd45536 100644 --- a/server/src/com/vaadin/ui/Grid.java +++ b/server/src/com/vaadin/ui/Grid.java @@ -38,6 +38,7 @@ import java.util.logging.Logger; import com.google.gwt.thirdparty.guava.common.collect.BiMap; import com.google.gwt.thirdparty.guava.common.collect.HashBiMap; +import com.google.gwt.thirdparty.guava.common.collect.Maps; import com.google.gwt.thirdparty.guava.common.collect.Sets; import com.google.gwt.thirdparty.guava.common.collect.Sets.SetView; import com.vaadin.data.Container; @@ -77,7 +78,7 @@ import com.vaadin.server.KeyMapper; import com.vaadin.server.VaadinSession; import com.vaadin.shared.MouseEventDetails; import com.vaadin.shared.data.sort.SortDirection; -import com.vaadin.shared.ui.grid.ConnectorIndexChange; +import com.vaadin.shared.ui.grid.DetailsConnectorChange; import com.vaadin.shared.ui.grid.EditorClientRpc; import com.vaadin.shared.ui.grid.EditorServerRpc; import com.vaadin.shared.ui.grid.GridClientRpc; @@ -2840,7 +2841,8 @@ public class Grid extends AbstractComponent implements SelectionNotifier, * {@link Collection#isEmpty() empty}, then this field is consistent * with the connector hierarchy. */ - private final Map visibleDetailsComponents = new HashMap(); + private final Map visibleDetailsComponents = Maps + .newHashMap(); /** A lookup map for which row contains which details component. */ private BiMap rowIndexToDetails = HashBiMap @@ -2863,7 +2865,13 @@ public class Grid extends AbstractComponent implements SelectionNotifier, * could find out the same thing by taking out all the other components * and checking whether Grid is their parent or not. */ - private final Set unattachedComponents = new HashSet(); + private final Set unattachedComponents = Sets.newHashSet(); + + /** + * Keeps tabs on all the details that did not get a component during + * {@link #createDetails(Object, int)}. + */ + private final Map emptyDetails = Maps.newHashMap(); /** * Creates a details component by the request of the client side, with @@ -2887,14 +2895,14 @@ public class Grid extends AbstractComponent implements SelectionNotifier, assert itemId != null : "itemId was null"; Integer newRowIndex = Integer.valueOf(rowIndex); - assert !visibleDetailsComponents.containsKey(itemId) : "itemId already has a component. Should be destroyed first."; + assert !visibleDetailsComponents.containsKey(itemId) : "itemId " + + "already has a component. Should be destroyed first."; RowReference rowReference = new RowReference(Grid.this); rowReference.set(itemId); Component details = getDetailsGenerator().getDetails(rowReference); if (details != null) { - if (details.getParent() != null) { String generatorName = getDetailsGenerator().getClass() .getName(); @@ -2916,6 +2924,20 @@ public class Grid extends AbstractComponent implements SelectionNotifier, visibleDetailsComponents.put(itemId, details); rowIndexToDetails.put(newRowIndex, details); unattachedComponents.add(details); + + assert !emptyDetails.containsKey(itemId) : "Bookeeping thinks " + + "itemId is empty even though we just created a " + + "component for it (" + itemId + ")"; + } else { + assert !emptyDetails.containsKey(itemId) : "Bookkeeping has " + + "already itemId marked as empty (itemId: " + itemId + + ", old index: " + emptyDetails.get(itemId) + + ", new index: " + newRowIndex + ")"; + assert !emptyDetails.containsValue(newRowIndex) : "Bookkeeping" + + " already had another itemId for this empty index " + + "(index: " + newRowIndex + ", new itemId: " + itemId + + ")"; + emptyDetails.put(itemId, newRowIndex); } /* @@ -2934,6 +2956,8 @@ public class Grid extends AbstractComponent implements SelectionNotifier, * the item id for which to destroy the details component */ public void destroyDetails(Object itemId) { + emptyDetails.remove(itemId); + Component removedComponent = visibleDetailsComponents .remove(itemId); if (removedComponent == null) { @@ -2972,8 +2996,8 @@ public class Grid extends AbstractComponent implements SelectionNotifier, * * @return information on how the connectors have changed */ - Set getAndResetConnectorChanges() { - Set changes = new HashSet(); + Set getAndResetConnectorChanges() { + Set changes = new HashSet(); // populate diff with added/changed for (Entry entry : rowIndexToDetails.entrySet()) { @@ -2996,7 +3020,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier, } if (!SharedUtil.equals(oldIndex, newIndex)) { - changes.add(new ConnectorIndexChange(component, oldIndex, + changes.add(new DetailsConnectorChange(component, oldIndex, newIndex)); } } @@ -3008,7 +3032,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier, Component component = entry.getValue(); Integer newIndex = rowIndexToDetails.inverse().get(component); if (newIndex == null) { - changes.add(new ConnectorIndexChange(null, oldIndex, null)); + changes.add(new DetailsConnectorChange(null, oldIndex, null)); } } @@ -3017,6 +3041,21 @@ public class Grid extends AbstractComponent implements SelectionNotifier, return changes; } + + public void refresh(Object itemId) { + Component component = visibleDetailsComponents.get(itemId); + Integer rowIndex = null; + if (component != null) { + rowIndex = rowIndexToDetails.inverse().get(component); + destroyDetails(itemId); + } else { + rowIndex = emptyDetails.remove(itemId); + } + + assert rowIndex != null : "Given itemId does not map to an existing detail row (" + + itemId + ")"; + createDetails(itemId, rowIndex.intValue()); + } } /** @@ -5374,7 +5413,9 @@ public class Grid extends AbstractComponent implements SelectionNotifier, this.detailsGenerator = detailsGenerator; - getLogger().warning("[[details]] update details on generator swap"); + datasourceExtension.refreshDetails(); + getRpcProxy(GridClientRpc.class).setDetailsConnectorChanges( + detailComponentManager.getAndResetConnectorChanges(), -1); } /** diff --git a/shared/src/com/vaadin/shared/ui/grid/ConnectorIndexChange.java b/shared/src/com/vaadin/shared/ui/grid/ConnectorIndexChange.java deleted file mode 100644 index 16be92007e..0000000000 --- a/shared/src/com/vaadin/shared/ui/grid/ConnectorIndexChange.java +++ /dev/null @@ -1,143 +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.shared.ui.grid; - -import java.io.Serializable; - -import com.vaadin.shared.Connector; - -/** - * A description of an indexing modification for a connector. This is used by - * Grid by internal bookkeeping updates. - * - * @since - * @author Vaadin Ltd - */ -public class ConnectorIndexChange implements Serializable { - - private Connector connector; - private Integer oldIndex; - private Integer newIndex; - - /** Create a new connector index change */ - public ConnectorIndexChange() { - } - - /** - * Convenience constructor for setting all the fields in one line. - *

- * Calling this constructor will also assert that the state of the pojo is - * consistent by internal assumptions. - * - * @param connector - * the changed connector - * @param oldIndex - * the old index - * @param newIndex - * the new index - */ - public ConnectorIndexChange(Connector connector, Integer oldIndex, - Integer newIndex) { - this.connector = connector; - this.oldIndex = oldIndex; - this.newIndex = newIndex; - - assert assertStateIsOk(); - } - - private boolean assertStateIsOk() { - assert (connector != null && newIndex != null) - || (connector == null && oldIndex != null && newIndex == null) : "connector: " - + nullityString(connector) - + ", oldIndex: " - + nullityString(oldIndex) - + ", newIndex: " - + nullityString(newIndex); - return true; - } - - private static String nullityString(Object object) { - return object == null ? "null" : "non-null"; - } - - /** - * Gets the old index for the connector. - *

- * If null, the connector is recently added. This means that - * {@link #getConnector()} is expected not to return null. - * - * @return the old index for the connector - */ - public Integer getOldIndex() { - assert assertStateIsOk(); - return oldIndex; - } - - /** - * Gets the new index for the connector. - *

- * If null, the connector should be removed. This means that - * {@link #getConnector()} is expected to return null as well. - * - * @return the new index for the connector - */ - public Integer getNewIndex() { - assert assertStateIsOk(); - return newIndex; - } - - /** - * Gets the changed connector. - * - * @return the changed connector. Might be null - */ - public Connector getConnector() { - assert assertStateIsOk(); - return connector; - } - - /** - * Sets the changed connector. - * - * @param connector - * the changed connector. May be null - */ - public void setConnector(Connector connector) { - this.connector = connector; - } - - /** - * Sets the old index - * - * @param oldIndex - * the old index. May be null if a new connector is - * being inserted - */ - public void setOldIndex(Integer oldIndex) { - this.oldIndex = oldIndex; - } - - /** - * Sets the new index - * - * @param newIndex - * the new index. May be null if a connector is - * being removed - */ - public void setNewIndex(Integer newIndex) { - this.newIndex = newIndex; - } -} diff --git a/shared/src/com/vaadin/shared/ui/grid/DetailsConnectorChange.java b/shared/src/com/vaadin/shared/ui/grid/DetailsConnectorChange.java new file mode 100644 index 0000000000..40f4541fb1 --- /dev/null +++ b/shared/src/com/vaadin/shared/ui/grid/DetailsConnectorChange.java @@ -0,0 +1,147 @@ +/* + * 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.shared.ui.grid; + +import java.io.Serializable; + +import com.vaadin.shared.Connector; + +/** + * A description of an indexing modification for a connector. This is used by + * Grid by internal bookkeeping updates. + * + * @since + * @author Vaadin Ltd + */ +public class DetailsConnectorChange implements Serializable { + + private Connector connector; + private Integer oldIndex; + private Integer newIndex; + + /** Create a new connector index change */ + public DetailsConnectorChange() { + } + + /** + * Convenience constructor for setting all the fields in one line. + *

+ * Calling this constructor will also assert that the state of the pojo is + * consistent by internal assumptions. + * + * @param connector + * the changed connector + * @param oldIndex + * the old index + * @param newIndex + * the new index + */ + public DetailsConnectorChange(Connector connector, Integer oldIndex, + Integer newIndex) { + this.connector = connector; + this.oldIndex = oldIndex; + this.newIndex = newIndex; + + assert assertStateIsOk(); + } + + private boolean assertStateIsOk() { + boolean connectorAndNewIndexIsNotNull = connector != null + && newIndex != null; + boolean connectorAndNewIndexIsNullThenOldIndexIsSet = connector == null + && newIndex == null && oldIndex != null; + + assert (connectorAndNewIndexIsNotNull || connectorAndNewIndexIsNullThenOldIndexIsSet) : "connector: " + + nullityString(connector) + + ", oldIndex: " + + nullityString(oldIndex) + + ", newIndex: " + + nullityString(newIndex); + return true; + } + + private static String nullityString(Object object) { + return object == null ? "null" : "non-null"; + } + + /** + * Gets the old index for the connector. + *

+ * If null, the connector is recently added. This means that + * {@link #getConnector()} is expected not to return null. + * + * @return the old index for the connector + */ + public Integer getOldIndex() { + assert assertStateIsOk(); + return oldIndex; + } + + /** + * Gets the new index for the connector. + *

+ * If null, the connector should be removed. This means that + * {@link #getConnector()} is expected to return null as well. + * + * @return the new index for the connector + */ + public Integer getNewIndex() { + assert assertStateIsOk(); + return newIndex; + } + + /** + * Gets the changed connector. + * + * @return the changed connector. Might be null + */ + public Connector getConnector() { + assert assertStateIsOk(); + return connector; + } + + /** + * Sets the changed connector. + * + * @param connector + * the changed connector. May be null + */ + public void setConnector(Connector connector) { + this.connector = connector; + } + + /** + * Sets the old index + * + * @param oldIndex + * the old index. May be null if a new connector is + * being inserted + */ + public void setOldIndex(Integer oldIndex) { + this.oldIndex = oldIndex; + } + + /** + * Sets the new index + * + * @param newIndex + * the new index. May be null if a connector is + * being removed + */ + public void setNewIndex(Integer newIndex) { + this.newIndex = newIndex; + } +} diff --git a/shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java b/shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java index 672c83ff53..98e7fac567 100644 --- a/shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java +++ b/shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java @@ -65,9 +65,10 @@ public interface GridClientRpc extends ClientRpc { * @param connectorChanges * the indexing changes of details connectors * @param fetchId - * the id of the request for fetching the changes + * the id of the request for fetching the changes. A negative + * number indicates a push (not requested by the client side) */ public void setDetailsConnectorChanges( - Set connectorChanges, int fetchId); + Set connectorChanges, int fetchId); } diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java index 08f0d7d5d2..63fb903f53 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java @@ -47,10 +47,12 @@ import com.vaadin.ui.Button; import com.vaadin.ui.Button.ClickEvent; import com.vaadin.ui.Button.ClickListener; import com.vaadin.ui.Component; +import com.vaadin.ui.CssLayout; import com.vaadin.ui.Grid; import com.vaadin.ui.Grid.CellReference; import com.vaadin.ui.Grid.CellStyleGenerator; import com.vaadin.ui.Grid.Column; +import com.vaadin.ui.Grid.DetailsGenerator; import com.vaadin.ui.Grid.FooterCell; import com.vaadin.ui.Grid.HeaderCell; import com.vaadin.ui.Grid.HeaderRow; @@ -60,6 +62,7 @@ import com.vaadin.ui.Grid.RowStyleGenerator; import com.vaadin.ui.Grid.SelectionMode; import com.vaadin.ui.Grid.SelectionModel; import com.vaadin.ui.Label; +import com.vaadin.ui.Notification; import com.vaadin.ui.Panel; import com.vaadin.ui.renderers.DateRenderer; import com.vaadin.ui.renderers.HtmlRenderer; @@ -114,6 +117,53 @@ public class GridBasicFeatures extends AbstractComponentTest { private Panel detailsPanel; + private final DetailsGenerator detailedDetailsGenerator = new DetailsGenerator() { + @Override + public Component getDetails(final RowReference rowReference) { + CssLayout cssLayout = new CssLayout(); + cssLayout.setHeight("200px"); + cssLayout.setWidth("100%"); + + Item item = rowReference.getItem(); + for (Object propertyId : item.getItemPropertyIds()) { + Property prop = item.getItemProperty(propertyId); + String string = prop.getValue().toString(); + cssLayout.addComponent(new Label(string)); + } + + final int rowIndex = grid.getContainerDataSource().indexOfId( + rowReference.getItemId()); + ClickListener clickListener = new ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + Notification.show("You clicked on the " + + "button in the details for " + "row " + rowIndex); + } + }; + cssLayout.addComponent(new Button("Press me", clickListener)); + return cssLayout; + } + }; + + private final DetailsGenerator watchingDetailsGenerator = new DetailsGenerator() { + private int id = 0; + + @Override + public Component getDetails(RowReference rowReference) { + return new Label("You are watching item id " + + rowReference.getItemId() + " (" + (id++) + ")"); + } + }; + + private final DetailsGenerator hierarchicalDetailsGenerator = new DetailsGenerator() { + @Override + public Component getDetails(RowReference rowReference) { + detailsPanel = new Panel(); + detailsPanel.setContent(new Label("One")); + return detailsPanel; + } + }; + @Override @SuppressWarnings("unchecked") protected Grid constructComponent() { @@ -1059,40 +1109,32 @@ public class GridBasicFeatures extends AbstractComponentTest { } private void createDetailsActions() { - createClickAction("custom details generator", "Details", - new Command() { - @Override - public void execute(Grid c, Void value, Object data) { - grid.setDetailsGenerator(new Grid.DetailsGenerator() { - private int seq = 0; + Command swapDetailsGenerator = new Command() { + @Override + public void execute(Grid c, DetailsGenerator generator, Object data) { + grid.setDetailsGenerator(generator); + } + }; - @Override - public Component getDetails( - RowReference rowReference) { - return new Label("You are watching item id " - + rowReference.getItemId() + " (" - + (seq++) + ")"); - } - }); - } - }, null); - createClickAction("hierarchy details generator", "Details", - new Command() { - @Override - public void execute(Grid c, Void value, Object data) { - grid.setDetailsGenerator(new Grid.DetailsGenerator() { - @Override - public Component getDetails( - RowReference rowReference) { - detailsPanel = new Panel(); - detailsPanel.setContent(new Label("One")); - return detailsPanel; - } - }); - } - }, null); + Command openOrCloseItemId = new Command() { + @Override + @SuppressWarnings("boxing") + public void execute(Grid g, Boolean visible, Object itemId) { + g.setDetailsVisible(itemId, visible); + } + }; - createClickAction("change hierarchy in generator", "Details", + createCategory("Generators", "Details"); + createClickAction("NULL", "Generators", swapDetailsGenerator, + DetailsGenerator.NULL); + createClickAction("\"Watching\"", "Generators", swapDetailsGenerator, + watchingDetailsGenerator); + createClickAction("Detailed", "Generators", swapDetailsGenerator, + detailedDetailsGenerator); + createClickAction("Hierarchical", "Generators", swapDetailsGenerator, + hierarchicalDetailsGenerator); + + createClickAction("- Change Component", "Generators", new Command() { @Override public void execute(Grid c, Void value, Object data) { @@ -1105,7 +1147,7 @@ public class GridBasicFeatures extends AbstractComponentTest { } }, null); - createClickAction("toggle firstItemId", "Details", + createClickAction("Toggle firstItemId", "Details", new Command() { @Override public void execute(Grid g, Void value, Object data) { @@ -1117,26 +1159,11 @@ public class GridBasicFeatures extends AbstractComponentTest { } }, null); - createBooleanAction("firstItemId", "Details", false, - new Command() { - @Override - @SuppressWarnings("boxing") - public void execute(Grid g, Boolean visible, Object data) { - g.setDetailsVisible(g.getContainerDataSource() - .firstItemId(), visible); - } - }); + createBooleanAction("Open firstItemId", "Details", false, + openOrCloseItemId, ds.firstItemId()); - createBooleanAction("lastItemId-5", "Details", false, - new Command() { - @Override - @SuppressWarnings("boxing") - public void execute(Grid g, Boolean visible, Object data) { - Object fifthLastItemId = g.getContainerDataSource() - .getItemIds(ROWS - 6, 1).get(0); - g.setDetailsVisible(fifthLastItemId, visible); - } - }); + createBooleanAction("Open 995", "Details", false, openOrCloseItemId, + ds.getIdByIndex(995)); } @Override diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridDetailsServerTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridDetailsServerTest.java index e9b5b688d1..f3f58b002e 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridDetailsServerTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridDetailsServerTest.java @@ -16,6 +16,7 @@ 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.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -27,7 +28,7 @@ import org.openqa.selenium.By; import org.openqa.selenium.NoSuchElementException; import com.vaadin.testbench.TestBenchElement; -import com.vaadin.tests.components.grid.basicfeatures.GridBasicFeatures; +import com.vaadin.testbench.elements.NotificationElement; import com.vaadin.tests.components.grid.basicfeatures.GridBasicFeaturesTest; public class GridDetailsServerTest extends GridBasicFeaturesTest { @@ -37,20 +38,21 @@ public class GridDetailsServerTest extends GridBasicFeaturesTest { * able to scroll that particular details row into view, making tests * awkward with two scroll commands back to back. */ - private static final int ALMOST_LAST_ITEM_INDEX = GridBasicFeatures.ROWS - 5; - private static final String[] ALMOST_LAST_ITEM_DETAILS = new String[] { - "Component", "Details", "lastItemId-5" }; - - private static final String[] FIRST_ITEM_DETAILS = new String[] { - "Component", "Details", "firstItemId" }; + private static final int ALMOST_LAST_INDEX = 995; + private static final String[] OPEN_ALMOST_LAST_ITEM_DETAILS = new String[] { + "Component", "Details", "Open " + ALMOST_LAST_INDEX }; + private static final String[] OPEN_FIRST_ITEM_DETAILS = new String[] { + "Component", "Details", "Open firstItemId" }; private static final String[] TOGGLE_FIRST_ITEM_DETAILS = new String[] { - "Component", "Details", "toggle firstItemId" }; - private static final String[] CUSTOM_DETAILS_GENERATOR = new String[] { - "Component", "Details", "custom details generator" }; - private static final String[] HIERARCHY_DETAILS_GENERATOR = new String[] { - "Component", "Details", "hierarchy details generator" }; + "Component", "Details", "Toggle firstItemId" }; + private static final String[] DETAILS_GENERATOR_NULL = new String[] { + "Component", "Details", "Generators", "NULL" }; + private static final String[] DETAILS_GENERATOR_WATCHING = new String[] { + "Component", "Details", "Generators", "\"Watching\"" }; + private static final String[] DETAILS_GENERATOR_HIERARCHICAL = new String[] { + "Component", "Details", "Generators", "Hierarchical" }; private static final String[] CHANGE_HIERARCHY = new String[] { - "Component", "Details", "change hierarchy in generator" }; + "Component", "Details", "Generators", "- Change Component" }; @Before public void setUp() { @@ -65,41 +67,42 @@ public class GridDetailsServerTest extends GridBasicFeaturesTest { } catch (NoSuchElementException ignore) { // expected } - selectMenuPath(FIRST_ITEM_DETAILS); + selectMenuPath(OPEN_FIRST_ITEM_DETAILS); assertNotNull("details should've opened", getGridElement() .getDetails(0)); } @Test(expected = NoSuchElementException.class) public void closeVisibleDetails() { - selectMenuPath(FIRST_ITEM_DETAILS); - selectMenuPath(FIRST_ITEM_DETAILS); + selectMenuPath(OPEN_FIRST_ITEM_DETAILS); + selectMenuPath(OPEN_FIRST_ITEM_DETAILS); getGridElement().getDetails(0); } @Test - public void openDetailsOutsideOfActiveRange() { + public void openDetailsOutsideOfActiveRange() throws InterruptedException { getGridElement().scroll(10000); - selectMenuPath(FIRST_ITEM_DETAILS); + selectMenuPath(OPEN_FIRST_ITEM_DETAILS); getGridElement().scroll(0); + Thread.sleep(50); assertNotNull("details should've been opened", getGridElement() .getDetails(0)); } @Test(expected = NoSuchElementException.class) public void closeDetailsOutsideOfActiveRange() { - selectMenuPath(FIRST_ITEM_DETAILS); + selectMenuPath(OPEN_FIRST_ITEM_DETAILS); getGridElement().scroll(10000); - selectMenuPath(FIRST_ITEM_DETAILS); + selectMenuPath(OPEN_FIRST_ITEM_DETAILS); getGridElement().scroll(0); getGridElement().getDetails(0); } @Test public void componentIsVisibleClientSide() { - selectMenuPath(CUSTOM_DETAILS_GENERATOR); - selectMenuPath(FIRST_ITEM_DETAILS); + selectMenuPath(DETAILS_GENERATOR_WATCHING); + selectMenuPath(OPEN_FIRST_ITEM_DETAILS); TestBenchElement details = getGridElement().getDetails(0); assertNotNull("No widget detected inside details", @@ -107,11 +110,11 @@ public class GridDetailsServerTest extends GridBasicFeaturesTest { } @Test - public void togglingAVisibleDetailsRowWithSeparateRoundtrips() { - selectMenuPath(CUSTOM_DETAILS_GENERATOR); - selectMenuPath(FIRST_ITEM_DETAILS); // open - selectMenuPath(FIRST_ITEM_DETAILS); // close - selectMenuPath(FIRST_ITEM_DETAILS); // open + public void openingDetailsTwice() { + selectMenuPath(DETAILS_GENERATOR_WATCHING); + selectMenuPath(OPEN_FIRST_ITEM_DETAILS); // open + selectMenuPath(OPEN_FIRST_ITEM_DETAILS); // close + selectMenuPath(OPEN_FIRST_ITEM_DETAILS); // open TestBenchElement details = getGridElement().getDetails(0); assertNotNull("No widget detected inside details", @@ -120,7 +123,7 @@ public class GridDetailsServerTest extends GridBasicFeaturesTest { @Test(expected = NoSuchElementException.class) public void scrollingDoesNotCreateAFloodOfDetailsRows() { - selectMenuPath(CUSTOM_DETAILS_GENERATOR); + selectMenuPath(DETAILS_GENERATOR_WATCHING); // scroll somewhere to hit uncached rows getGridElement().scrollToRow(101); @@ -133,8 +136,8 @@ public class GridDetailsServerTest extends GridBasicFeaturesTest { public void openingDetailsOutOfView() { getGridElement().scrollToRow(500); - selectMenuPath(CUSTOM_DETAILS_GENERATOR); - selectMenuPath(FIRST_ITEM_DETAILS); + selectMenuPath(DETAILS_GENERATOR_WATCHING); + selectMenuPath(OPEN_FIRST_ITEM_DETAILS); getGridElement().scrollToRow(0); @@ -145,8 +148,8 @@ public class GridDetailsServerTest extends GridBasicFeaturesTest { @Test public void togglingAVisibleDetailsRowWithOneRoundtrip() { - selectMenuPath(CUSTOM_DETAILS_GENERATOR); - selectMenuPath(FIRST_ITEM_DETAILS); // open + selectMenuPath(DETAILS_GENERATOR_WATCHING); + selectMenuPath(OPEN_FIRST_ITEM_DETAILS); // open assertTrue("Unexpected generator content", getGridElement().getDetails(0).getText().endsWith("(0)")); @@ -156,36 +159,111 @@ public class GridDetailsServerTest extends GridBasicFeaturesTest { } @Test - @Ignore("This will be patched with https://dev.vaadin.com/review/#/c/7917/") public void almosLastItemIdIsRendered() { - selectMenuPath(CUSTOM_DETAILS_GENERATOR); - selectMenuPath(ALMOST_LAST_ITEM_DETAILS); + selectMenuPath(DETAILS_GENERATOR_WATCHING); + selectMenuPath(OPEN_ALMOST_LAST_ITEM_DETAILS); scrollGridVerticallyTo(100000); TestBenchElement details = getGridElement().getDetails( - ALMOST_LAST_ITEM_INDEX); + ALMOST_LAST_INDEX); assertNotNull(details); assertTrue("Unexpected details content", - details.getText().endsWith(ALMOST_LAST_ITEM_INDEX + " (0)")); + details.getText().endsWith(ALMOST_LAST_INDEX + " (0)")); } @Test public void hierarchyChangesWorkInDetails() { - selectMenuPath(HIERARCHY_DETAILS_GENERATOR); - selectMenuPath(FIRST_ITEM_DETAILS); + selectMenuPath(DETAILS_GENERATOR_HIERARCHICAL); + selectMenuPath(OPEN_FIRST_ITEM_DETAILS); assertEquals("One", getGridElement().getDetails(0).getText()); selectMenuPath(CHANGE_HIERARCHY); assertEquals("Two", getGridElement().getDetails(0).getText()); } + @Ignore("This use case is not currently supported by Grid. If the detail " + + "is out of view, the component is detached from the UI and a " + + "new instance is generated when scrolled back. Support will " + + "maybe be incorporated at a later time") @Test - @Ignore("This will be patched with https://dev.vaadin.com/review/#/c/7917/") public void hierarchyChangesWorkInDetailsWhileOutOfView() { - selectMenuPath(HIERARCHY_DETAILS_GENERATOR); - selectMenuPath(FIRST_ITEM_DETAILS); + selectMenuPath(DETAILS_GENERATOR_HIERARCHICAL); + selectMenuPath(OPEN_FIRST_ITEM_DETAILS); scrollGridVerticallyTo(10000); selectMenuPath(CHANGE_HIERARCHY); scrollGridVerticallyTo(0); assertEquals("Two", getGridElement().getDetails(0).getText()); } + + @Test + public void swappingDetailsGenerators_noDetailsShown() { + selectMenuPath(DETAILS_GENERATOR_WATCHING); + selectMenuPath(DETAILS_GENERATOR_NULL); + assertFalse("Got some errors", $(NotificationElement.class).exists()); + } + + @Test + public void swappingDetailsGenerators_shownDetails() { + selectMenuPath(OPEN_FIRST_ITEM_DETAILS); + assertTrue("Details should be empty at the start", getGridElement() + .getDetails(0).getText().isEmpty()); + + selectMenuPath(DETAILS_GENERATOR_WATCHING); + assertFalse("Details should not be empty after swapping generator", + getGridElement().getDetails(0).getText().isEmpty()); + } + + @Test + public void swappingDetailsGenerators_whileDetailsScrolledOut_showNever() { + scrollGridVerticallyTo(1000); + selectMenuPath(OPEN_FIRST_ITEM_DETAILS); + selectMenuPath(DETAILS_GENERATOR_WATCHING); + assertFalse("Got some errors", $(NotificationElement.class).exists()); + } + + @Test + public void swappingDetailsGenerators_whileDetailsScrolledOut_showAfter() { + scrollGridVerticallyTo(1000); + selectMenuPath(OPEN_FIRST_ITEM_DETAILS); + selectMenuPath(DETAILS_GENERATOR_WATCHING); + scrollGridVerticallyTo(0); + + assertFalse("Got some errors", $(NotificationElement.class).exists()); + assertNotNull("Could not find a details", getGridElement() + .getDetails(0)); + } + + @Test + public void swappingDetailsGenerators_whileDetailsScrolledOut_showBefore() { + selectMenuPath(OPEN_FIRST_ITEM_DETAILS); + selectMenuPath(DETAILS_GENERATOR_WATCHING); + scrollGridVerticallyTo(1000); + + assertFalse("Got some errors", $(NotificationElement.class).exists()); + assertNotNull("Could not find a details", getGridElement() + .getDetails(0)); + } + + @Test + public void swappingDetailsGenerators_whileDetailsScrolledOut_showBeforeAndAfter() { + selectMenuPath(OPEN_FIRST_ITEM_DETAILS); + selectMenuPath(DETAILS_GENERATOR_WATCHING); + scrollGridVerticallyTo(1000); + scrollGridVerticallyTo(0); + + assertFalse("Got some errors", $(NotificationElement.class).exists()); + assertNotNull("Could not find a details", getGridElement() + .getDetails(0)); + } + + @Test + public void nullDetailComponentToggling() { + selectMenuPath(OPEN_FIRST_ITEM_DETAILS); + selectMenuPath(DETAILS_GENERATOR_WATCHING); + selectMenuPath(DETAILS_GENERATOR_NULL); + assertTrue("Details should be empty with null component", + getGridElement().getDetails(0).getText().isEmpty()); + selectMenuPath(DETAILS_GENERATOR_WATCHING); + assertFalse("Details should be not empty with details component", + getGridElement().getDetails(0).getText().isEmpty()); + } } -- cgit v1.2.3 From 4e26f150752d29a015dec71dfcba186b19b7506a Mon Sep 17 00:00:00 2001 From: Pekka Hyvönen Date: Wed, 18 Mar 2015 14:37:56 +0200 Subject: Grid's columns hidable with toggles in the sidebar (#17023) There is know issue with hidden columns and reordering columns. Change-Id: If308c84aed5dd23c2c00cb38ed6bba41c6f1a28e --- client/src/com/vaadin/client/widgets/Grid.java | 233 +++++++++++--- .../basicfeatures/GridBasicClientFeaturesTest.java | 5 + .../grid/basicfeatures/GridColumnHidingTest.java | 337 +++++++++++++++++++-- .../grid/basicfeatures/GridColumnReorderTest.java | 4 - 4 files changed, 504 insertions(+), 75 deletions(-) (limited to 'client/src') diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index e84ea7d1f5..0238d4a1a0 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -66,6 +66,7 @@ import com.google.gwt.user.client.ui.HasEnabled; import com.google.gwt.user.client.ui.HasHorizontalAlignment.HorizontalAlignmentConstant; import com.google.gwt.user.client.ui.HasWidgets; import com.google.gwt.user.client.ui.ResizeComposite; +import com.google.gwt.user.client.ui.ToggleButton; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; import com.vaadin.client.BrowserInfo; @@ -2791,6 +2792,11 @@ public class Grid extends ResizeComposite implements /** * Sidebar displaying toggles for hidable columns and additional custom * widgets. + *

+ * The button for opening the sidebar is automatically visible inside the + * grid, if it contains any column hiding options or custom widgets. The + * column hiding toggles and custom widgets become visible once the sidebar + * has been opened. * * @since */ @@ -2808,13 +2814,23 @@ public class Grid extends ResizeComposite implements } }; + /** + * Contains all the widgets which should be shown once the sidebar is + * opened + */ + private final List widgets = new ArrayList(); + private final VerticalPanel rootContainer; private final VButton openCloseButton; + private final Grid grid; + private boolean open; - public Sidebar() { + public Sidebar(Grid grid) { + this.grid = grid; + rootContainer = new VerticalPanel(); initWidget(rootContainer); @@ -2838,6 +2854,9 @@ public class Grid extends ResizeComposite implements if (!open) { addStyleName("opened"); open = true; + for (Widget w : widgets) { + rootContainer.add(w); + } } } @@ -2850,6 +2869,8 @@ public class Grid extends ResizeComposite implements if (open) { removeStyleName("opened"); open = false; + rootContainer.clear(); + rootContainer.add(openCloseButton); } } @@ -2866,12 +2887,158 @@ public class Grid extends ResizeComposite implements return open; } + /** + * Adds or moves the given widget to the end of the sidebar. + * + * @param widget + * the widget to add or move + */ + public void add(Widget widget) { + widgets.remove(widget); + widgets.add(widget); + if (open) { + rootContainer.add(widget); + } + updateVisibility(); + } + + /** + * Removes the given widget from the sidebar. + * + * @param widget + * the widget to remove + */ + public void remove(Widget widget) { + widgets.remove(widget); + if (open) { + rootContainer.remove(widget); + } + updateVisibility(); + } + + /** + * Inserts given widget to the given index inside the sidebar. If the + * widget is already in the sidebar, then it is moved to the new index. + *

+ * See + * {@link VerticalPanel#insert(com.google.gwt.user.client.ui.IsWidget, int)} + * for further details. + * + * @param widget + * the widget to insert + * @param beforeIndex + * 0-based index position for the widget. + */ + public void insert(Widget widget, int beforeIndex) { + widgets.remove(widget); + widgets.add(beforeIndex, widget); + if (open) { + // the first widget in the container is always the open button + rootContainer.insert(widget, beforeIndex + 1); + } + updateVisibility(); + } + @Override public void setStylePrimaryName(String styleName) { super.setStylePrimaryName(styleName); openCloseButton.setStylePrimaryName(styleName + "-button"); } + private void updateVisibility() { + final boolean hasWidgets = widgets.size() > 0; + final boolean isVisible = getParent() != null; + if (isVisible && !hasWidgets) { + getElement().removeFromParent(); + removeFromParent(); + } else if (!isVisible && hasWidgets) { + grid.getElement().appendChild(getElement()); + Grid.setParent(this, grid); + } + } + + } + + /** + * UI and functionality related to hiding columns with toggles in the + * sidebar. + */ + private final class ColumnHider extends VerticalPanel { + + ColumnHider() { + setStyleName("column-hiding-panel"); + } + + /** Map from columns to their hiding toggles, component might change */ + private HashMap, ToggleButton> columnToHidingToggleMap = new HashMap, ToggleButton>(); + + private void updateColumnHidable(final Column column) { + if (column.isHidable()) { + ToggleButton cb = columnToHidingToggleMap.get(column); + if (cb == null) { + cb = createToggle(column); + } + updateToggleValue(cb, column.isHidden()); + } else if (columnToHidingToggleMap.containsValue(column)) { + ((Widget) columnToHidingToggleMap.remove(column)) + .removeFromParent(); + } + updateTogglesOrder(); + updatePanelVisibility(); + } + + private ToggleButton createToggle(final Column column) { + ToggleButton toggle = new ToggleButton(column.headerCaption); + toggle.addStyleName("column-hiding-toggle"); + toggle.addValueChangeHandler(new ValueChangeHandler() { + + @Override + public void onValueChange(ValueChangeEvent event) { + column.setHidden(!event.getValue(), true); + } + }); + columnToHidingToggleMap.put(column, toggle); + return toggle; + } + + private void updateTogglesOrder() { + clear(); + for (Column c : getColumns()) { + if (c.isHidable()) { + add(columnToHidingToggleMap.get(c)); + } + } + } + + private void updatePanelVisibility() { + final boolean columnHidable = getWidgetCount() > 0; + // parent for the panel might be null sidebar is not open + final boolean columnTogglesPanelIsVisible = sidebar.widgets + .contains(this); + + if (columnHidable && !columnTogglesPanelIsVisible) { + sidebar.insert(this, 0); + } else if (!columnHidable && columnTogglesPanelIsVisible) { + sidebar.remove(this); + } + } + + private void updateToggleValue(Column column) { + if (column.isHidable()) { + updateToggleValue(columnToHidingToggleMap.get(column), + column.isHidden()); + } // else we can just ignore + } + + private void updateToggleValue(ToggleButton hasValue, boolean hidden) { + hasValue.setValue(!hidden, false); + hasValue.setStyleName("hidden", hidden); + } + + private void updateColumnHidingToggleCaption(Column column) { + columnToHidingToggleMap.get(column).setText(column.headerCaption); + } + } /** @@ -2883,7 +3050,7 @@ public class Grid extends ResizeComposite implements private final Footer footer = GWT.create(Footer.class); - private final Sidebar sidebar = GWT.create(Sidebar.class); + private final Sidebar sidebar = new Sidebar(this); /** * List of columns in the grid. Order defines the visible order. @@ -2963,6 +3130,8 @@ public class Grid extends ResizeComposite implements private boolean columnReorderingAllowed; + private ColumnHider columnHider = new ColumnHider(); + private DragAndDropHandler dndHandler = new DragAndDropHandler(); private AutoScroller autoScroller = new AutoScroller(this); @@ -3589,6 +3758,9 @@ public class Grid extends ResizeComposite implements HeaderRow row = grid.getHeader().getDefaultRow(); if (row != null) { row.getCell(this).setText(headerCaption); + if (isHidable()) { + grid.columnHider.updateColumnHidingToggleCaption(this); + } } } @@ -3756,6 +3928,10 @@ public class Grid extends ResizeComposite implements * to show */ public void setHidden(boolean hidden) { + setHidden(hidden, false); + } + + private void setHidden(boolean hidden, boolean userOriginated) { if (this.hidden != hidden) { if (hidden) { grid.escalator.getColumnConfiguration().removeColumns( @@ -3766,9 +3942,10 @@ public class Grid extends ResizeComposite implements grid.escalator.getColumnConfiguration().insertColumns( grid.getVisibleColumns().indexOf(this), 1); } + grid.columnHider.updateToggleValue(this); scheduleColumnWidthRecalculator(); this.grid.fireEvent(new ColumnVisibilityChangeEvent(this, - hidden, false)); + hidden, userOriginated)); } } @@ -3796,8 +3973,10 @@ public class Grid extends ResizeComposite implements * false if not */ public void setHidable(boolean hidable) { - this.hidable = hidable; - grid.updateSideBarVisibility(); + if (this.hidable != hidable) { + this.hidable = hidable; + grid.columnHider.updateColumnHidable(this); + } } /** @@ -6686,6 +6865,8 @@ public class Grid extends ResizeComposite implements row.calculateColspans(); } + columnHider.updateTogglesOrder(); + fireEvent(new ColumnReorderEvent()); } @@ -7039,51 +7220,17 @@ public class Grid extends ResizeComposite implements autoColumnWidthsRecalculator.schedule(); } - /** - * Setter for displaying the grid's {@link Sidebar}. The sidebar is visible - * automatically when there are {@link Column#setHidable(boolean) hidable - * columns}. - *

- * Setting the sidebar visible doens't open it - it only shows the button - * for opening it. For opening and closing the sidebar use - * {@link Sidebar#open()} and {@link Sidebar#close()}. - * - * @since - * @param visible - * true for showing the sidebar, false - * for removing it - */ - public void setSidebarVisible(boolean visible) { - if ((sidebar.getParent() != null) != visible) { - if (visible) { - getElement().appendChild(sidebar.getElement()); - setParent(sidebar, this); - } else { - sidebar.getElement().removeFromParent(); - sidebar.removeFromParent(); - } - } - } - /** * Returns the sidebar for this grid. + *

+ * The grid's sidebar shows the column hiding options for those columns that + * have been set as {@link Column#setHidable(boolean) hidable}. * * @since - * @return + * @return the sidebar widget for this grid */ public Sidebar getSidebar() { return sidebar; } - private void updateSideBarVisibility() { - boolean visible = false; - for (Column c : getColumns()) { - if (c.isHidable()) { - visible = true; - break; - } - } - setSidebarVisible(visible); - } - } diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicClientFeaturesTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicClientFeaturesTest.java index 8b4eb0e267..90b572cffe 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicClientFeaturesTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicClientFeaturesTest.java @@ -98,4 +98,9 @@ public abstract class GridBasicClientFeaturesTest extends GridBasicFeaturesTest assertColumnHeader("HEADER (0," + indices[i] + ")", headers.get(i)); } } + + protected void toggleColumnReorder() { + selectMenuPath("Component", "State", "Column Reordering"); + } + } diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnHidingTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnHidingTest.java index ba4468da2c..87af43af12 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnHidingTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnHidingTest.java @@ -24,6 +24,7 @@ import static org.junit.Assert.assertTrue; import java.util.List; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.openqa.selenium.By; import org.openqa.selenium.WebElement; @@ -43,12 +44,12 @@ public class GridColumnHidingTest extends GridBasicClientFeaturesTest { selectMenuPath("Component", "State", "Width", "1000px"); assertColumnHeaderOrder(0, 1, 2, 3, 4, 5, 6); - toggleHideColumn(0); + toggleHideColumnAPI(0); assertColumnHeaderOrder(1, 2, 3, 4, 5, 6); - toggleHideColumn(1); - toggleHideColumn(2); - toggleHideColumn(3); + toggleHideColumnAPI(1); + toggleHideColumnAPI(2); + toggleHideColumnAPI(3); assertColumnHeaderOrder(4, 5, 6, 7, 8); } @@ -57,19 +58,19 @@ public class GridColumnHidingTest extends GridBasicClientFeaturesTest { selectMenuPath("Component", "State", "Width", "1000px"); assertColumnHeaderOrder(0, 1, 2, 3, 4, 5, 6); - toggleHideColumn(0); + toggleHideColumnAPI(0); assertColumnHeaderOrder(1, 2, 3, 4, 5, 6); - toggleHideColumn(0); + toggleHideColumnAPI(0); assertColumnHeaderOrder(0, 1, 2, 3, 4, 5, 6); - toggleHideColumn(1); - toggleHideColumn(2); - toggleHideColumn(3); + toggleHideColumnAPI(1); + toggleHideColumnAPI(2); + toggleHideColumnAPI(3); assertColumnHeaderOrder(0, 4, 5, 6, 7, 8); - toggleHideColumn(1); - toggleHideColumn(2); + toggleHideColumnAPI(1); + toggleHideColumnAPI(2); assertColumnHeaderOrder(0, 1, 2, 4, 5, 6); } @@ -78,26 +79,26 @@ public class GridColumnHidingTest extends GridBasicClientFeaturesTest { selectMenuPath("Component", "State", "Width", "1000px"); assertColumnHeaderOrder(0, 1, 2, 3, 4, 5, 6); - toggleHideColumn(2); + toggleHideColumnAPI(2); assertColumnHeaderOrder(0, 1, 3, 4, 5, 6); - toggleHideColumn(2); + toggleHideColumnAPI(2); assertColumnHeaderOrder(0, 1, 2, 3, 4, 5, 6); - toggleHideColumn(2); + toggleHideColumnAPI(2); assertColumnHeaderOrder(0, 1, 3, 4, 5, 6); - toggleHideColumn(2); + toggleHideColumnAPI(2); assertColumnHeaderOrder(0, 1, 2, 3, 4, 5, 6); } @Test - public void testColumnHiding_onVisibilityChange_triggersClientSideEvent() { + public void testColumnHiding_changeVisibilityAPI_triggersClientSideEvent() { assertColumnHeaderOrder(0, 1, 2, 3, 4); selectMenuPath("Component", "Internals", "Listeners", "Add Column Visibility Change listener"); - toggleHideColumn(2); + toggleHideColumnAPI(2); assertColumnHeaderOrder(0, 1, 3, 4); WebElement webElement = findElement(By.id("columnvisibility")); @@ -115,7 +116,7 @@ public class GridColumnHidingTest extends GridBasicClientFeaturesTest { assertEquals(false, userOriginated); assertEquals(true, hidden); - toggleHideColumn(2); + toggleHideColumnAPI(2); assertColumnHeaderOrder(0, 1, 2, 3, 4); webElement = findElement(By.id("columnvisibility")); @@ -132,12 +133,55 @@ public class GridColumnHidingTest extends GridBasicClientFeaturesTest { assertEquals(false, hidden); } + @Test + public void testColumnHiding_changeVisibilityToggle_triggersClientSideEvent() { + assertColumnHeaderOrder(0, 1, 2, 3, 4); + selectMenuPath("Component", "Internals", "Listeners", + "Add Column Visibility Change listener"); + + toggleHidableColumnAPI(2); + clickSidebarOpenButton(); + getColumnHidingToggle(2).click(); + assertColumnHeaderOrder(0, 1, 3, 4); + + WebElement webElement = findElement(By.id("columnvisibility")); + int counter = Integer.parseInt(webElement.getAttribute("counter")); + int columnIndex = Integer.parseInt(webElement + .getAttribute("columnindex")); + boolean userOriginated = Boolean.parseBoolean(webElement + .getAttribute("useroriginated")); + boolean hidden = Boolean.parseBoolean(webElement + .getAttribute("ishidden")); + + assertNotNull("no event fired", webElement); + assertEquals(1, counter); + assertEquals(2, columnIndex); + assertEquals(true, userOriginated); + assertEquals(true, hidden); + + getColumnHidingToggle(2).click(); + assertColumnHeaderOrder(0, 1, 2, 3, 4); + + webElement = findElement(By.id("columnvisibility")); + counter = Integer.parseInt(webElement.getAttribute("counter")); + columnIndex = Integer.parseInt(webElement.getAttribute("columnIndex")); + userOriginated = Boolean.parseBoolean(webElement + .getAttribute("userOriginated")); + hidden = Boolean.parseBoolean(webElement.getAttribute("ishidden")); + + assertNotNull("no event fired", webElement); + assertEquals(2, counter); + assertEquals(2, columnIndex); + assertEquals(true, userOriginated); + assertEquals(false, hidden); + } + @Test public void testColumnHidability_onTriggerColumnHidability_showsSidebarButton() { WebElement sidebar = getSidebar(); assertNull(sidebar); - toggleHidableColumn(0); + toggleHidableColumnAPI(0); sidebar = getSidebar(); assertNotNull(sidebar); @@ -147,32 +191,248 @@ public class GridColumnHidingTest extends GridBasicClientFeaturesTest { public void testColumnHidability_triggeringColumnHidabilityWithSeveralColumns_showsAndHidesSiderbarButton() { verifySidebarNotVisible(); - toggleHidableColumn(3); - toggleHidableColumn(4); + toggleHidableColumnAPI(3); + toggleHidableColumnAPI(4); verifySidebarVisible(); - toggleHidableColumn(3); + toggleHidableColumnAPI(3); verifySidebarVisible(); - toggleHidableColumn(4); + toggleHidableColumnAPI(4); verifySidebarNotVisible(); } @Test public void testColumnHidability_clickingSidebarButton_opensClosesSidebar() { - toggleHidableColumn(0); + toggleHidableColumnAPI(0); verifySidebarClosed(); - getSidebarOpenButton().click(); + clickSidebarOpenButton(); verifySidebarOpened(); - getSidebarOpenButton().click(); + clickSidebarOpenButton(); + + verifySidebarClosed(); + } + + @Test + public void testColumnHidability_settingColumnHidable_showsToggleInSidebar() { + toggleHidableColumnAPI(0); + verifySidebarClosed(); + clickSidebarOpenButton(); + verifySidebarOpened(); + + verifyColumnHidingOption(0, false); + } + + @Test + public void testColumnHiding_hidingColumnWithToggle_works() { + assertColumnHeaderOrder(0, 1, 2, 3, 4); + toggleHidableColumnAPI(0); + verifySidebarClosed(); + clickSidebarOpenButton(); + verifySidebarOpened(); + verifyColumnHidingOption(0, false); + + getColumnHidingToggle(0).click(); + verifyColumnHidingOption(0, true); + assertColumnHeaderOrder(1, 2, 3, 4); + + getColumnHidingToggle(0).click(); + verifyColumnHidingOption(0, false); + assertColumnHeaderOrder(0, 1, 2, 3, 4); + } + + @Test + public void testColumnHiding_updatingHiddenWhileSidebarClosed_updatesToggleValue() { + toggleHidableColumnAPI(0); + toggleHidableColumnAPI(3); + toggleHideColumnAPI(3); + assertColumnHeaderOrder(0, 1, 2, 4); + verifySidebarClosed(); + + clickSidebarOpenButton(); + verifySidebarOpened(); + verifyColumnHidingOption(0, false); + verifyColumnHidingOption(3, true); + clickSidebarOpenButton(); verifySidebarClosed(); + + toggleHideColumnAPI(0); + toggleHideColumnAPI(3); + + clickSidebarOpenButton(); + verifySidebarOpened(); + verifyColumnHidingOption(0, true); + verifyColumnHidingOption(3, false); + + } + + @Test + public void testColumnHiding_hidingMultipleColumnsWithToggle_hidesColumns() { + assertColumnHeaderOrder(0, 1, 2, 3, 4); + + toggleHideColumnAPI(1); + toggleHidableColumnAPI(0); + toggleHidableColumnAPI(1); + toggleHidableColumnAPI(2); + toggleHidableColumnAPI(3); + toggleHidableColumnAPI(4); + verifySidebarClosed(); + assertColumnHeaderOrder(0, 2, 3, 4); + + clickSidebarOpenButton(); + verifySidebarOpened(); + verifyColumnHidingOption(0, false); + verifyColumnHidingOption(1, true); + verifyColumnHidingOption(2, false); + verifyColumnHidingOption(3, false); + verifyColumnHidingOption(4, false); + + // must be done in a funny order so that the header indexes won't break + // (because of data source uses counter) + getColumnHidingToggle(1).click(); + getColumnHidingToggle(2).click(); + getColumnHidingToggle(3).click(); + getColumnHidingToggle(4).click(); + getColumnHidingToggle(0).click(); + verifyColumnHidingOption(0, true); + verifyColumnHidingOption(1, false); + verifyColumnHidingOption(2, true); + verifyColumnHidingOption(3, true); + verifyColumnHidingOption(4, true); + + assertColumnHeaderOrder(1, 5, 6, 7); + + getColumnHidingToggle(0).click(); + getColumnHidingToggle(2).click(); + getColumnHidingToggle(1).click(); + verifyColumnHidingOption(0, false); + verifyColumnHidingOption(1, true); + verifyColumnHidingOption(2, false); + assertColumnHeaderOrder(0, 2, 5, 6); + } + + @Test + public void testColumnHidability_changingHidabilityWhenSidebarClosed_addsRemovesToggles() { + toggleHideColumnAPI(0); + toggleHideColumnAPI(4); + assertColumnHeaderOrder(1, 2, 3, 5); + toggleHidableColumnAPI(0); + toggleHidableColumnAPI(3); + toggleHidableColumnAPI(4); + verifySidebarClosed(); + + clickSidebarOpenButton(); + verifySidebarOpened(); + verifyColumnHidingOption(0, true); + verifyColumnHidingOption(3, false); + verifyColumnHidingOption(4, true); + + clickSidebarOpenButton(); + verifySidebarClosed(); + + toggleHidableColumnAPI(0); + toggleHidableColumnAPI(3); + + verifySidebarClosed(); + clickSidebarOpenButton(); + verifySidebarOpened(); + verifyColumnHidingOption(4, true); + + assertNull(getColumnHidingToggle(0)); + assertNull(getColumnHidingToggle(3)); + } + + @Test + public void testColumnHidability_togglingHidability_placesTogglesInRightOrder() { + toggleHidableColumnAPI(3); + toggleHidableColumnAPI(2); + clickSidebarOpenButton(); + + verifyColumnHidingTogglesOrder(2, 3); + + toggleHidableColumnAPI(1); + toggleHidableColumnAPI(2); + toggleHidableColumnAPI(6); + toggleHidableColumnAPI(0); + + verifyColumnHidingTogglesOrder(0, 1, 3, 6); + + clickSidebarOpenButton(); + + toggleHidableColumnAPI(2); + toggleHidableColumnAPI(4); + toggleHidableColumnAPI(7); + + clickSidebarOpenButton(); + + verifyColumnHidingTogglesOrder(0, 1, 2, 3, 4, 6, 7); + } + + @Test + public void testColumnHidability_reorderingColumns_updatesColumnToggleOrder() { + selectMenuPath("Component", "State", "Width", "1000px"); + toggleHidableColumnAPI(0); + toggleHidableColumnAPI(1); + toggleHidableColumnAPI(3); + toggleHidableColumnAPI(4); + clickSidebarOpenButton(); + verifyColumnHidingTogglesOrder(0, 1, 3, 4); + clickSidebarOpenButton(); + + toggleColumnReorder(); + dragAndDropColumnHeader(0, 3, 0, 5); + + assertColumnHeaderOrder(3, 0, 1, 2, 4); + clickSidebarOpenButton(); + verifyColumnHidingTogglesOrder(3, 0, 1, 4); + + clickSidebarOpenButton(); + dragAndDropColumnHeader(0, 1, 3, 100); + dragAndDropColumnHeader(0, 4, 0, 5); + dragAndDropColumnHeader(0, 3, 0, 5); + + assertColumnHeaderOrder(2, 4, 3, 1, 0); + clickSidebarOpenButton(); + verifyColumnHidingTogglesOrder(4, 3, 1, 0); + } + + // know issue, will be fixed in next patch + @Test + @Ignore + public void testColumnHidingAndReorder_reorderingOverHiddenColumns_orderIsKept() { + toggleColumnReorder(); + toggleHideColumnAPI(0); + assertColumnHeaderOrder(1, 2, 3, 4, 5); + + dragAndDropColumnHeader(0, 1, 0, 5); + assertColumnHeaderOrder(2, 1, 3, 4, 5); + + toggleHideColumnAPI(0); + assertColumnHeaderOrder(0, 2, 1, 3, 4, 5); + } + + private void verifyColumnHidingTogglesOrder(int... indices) { + WebElement sidebar = getSidebar(); + List elements = sidebar.findElements(By + .className("column-hiding-toggle")); + for (int i = 0; i < indices.length; i++) { + WebElement e = elements.get(i); + assertTrue(("Header (0," + indices[i] + ")").equalsIgnoreCase(e + .getText())); + } + } + + private void verifyColumnHidingOption(int columnIndex, boolean hidden) { + WebElement columnHidingToggle = getColumnHidingToggle(columnIndex); + assertEquals(hidden, + columnHidingToggle.getAttribute("class").contains("hidden")); } private void verifySidebarOpened() { @@ -206,12 +466,33 @@ public class GridColumnHidingTest extends GridBasicClientFeaturesTest { return elements.isEmpty() ? null : elements.get(0); } - private void toggleHidableColumn(int columnIndex) { + /** + * Returns the toggle inside the sidebar for hiding the column at the given + * index, or null if not found. + */ + private WebElement getColumnHidingToggle(int columnIndex) { + WebElement sidebar = getSidebar(); + List elements = sidebar.findElements(By + .className("column-hiding-toggle")); + for (WebElement e : elements) { + if (("Header (0," + columnIndex + ")") + .equalsIgnoreCase(e.getText())) { + return e; + } + } + return null; + } + + private void clickSidebarOpenButton() { + getSidebarOpenButton().click(); + } + + private void toggleHidableColumnAPI(int columnIndex) { selectMenuPath("Component", "Columns", "Column " + columnIndex, "Hidable"); } - private void toggleHideColumn(int columnIndex) { + private void toggleHideColumnAPI(int columnIndex) { selectMenuPath("Component", "Columns", "Column " + columnIndex, "Hidden"); } diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderTest.java index 685c00bbbd..023c206825 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderTest.java @@ -601,10 +601,6 @@ public class GridColumnReorderTest extends GridBasicClientFeaturesTest { assertColumnHeaderOrder(1, 2, 0, 3, 4); } - private void toggleColumnReorder() { - selectMenuPath("Component", "State", "Column Reordering"); - } - private void toggleSortableColumn(int index) { selectMenuPath("Component", "Columns", "Column " + index, "Sortable"); } -- cgit v1.2.3 From 2be1e43d7081f0bc2c5f905d6b007fe597934ae3 Mon Sep 17 00:00:00 2001 From: Henrik Paul Date: Wed, 18 Mar 2015 10:05:47 +0200 Subject: Adds server side column hiding API to Grid (#17023) Change-Id: Ic00e873176f499dfc45976439e09d712932775da --- .../vaadin/client/connectors/GridConnector.java | 4 + server/src/com/vaadin/ui/Grid.java | 159 +++++++++++++++++++++ .../com/vaadin/shared/ui/grid/GridColumnState.java | 6 + .../grid/basicfeatures/GridBasicFeatures.java | 43 +++++- .../server/GridColumnVisibilityTest.java | 89 ++++++++++++ 5 files changed, 300 insertions(+), 1 deletion(-) create mode 100644 uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridColumnVisibilityTest.java (limited to 'client/src') diff --git a/client/src/com/vaadin/client/connectors/GridConnector.java b/client/src/com/vaadin/client/connectors/GridConnector.java index 495bcd0411..d3045ee13b 100644 --- a/client/src/com/vaadin/client/connectors/GridConnector.java +++ b/client/src/com/vaadin/client/connectors/GridConnector.java @@ -824,6 +824,10 @@ public class GridConnector extends AbstractHasComponentsConnector implements column.setExpandRatio(state.expandRatio); column.setSortable(state.sortable); + + column.setHidden(state.hidden); + column.setHideable(state.hidable); + column.setEditable(state.editable); column.setEditorConnector((AbstractFieldConnector) state.editorConnector); } diff --git a/server/src/com/vaadin/ui/Grid.java b/server/src/com/vaadin/ui/Grid.java index 12722b2596..31a25d8f8f 100644 --- a/server/src/com/vaadin/ui/Grid.java +++ b/server/src/com/vaadin/ui/Grid.java @@ -166,6 +166,67 @@ import elemental.json.JsonValue; public class Grid extends AbstractComponent implements SelectionNotifier, SortNotifier, SelectiveRenderer, ItemClickNotifier { + /** + * An event listener for column visibility change events in the Grid. + * + * @since + */ + public interface ColumnVisibilityChangeListener extends Serializable { + /** + * Called when a column has become hidden or unhidden. + * + * @param event + */ + void columnVisibilityChanged(ColumnVisibilityChangeEvent event); + } + + /** + * An event that is fired when a column becomes hidden or unhidden. + * + * @since + */ + public static class ColumnVisibilityChangeEvent extends Component.Event { + + private final Column column; + private final boolean userOriginated; + + /** + * Constructor for a column visibility change event. + * + * @param column + * the column that changed its visibility + * @param isUserOriginated + * true iff the event was triggered by an UI + * interaction + */ + public ColumnVisibilityChangeEvent(Component source, Column column, + boolean isUserOriginated) { + super(source); + this.column = column; + userOriginated = isUserOriginated; + } + + /** + * Gets the column that became hidden or unhidden. + * + * @return the column that became hidden or unhidden. + * @see Column#isHidden() + */ + public Column getColumn() { + return column; + } + + /** + * Returns true if the column reorder was done by the user, + * false if not and it was triggered by server side code. + * + * @return true if event is a result of user interaction + */ + public boolean isUserOriginated() { + return userOriginated; + } + } + /** * Custom field group that allows finding property types before an item has * been bound. @@ -2715,6 +2776,66 @@ public class Grid extends AbstractComponent implements SelectionNotifier, public Field getEditorField() { return grid.getEditorField(getPropertyId()); } + + /** + * Hides or shows the column. By default columns are visible before + * explicitly hiding them. + * + * @since + * @param hidden + * true to hide the column, false + * to show + */ + public void setHidden(boolean hidden) { + if (hidden != getState().hidden) { + getState().hidden = hidden; + grid.markAsDirty(); + grid.fireColumnVisibilityChangeEvent(this, false); + } + } + + /** + * Is this column hidden. Default is {@code false}. + * + * @since + * @return true if the column is currently hidden, + * false otherwise + */ + public boolean isHidden() { + return getState().hidden; + } + + /** + * Set whether it is possible for the user to hide this column or not. + * Default is {@code false}. + *

+ * Note: it is still possible to hide the column + * programmatically using {@link #setHidden(boolean)} + * + * @since + * @param hidable + * true iff the column may be hidable by the + * user via UI interaction + */ + public void setHidable(boolean hidable) { + getState().hidable = hidable; + grid.markAsDirty(); + } + + /** + * Is it possible for the the user to hide this column. Default is + * {@code false}. + *

+ * Note: the column can be programmatically hidden using + * {@link #setHidden(boolean)} regardless of the returned value. + * + * @since + * @return true if the user can hide the column, + * false if not + */ + public boolean isHidable() { + return getState().hidable; + } } /** @@ -2952,6 +3073,11 @@ public class Grid extends AbstractComponent implements SelectionNotifier, .findMethod(ColumnReorderListener.class, "columnReorder", ColumnReorderEvent.class); + private static final Method COLUMN_VISIBILITY_METHOD = ReflectTools + .findMethod(ColumnVisibilityChangeListener.class, + "columnVisibilityChanged", + ColumnVisibilityChangeEvent.class); + /** * Creates a new Grid with a new {@link IndexedContainer} as the data * source. @@ -5242,4 +5368,37 @@ public class Grid extends AbstractComponent implements SelectionNotifier, public void recalculateColumnWidths() { getRpcProxy(GridClientRpc.class).recalculateColumnWidths(); } + + /** + * Registers a new column visibility change listener + * + * @since + * @param listener + * the listener to register + */ + public void addColumnVisibilityChangeListener( + ColumnVisibilityChangeListener listener) { + addListener(ColumnVisibilityChangeEvent.class, listener, + COLUMN_VISIBILITY_METHOD); + } + + /** + * Removes a previously registered column visibility change listener + * + * @since + * @param listener + * the listener to remove + */ + public void removeColumnVisibilityChangeListener( + ColumnVisibilityChangeListener listener) { + removeListener(ColumnVisibilityChangeEvent.class, listener, + COLUMN_VISIBILITY_METHOD); + } + + private void fireColumnVisibilityChangeEvent(Column column, + boolean isUserOriginated) { + fireEvent(new ColumnVisibilityChangeEvent(this, column, + isUserOriginated)); + } + } diff --git a/shared/src/com/vaadin/shared/ui/grid/GridColumnState.java b/shared/src/com/vaadin/shared/ui/grid/GridColumnState.java index 4c5b2c3a02..b966c53352 100644 --- a/shared/src/com/vaadin/shared/ui/grid/GridColumnState.java +++ b/shared/src/com/vaadin/shared/ui/grid/GridColumnState.java @@ -76,4 +76,10 @@ public class GridColumnState implements Serializable { * minWidth is less than the calculated width, minWidth will win. */ public double minWidth = GridConstants.DEFAULT_MIN_WIDTH; + + /** Is the column currently hidden. */ + public boolean hidden = false; + + /** Can the column be hidden by the UI. */ + public boolean hidable = false; } diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java index e9f6bfdbb9..aeeaa25ac3 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java @@ -52,6 +52,8 @@ import com.vaadin.ui.Grid.CellStyleGenerator; import com.vaadin.ui.Grid.Column; import com.vaadin.ui.Grid.ColumnReorderEvent; import com.vaadin.ui.Grid.ColumnReorderListener; +import com.vaadin.ui.Grid.ColumnVisibilityChangeEvent; +import com.vaadin.ui.Grid.ColumnVisibilityChangeListener; import com.vaadin.ui.Grid.FooterCell; import com.vaadin.ui.Grid.HeaderCell; import com.vaadin.ui.Grid.HeaderRow; @@ -120,6 +122,16 @@ public class GridBasicFeatures extends AbstractComponentTest { } }; + private ColumnVisibilityChangeListener columnVisibilityListener = new ColumnVisibilityChangeListener() { + @Override + public void columnVisibilityChanged(ColumnVisibilityChangeEvent event) { + log("Visibility changed: "// + + "propertyId: " + event.getColumn().getPropertyId() // + + ", isHidden: " + event.getColumn().isHidden() // + + ", userOriginated: " + event.isUserOriginated()); + } + }; + @Override @SuppressWarnings("unchecked") protected Grid constructComponent() { @@ -532,6 +544,17 @@ public class GridBasicFeatures extends AbstractComponentTest { } } }); + createBooleanAction("ColumnVisibilityChangeListener", "State", false, + new Command() { + @Override + public void execute(Grid grid, Boolean value, Object data) { + if (value) { + grid.addColumnVisibilityChangeListener(columnVisibilityListener); + } else { + grid.removeColumnVisibilityChangeListener(columnVisibilityListener); + } + } + }); createBooleanAction("Single select allow deselect", "State", singleSelectAllowDeselect, new Command() { @@ -676,6 +699,7 @@ public class GridBasicFeatures extends AbstractComponentTest { }, null); } + @SuppressWarnings("boxing") protected void createColumnActions() { createCategory("Columns", null); @@ -736,6 +760,24 @@ public class GridBasicFeatures extends AbstractComponentTest { } }, c); + createBooleanAction("Hidable", getColumnProperty(c), false, + new Command() { + @Override + public void execute(Grid c, Boolean hidable, + Object propertyId) { + grid.getColumn(propertyId).setHidable(hidable); + } + }, getColumnProperty(c)); + + createBooleanAction("Hidden", getColumnProperty(c), false, + new Command() { + @Override + public void execute(Grid c, Boolean hidden, + Object propertyId) { + grid.getColumn(propertyId).setHidden(hidden); + } + }, getColumnProperty(c)); + createCategory("Column " + c + " Width", getColumnProperty(c)); createClickAction("Auto", "Column " + c + " Width", @@ -753,7 +795,6 @@ public class GridBasicFeatures extends AbstractComponentTest { createClickAction("25.5px", "Column " + c + " Width", new Command() { @Override - @SuppressWarnings("boxing") public void execute(Grid grid, Void value, Object columnIndex) { grid.getColumns().get((Integer) columnIndex) diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridColumnVisibilityTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridColumnVisibilityTest.java new file mode 100644 index 0000000000..8fb733dfa0 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridColumnVisibilityTest.java @@ -0,0 +1,89 @@ +/* + * 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.server; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Before; +import org.junit.Test; + +import com.vaadin.testbench.annotations.RunLocally; +import com.vaadin.testbench.parallel.Browser; +import com.vaadin.testbench.parallel.TestCategory; +import com.vaadin.tests.components.grid.basicfeatures.GridBasicFeaturesTest; + +@TestCategory("grid") +@RunLocally(Browser.PHANTOMJS) +public class GridColumnVisibilityTest extends GridBasicFeaturesTest { + + private static final String[] TOGGLE_LISTENER = new String[] { "Component", + "State", "ColumnVisibilityChangeListener" }; + private static final String[] TOGGLE_HIDE_COLUMN_0 = new String[] { + "Component", "Columns", "Column 0", "Hidden" }; + + private static final String COLUMN_0_BECAME_HIDDEN_MSG = "Visibility " + + "changed: propertyId: Column 0, isHidden: true"; + private static final String COLUMN_0_BECAME_UNHIDDEN_MSG = "Visibility " + + "changed: propertyId: Column 0, isHidden: false"; + + @Before + public void setUp() { + openTestURL(); + } + + @Test + public void columnIsNotShownWhenHidden() { + assertEquals("column 0", getGridElement().getHeaderCell(0, 0).getText() + .toLowerCase()); + + selectMenuPath(TOGGLE_HIDE_COLUMN_0); + assertEquals("column 1", getGridElement().getHeaderCell(0, 0).getText() + .toLowerCase()); + } + + @Test + public void columnIsShownWhenUnhidden() { + selectMenuPath(TOGGLE_HIDE_COLUMN_0); + selectMenuPath(TOGGLE_HIDE_COLUMN_0); + assertEquals("column 0", getGridElement().getHeaderCell(0, 0).getText() + .toLowerCase()); + } + + @Test + public void registeringListener() { + assertFalse(logContainsText(COLUMN_0_BECAME_HIDDEN_MSG)); + selectMenuPath(TOGGLE_LISTENER); + assertFalse(logContainsText(COLUMN_0_BECAME_HIDDEN_MSG)); + + selectMenuPath(TOGGLE_HIDE_COLUMN_0); + assertTrue(logContainsText(COLUMN_0_BECAME_HIDDEN_MSG)); + + selectMenuPath(TOGGLE_HIDE_COLUMN_0); + assertTrue(logContainsText(COLUMN_0_BECAME_UNHIDDEN_MSG)); + } + + @Test + public void deregisteringListener() { + selectMenuPath(TOGGLE_LISTENER); + selectMenuPath(TOGGLE_HIDE_COLUMN_0); + + selectMenuPath(TOGGLE_LISTENER); + selectMenuPath(TOGGLE_HIDE_COLUMN_0); + assertFalse(logContainsText(COLUMN_0_BECAME_UNHIDDEN_MSG)); + } +} -- cgit v1.2.3 From 0e591d29925a957b3b5e18a71e922f6a859e87b7 Mon Sep 17 00:00:00 2001 From: Henrik Paul Date: Thu, 19 Mar 2015 13:21:21 +0200 Subject: Removes unnecessary todo logging (#16644) Change-Id: I5992e6f48c472f252bfbcc8d7b4991ded15fd197 --- client/src/com/vaadin/client/widgets/Escalator.java | 17 ----------------- 1 file changed, 17 deletions(-) (limited to 'client/src') diff --git a/client/src/com/vaadin/client/widgets/Escalator.java b/client/src/com/vaadin/client/widgets/Escalator.java index 828d9ca29e..0d65dda611 100644 --- a/client/src/com/vaadin/client/widgets/Escalator.java +++ b/client/src/com/vaadin/client/widgets/Escalator.java @@ -3483,10 +3483,6 @@ public class Escalator extends Widget implements RequiresResize, * instead. */ public void verifyEscalatorCount() { - - // TODO - getLogger().warning("[[spacers]] verifying escalator row count"); - /* * This method indeed has a smell very similar to paintRemoveRows * and paintInsertRows. @@ -3520,11 +3516,6 @@ public class Escalator extends Widget implements RequiresResize, if (neededEscalatorRowsDiff > 0) { // needs more - // TODO - getLogger().warning( - "[[spacers]] adding more rows while expanding the " - + "body section"); - /* * This is a workaround for the issue where we might be scrolled * to the bottom, and the widget expands beyond the content @@ -3595,11 +3586,6 @@ public class Escalator extends Widget implements RequiresResize, else if (neededEscalatorRowsDiff < 0) { // needs less - // TODO - getLogger().warning( - "[[spacers]] removing spacers while shrinking the body " - + "section"); - final ListIterator iter = visualRowOrder .listIterator(visualRowOrder.size()); for (int i = 0; i < -neededEscalatorRowsDiff; i++) { @@ -3650,9 +3636,6 @@ public class Escalator extends Widget implements RequiresResize, Profiler.enter("Escalator.BodyRowContainer.reapplyDefaultRowHeights"); - // TODO - getLogger().warning("[[spacer]] reapply default body row heights"); - /* step 1: resize and reposition rows */ for (int i = 0; i < visualRowOrder.size(); i++) { TableRowElement tr = visualRowOrder.get(i); -- cgit v1.2.3 From f6a148783e898c828155aab59b97f7a52faa5129 Mon Sep 17 00:00:00 2001 From: Leif Åstrand Date: Thu, 19 Mar 2015 13:13:14 +0200 Subject: Restructure sidebar widget handling to work with custom content (#17023) Change-Id: Ib24d1536af89ce97b2117d813e0f75405df7dab7 --- client/src/com/vaadin/client/widgets/Grid.java | 129 +++++++++++---------- .../client/GridSidebarContentTest.java | 105 +++++++++++++++++ .../client/grid/GridBasicClientFeaturesWidget.java | 39 +++++++ 3 files changed, 211 insertions(+), 62 deletions(-) create mode 100644 uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridSidebarContentTest.java (limited to 'client/src') diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index 174f2dde38..e9288a7ece 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -63,8 +63,8 @@ import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.CheckBox; import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.HasEnabled; -import com.google.gwt.user.client.ui.HasHorizontalAlignment.HorizontalAlignmentConstant; import com.google.gwt.user.client.ui.HasWidgets; import com.google.gwt.user.client.ui.ResizeComposite; import com.google.gwt.user.client.ui.ToggleButton; @@ -2887,8 +2887,8 @@ public class Grid extends ResizeComposite implements } /** - * Sidebar displaying toggles for hidable columns and additional custom - * widgets. + * Sidebar displaying toggles for hidable columns and custom widgets + * provided by the application. *

* The button for opening the sidebar is automatically visible inside the * grid, if it contains any column hiding options or custom widgets. The @@ -2903,7 +2903,7 @@ public class Grid extends ResizeComposite implements @Override public void onClick(ClickEvent event) { - if (!open) { + if (!isOpen()) { open(); } else { close(); @@ -2911,77 +2911,67 @@ public class Grid extends ResizeComposite implements } }; - /** - * Contains all the widgets which should be shown once the sidebar is - * opened - */ - private final List widgets = new ArrayList(); + private final FlowPanel rootContainer; - private final VerticalPanel rootContainer; + private final FlowPanel content; private final VButton openCloseButton; private final Grid grid; - private boolean open; - - public Sidebar(Grid grid) { + private Sidebar(Grid grid) { this.grid = grid; - rootContainer = new VerticalPanel(); + rootContainer = new FlowPanel(); initWidget(rootContainer); openCloseButton = new VButton(); openCloseButton.addClickHandler(openCloseButtonHandler); rootContainer.add(openCloseButton); - rootContainer - .setCellHorizontalAlignment( - openCloseButton, - HorizontalAlignmentConstant - .endOf(com.google.gwt.i18n.client.HasDirection.Direction.LTR)); + + content = new FlowPanel() { + @Override + public boolean remove(Widget w) { + // Check here to catch child.removeFromParent() calls + boolean removed = super.remove(w); + if (removed) { + updateVisibility(); + } + + return removed; + } + }; } /** - * Opens the sidebar if not yet opened. - * - * @since + * Opens the sidebar if not yet opened. Opening the sidebar has no + * effect if it is empty. */ public void open() { - if (!open) { + if (!isOpen() && isInDOM()) { addStyleName("opened"); - open = true; - for (Widget w : widgets) { - rootContainer.add(w); - } + rootContainer.add(content); } } /** * Closes the sidebar if not yet closed. - * - * @since */ public void close() { - if (open) { + if (isOpen()) { removeStyleName("opened"); - open = false; - rootContainer.clear(); - rootContainer.add(openCloseButton); + content.removeFromParent(); } } /** * Returns whether the sidebar is open or not. - *

- * Note: The sidebar can be in "open state" but not actually - * visible inside grid. See {@link #isVisibleInGrid()}. * - * @since * @return true if open, false if not */ public boolean isOpen() { - return open; + return content.getParent() == rootContainer; } /** @@ -2991,11 +2981,7 @@ public class Grid extends ResizeComposite implements * the widget to add or move */ public void add(Widget widget) { - widgets.remove(widget); - widgets.add(widget); - if (open) { - rootContainer.add(widget); - } + content.add(widget); updateVisibility(); } @@ -3006,11 +2992,8 @@ public class Grid extends ResizeComposite implements * the widget to remove */ public void remove(Widget widget) { - widgets.remove(widget); - if (open) { - rootContainer.remove(widget); - } - updateVisibility(); + content.remove(widget); + // updateVisibility is called by remove listener } /** @@ -3018,7 +3001,7 @@ public class Grid extends ResizeComposite implements * widget is already in the sidebar, then it is moved to the new index. *

* See - * {@link VerticalPanel#insert(com.google.gwt.user.client.ui.IsWidget, int)} + * {@link FlowPanel#insert(com.google.gwt.user.client.ui.IsWidget, int)} * for further details. * * @param widget @@ -3027,33 +3010,34 @@ public class Grid extends ResizeComposite implements * 0-based index position for the widget. */ public void insert(Widget widget, int beforeIndex) { - widgets.remove(widget); - widgets.add(beforeIndex, widget); - if (open) { - // the first widget in the container is always the open button - rootContainer.insert(widget, beforeIndex + 1); - } + content.insert(widget, beforeIndex); updateVisibility(); } @Override public void setStylePrimaryName(String styleName) { super.setStylePrimaryName(styleName); + content.setStylePrimaryName(styleName + "-content"); openCloseButton.setStylePrimaryName(styleName + "-button"); } private void updateVisibility() { - final boolean hasWidgets = widgets.size() > 0; - final boolean isVisible = getParent() != null; + final boolean hasWidgets = content.getWidgetCount() > 0; + final boolean isVisible = isInDOM(); if (isVisible && !hasWidgets) { + Grid.setParent(this, null); getElement().removeFromParent(); - removeFromParent(); } else if (!isVisible && hasWidgets) { + close(); grid.getElement().appendChild(getElement()); Grid.setParent(this, grid); } } + private boolean isInDOM() { + return getParent() != null; + } + } /** @@ -3109,9 +3093,7 @@ public class Grid extends ResizeComposite implements private void updatePanelVisibility() { final boolean columnHidable = getWidgetCount() > 0; - // parent for the panel might be null sidebar is not open - final boolean columnTogglesPanelIsVisible = sidebar.widgets - .contains(this); + final boolean columnTogglesPanelIsVisible = getParent() != null; if (columnHidable && !columnTogglesPanelIsVisible) { sidebar.insert(this, 0); @@ -3225,7 +3207,6 @@ public class Grid extends ResizeComposite implements private boolean enabled = true; - private DetailsGenerator detailsGenerator = DetailsGenerator.NULL; private GridSpacerUpdater gridSpacerUpdater = new GridSpacerUpdater(); /** A set keeping track of the indices of all currently open details */ @@ -7246,6 +7227,30 @@ public class Grid extends ResizeComposite implements widget.@com.google.gwt.user.client.ui.Widget::setParent(Lcom/google/gwt/user/client/ui/Widget;)(parent); }-*/; + private static native final void onAttach(Widget widget) + /*-{ + widget.@Widget::onAttach()(); + }-*/; + + private static native final void onDetach(Widget widget) + /*-{ + widget.@Widget::onDetach()(); + }-*/; + + @Override + protected void doAttachChildren() { + if (getSidebar().getParent() == this) { + onAttach(getSidebar()); + } + } + + @Override + protected void doDetachChildren() { + if (getSidebar().getParent() == this) { + onDetach(getSidebar()); + } + } + /** * Resets all cached pixel sizes and reads new values from the DOM. This * methods should be used e.g. when styles affecting the dimensions of diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridSidebarContentTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridSidebarContentTest.java new file mode 100644 index 0000000000..563fe890ec --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridSidebarContentTest.java @@ -0,0 +1,105 @@ +/* + * 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.client; + +import org.junit.Assert; +import org.junit.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; + +import com.vaadin.tests.components.grid.basicfeatures.GridBasicClientFeaturesTest; +import com.vaadin.tests.components.grid.basicfeatures.element.CustomGridElement; + +public class GridSidebarContentTest extends GridBasicClientFeaturesTest { + + @Test + public void testSidebarWithHidableColumn() { + openTestURL(); + CustomGridElement gridElement = getGridElement(); + + Assert.assertEquals("Sidebar should not be initially present", 0, + countBySelector(".v-grid-sidebar")); + + selectMenuPath("Component", "Columns", "Column 0", "Hidable"); + + gridElement.findElement(By.className("v-grid-sidebar-button")).click(); + + WebElement toggle = gridElement.findElement(By + .className("column-hiding-toggle")); + + Assert.assertEquals("Column 0 should be togglable", "Header (0,0)", + toggle.getText()); + + selectMenuPath("Component", "Columns", "Column 0", "Hidable"); + Assert.assertEquals("Sidebar should disappear without toggable column", + 0, countBySelector(".v-grid-sidebar")); + + } + + @Test + public void testSidebarWithCustomContent() { + openTestURL(); + CustomGridElement gridElement = getGridElement(); + + Assert.assertEquals("Sidebar should not be initially present", 0, + countBySelector(".v-grid-sidebar")); + + selectMenuPath("Component", "Sidebar", "Toggle sidebar entry"); + + gridElement.findElement(By.className("v-grid-sidebar-button")).click(); + + WebElement sidebarButton = gridElement.findElement(By + .cssSelector(".v-grid-sidebar-content button")); + + Assert.assertEquals("Sidebar button", sidebarButton.getText()); + + sidebarButton.click(); + + Assert.assertEquals("Click count: 1", sidebarButton.getText()); + + selectMenuPath("Component", "Sidebar", "Toggle sidebar entry"); + + Assert.assertEquals("Sidebar should disappear after content remove", 0, + countBySelector(".v-grid-sidebar")); + } + + @Test + public void testProgrammaticSidebarToggle() { + openTestURL(); + + selectMenuPath("Component", "Sidebar", "Toggle sidebar visibility"); + + Assert.assertEquals("Toggling without content should't show anything", + 0, countBySelector(".v-grid-sidebar-content button")); + + selectMenuPath("Component", "Sidebar", "Toggle sidebar entry"); + selectMenuPath("Component", "Sidebar", "Toggle sidebar visibility"); + + Assert.assertEquals("Toggling with content should show sidebar", 1, + countBySelector(".v-grid-sidebar-content button")); + + selectMenuPath("Component", "Sidebar", "Toggle sidebar visibility"); + + Assert.assertEquals("Toggling again should hide sidebar", 0, + countBySelector(".v-grid-sidebar-content button")); + } + + private int countBySelector(String cssSelector) { + return getGridElement().findElements(By.cssSelector(cssSelector)) + .size(); + } + +} 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 6615cda8da..97d1a50ff6 100644 --- a/uitest/src/com/vaadin/tests/widgetset/client/grid/GridBasicClientFeaturesWidget.java +++ b/uitest/src/com/vaadin/tests/widgetset/client/grid/GridBasicClientFeaturesWidget.java @@ -80,6 +80,7 @@ import com.vaadin.client.widgets.Grid.Column; import com.vaadin.client.widgets.Grid.FooterRow; import com.vaadin.client.widgets.Grid.HeaderRow; import com.vaadin.client.widgets.Grid.SelectionMode; +import com.vaadin.client.widgets.Grid.Sidebar; import com.vaadin.tests.widgetset.client.grid.GridBasicClientFeaturesWidget.Data; /** @@ -199,6 +200,17 @@ public class GridBasicClientFeaturesWidget extends private boolean secondEditorError = false; + private Button sidebarEntry = new Button("Sidebar button", + new ClickHandler() { + private int count = 0; + + @Override + public void onClick(ClickEvent event) { + count++; + sidebarEntry.setText("Click count: " + count); + } + }); + /** * Our basic data object */ @@ -408,6 +420,7 @@ public class GridBasicClientFeaturesWidget extends createInternalsMenu(); createDataSourceMenu(); createDetailsMenu(); + createSidebarMenu(); grid.getElement().getStyle().setZIndex(0); @@ -1441,6 +1454,32 @@ public class GridBasicClientFeaturesWidget extends grid.setDetailsVisible(100, visible); } }, menupath); + } + private void createSidebarMenu() { + String[] menupath = new String[] { "Component", "Sidebar" }; + + addMenuCommand("Toggle sidebar visibility", new ScheduledCommand() { + @Override + public void execute() { + Sidebar sidebar = grid.getSidebar(); + if (sidebar.isOpen()) { + sidebar.close(); + } else { + sidebar.open(); + } + } + }, menupath); + + addMenuCommand("Toggle sidebar entry", new ScheduledCommand() { + @Override + public void execute() { + if (sidebarEntry.getParent() != null) { + sidebarEntry.removeFromParent(); + } else { + grid.getSidebar().add(sidebarEntry); + } + } + }, menupath); } } -- cgit v1.2.3 From 27e0595fdb784891eda15a7b02f899d5d358b0d1 Mon Sep 17 00:00:00 2001 From: Leif Åstrand Date: Fri, 20 Mar 2015 12:20:10 +0200 Subject: Fix various small issues discovered while reviewing API Change-Id: I196e490d5c5ae77ba895e0fca1b0d9160b6a7855 --- client/src/com/vaadin/client/widget/grid/AutoScroller.java | 8 ++++---- client/src/com/vaadin/client/widget/grid/DetailsGenerator.java | 1 - .../vaadin/client/widget/grid/events/ColumnReorderHandler.java | 1 - client/src/com/vaadin/client/widgets/Escalator.java | 2 +- client/src/com/vaadin/client/widgets/Grid.java | 2 +- server/src/com/vaadin/ui/Grid.java | 4 +++- shared/src/com/vaadin/shared/ui/grid/DetailsConnectorChange.java | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) (limited to 'client/src') diff --git a/client/src/com/vaadin/client/widget/grid/AutoScroller.java b/client/src/com/vaadin/client/widget/grid/AutoScroller.java index f7c80df623..773dc012c8 100644 --- a/client/src/com/vaadin/client/widget/grid/AutoScroller.java +++ b/client/src/com/vaadin/client/widget/grid/AutoScroller.java @@ -75,7 +75,7 @@ public class AutoScroller { public enum ScrollAxis { VERTICAL, HORIZONTAL - }; + } /** The maximum number of pixels per second to autoscroll. */ private static final int SCROLL_TOP_SPEED_PX_SEC = 500; @@ -508,10 +508,10 @@ public class AutoScroller { * Defaults to 100px. * * @param px - * the height/width for the auto scroll area depending on + * the pixel height/width for the auto scroll area depending on * direction */ - public void setScrollAreaPX(int px) { + public void setScrollArea(int px) { scrollAreaPX = px; } @@ -522,7 +522,7 @@ public class AutoScroller { * * @return size in pixels */ - public int getScrollAreaPX() { + public int getScrollArea() { return scrollAreaPX; } diff --git a/client/src/com/vaadin/client/widget/grid/DetailsGenerator.java b/client/src/com/vaadin/client/widget/grid/DetailsGenerator.java index 309e3f1ea3..103bf96291 100644 --- a/client/src/com/vaadin/client/widget/grid/DetailsGenerator.java +++ b/client/src/com/vaadin/client/widget/grid/DetailsGenerator.java @@ -42,6 +42,5 @@ public interface DetailsGenerator { * @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?) Widget getDetails(int rowIndex); } diff --git a/client/src/com/vaadin/client/widget/grid/events/ColumnReorderHandler.java b/client/src/com/vaadin/client/widget/grid/events/ColumnReorderHandler.java index e4f258088f..4733ed8bc0 100644 --- a/client/src/com/vaadin/client/widget/grid/events/ColumnReorderHandler.java +++ b/client/src/com/vaadin/client/widget/grid/events/ColumnReorderHandler.java @@ -33,7 +33,6 @@ public interface ColumnReorderHandler extends EventHandler { * A column reorder event, fired by Grid when the columns of the Grid have * been reordered. * - * @since * @param event * column reorder event */ diff --git a/client/src/com/vaadin/client/widgets/Escalator.java b/client/src/com/vaadin/client/widgets/Escalator.java index 0d65dda611..75b797eb1f 100644 --- a/client/src/com/vaadin/client/widgets/Escalator.java +++ b/client/src/com/vaadin/client/widgets/Escalator.java @@ -4494,7 +4494,7 @@ public class Escalator extends Widget implements RequiresResize, * The meaning of each value may differ depending on the context it is being * used in. Check that particular method's JavaDoc. */ - public enum SpacerInclusionStrategy { + private enum SpacerInclusionStrategy { /** A representation of "the entire spacer". */ COMPLETE, diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index e9288a7ece..9821591b91 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -3385,7 +3385,7 @@ public class Grid extends ResizeComposite implements dragElement.addClassName("dragged-column-header"); // start the auto scroll handler - autoScroller.setScrollAreaPX(60); + autoScroller.setScrollArea(60); autoScroller.start(startingEvent, ScrollAxis.HORIZONTAL, autoScrollerCallback); return true; diff --git a/server/src/com/vaadin/ui/Grid.java b/server/src/com/vaadin/ui/Grid.java index 4488789406..4a52dba173 100644 --- a/server/src/com/vaadin/ui/Grid.java +++ b/server/src/com/vaadin/ui/Grid.java @@ -194,13 +194,15 @@ public class Grid extends AbstractComponent implements SelectionNotifier, /** * Constructor for a column visibility change event. * + * @param source + * the grid from which this event originates * @param column * the column that changed its visibility * @param isUserOriginated * true iff the event was triggered by an UI * interaction */ - public ColumnVisibilityChangeEvent(Component source, Column column, + public ColumnVisibilityChangeEvent(Grid source, Column column, boolean isUserOriginated) { super(source); this.column = column; diff --git a/shared/src/com/vaadin/shared/ui/grid/DetailsConnectorChange.java b/shared/src/com/vaadin/shared/ui/grid/DetailsConnectorChange.java index 40f4541fb1..5b80f27b1e 100644 --- a/shared/src/com/vaadin/shared/ui/grid/DetailsConnectorChange.java +++ b/shared/src/com/vaadin/shared/ui/grid/DetailsConnectorChange.java @@ -21,7 +21,7 @@ import com.vaadin.shared.Connector; /** * A description of an indexing modification for a connector. This is used by - * Grid by internal bookkeeping updates. + * Grid for internal bookkeeping updates. * * @since * @author Vaadin Ltd -- cgit v1.2.3 From aa1150e22ec2412ab999cb35cb6a0f98a32d7f88 Mon Sep 17 00:00:00 2001 From: Pekka Hyvönen Date: Thu, 19 Mar 2015 16:23:12 +0200 Subject: Fixes several issues with hidden columns in Grid (#17023) Makes sure that hidden columns are taken into account when calculating indices. This is because escalator doesn't know about hidden columns, thus need to convert cell indices to actual column indices in quite many places. Change-Id: I4c0f3097938ee9340a1922464e12ad7261084ecb --- .../vaadin/client/widget/grid/CellReference.java | 27 +++- .../client/widget/grid/EventCellReference.java | 8 +- .../client/widget/grid/RendererCellReference.java | 8 +- client/src/com/vaadin/client/widgets/Grid.java | 120 +++++++++++------ .../grid/basicfeatures/GridColumnHidingTest.java | 149 ++++++++++++++++++++- .../grid/basicfeatures/GridColumnReorderTest.java | 24 +++- .../client/grid/GridBasicClientFeaturesWidget.java | 1 + 7 files changed, 285 insertions(+), 52 deletions(-) (limited to 'client/src') diff --git a/client/src/com/vaadin/client/widget/grid/CellReference.java b/client/src/com/vaadin/client/widget/grid/CellReference.java index a2e841de43..2e50480646 100644 --- a/client/src/com/vaadin/client/widget/grid/CellReference.java +++ b/client/src/com/vaadin/client/widget/grid/CellReference.java @@ -32,6 +32,8 @@ import com.vaadin.client.widgets.Grid; * @since 7.4 */ public class CellReference { + + private int columnIndexDOM; private int columnIndex; private Grid.Column column; private final RowReference rowReference; @@ -42,13 +44,20 @@ public class CellReference { /** * Sets the identifying information for this cell. + *

+ * The difference between {@link #columnIndexDOM} and {@link #columnIndex} + * comes from hidden columns. * + * @param columnIndexDOM + * the index of the column in the DOM * @param columnIndex * the index of the column * @param column * the column object */ - public void set(int columnIndex, Grid.Column column) { + public void set(int columnIndexDOM, int columnIndex, + Grid.Column column) { + this.columnIndexDOM = columnIndexDOM; this.columnIndex = columnIndex; this.column = column; } @@ -82,6 +91,9 @@ public class CellReference { /** * Gets the index of the column. + *

+ * NOTE: The index includes hidden columns in the count, unlike + * {@link #getColumnIndexDOM()}. * * @return the index of the column */ @@ -89,6 +101,17 @@ public class CellReference { return columnIndex; } + /** + * Gets the index of the cell in the DOM. The difference to + * {@link #getColumnIndex()} is caused by hidden columns. + * + * @since + * @return the index of the column in the DOM + */ + public int getColumnIndexDOM() { + return columnIndexDOM; + } + /** * Gets the column objects. * @@ -113,7 +136,7 @@ public class CellReference { * @return the element of the cell */ public TableCellElement getElement() { - return rowReference.getElement().getCells().getItem(columnIndex); + return rowReference.getElement().getCells().getItem(columnIndexDOM); } /** diff --git a/client/src/com/vaadin/client/widget/grid/EventCellReference.java b/client/src/com/vaadin/client/widget/grid/EventCellReference.java index cf13798e11..7ca1d5de75 100644 --- a/client/src/com/vaadin/client/widget/grid/EventCellReference.java +++ b/client/src/com/vaadin/client/widget/grid/EventCellReference.java @@ -18,6 +18,7 @@ package com.vaadin.client.widget.grid; import com.google.gwt.dom.client.TableCellElement; import com.vaadin.client.widget.escalator.Cell; import com.vaadin.client.widgets.Grid; +import com.vaadin.client.widgets.Grid.Column; /** * A data class which contains information which identifies a cell being the @@ -48,11 +49,14 @@ public class EventCellReference extends CellReference { */ public void set(Cell targetCell) { int row = targetCell.getRow(); - int column = targetCell.getColumn(); + int columnIndexDOM = targetCell.getColumn(); + Column column = grid.getVisibleColumns().get(columnIndexDOM); + // At least for now we don't need to have the actual TableRowElement // available. getRowReference().set(row, grid.getDataSource().getRow(row), null); - set(column, grid.getColumn(column)); + int columnIndex = grid.getColumns().indexOf(column); + set(columnIndexDOM, columnIndex, column); this.element = targetCell.getElement(); } diff --git a/client/src/com/vaadin/client/widget/grid/RendererCellReference.java b/client/src/com/vaadin/client/widget/grid/RendererCellReference.java index 533eafded6..994db50aa0 100644 --- a/client/src/com/vaadin/client/widget/grid/RendererCellReference.java +++ b/client/src/com/vaadin/client/widget/grid/RendererCellReference.java @@ -49,12 +49,16 @@ public class RendererCellReference extends CellReference { * * @param cell * the flyweight cell to reference + * @param columnIndex + * the index of the column in the grid, including hidden cells * @param column * the column to reference */ - public void set(FlyweightCell cell, Grid.Column column) { + public void set(FlyweightCell cell, int columnIndex, + Grid.Column column) { this.cell = cell; - super.set(cell.getColumn(), (Grid.Column) column); + super.set(cell.getColumn(), columnIndex, + (Grid.Column) column); } /** diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index 9821591b91..5091d8882f 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -1449,7 +1449,7 @@ public class Grid extends ResizeComposite implements cellWrapper.appendChild(cell); - Column column = grid.getColumn(i); + Column column = grid.getVisibleColumn(i); if (column.isEditable()) { Widget editor = getHandler().getWidget(column); if (editor != null) { @@ -1913,28 +1913,32 @@ public class Grid extends ResizeComposite implements /** * Sets the currently focused. + *

+ * NOTE: the column index is the index in DOM, not the logical + * column index which includes hidden columns. * - * @param row + * @param rowIndex * the index of the row having focus - * @param column - * the index of the column having focus + * @param columnIndexDOM + * the index of the cell having focus * @param container * the row container having focus */ - private void setCellFocus(int row, int column, RowContainer container) { - if (row == rowWithFocus && cellFocusRange.contains(column) + private void setCellFocus(int rowIndex, int columnIndexDOM, + RowContainer container) { + if (rowIndex == rowWithFocus && cellFocusRange.contains(columnIndexDOM) && container == this.containerWithFocus) { refreshRow(rowWithFocus); return; } int oldRow = rowWithFocus; - rowWithFocus = row; + rowWithFocus = rowIndex; Range oldRange = cellFocusRange; if (container == escalator.getBody()) { scrollToRow(rowWithFocus); - cellFocusRange = Range.withLength(column, 1); + cellFocusRange = Range.withLength(columnIndexDOM, 1); } else { int i = 0; Element cell = container.getRowElement(rowWithFocus) @@ -1943,7 +1947,7 @@ public class Grid extends ResizeComposite implements int colSpan = cell .getPropertyInt(FlyweightCell.COLSPAN_ATTR); Range cellRange = Range.withLength(i, colSpan); - if (cellRange.contains(column)) { + if (cellRange.contains(columnIndexDOM)) { cellFocusRange = cellRange; break; } @@ -1951,10 +1955,10 @@ public class Grid extends ResizeComposite implements ++i; } while (cell != null); } - - if (column >= escalator.getColumnConfiguration() + int columnIndex = getColumns().indexOf(getVisibleColumn(columnIndexDOM)); + if (columnIndex >= escalator.getColumnConfiguration() .getFrozenColumnCount()) { - escalator.scrollToColumn(column, ScrollDestination.ANY, 10); + escalator.scrollToColumn(columnIndexDOM, ScrollDestination.ANY, 10); } if (this.containerWithFocus == container) { @@ -2000,7 +2004,7 @@ public class Grid extends ResizeComposite implements * a cell object */ public void setCellFocus(CellReference cell) { - setCellFocus(cell.getRowIndex(), cell.getColumnIndex(), + setCellFocus(cell.getRowIndex(), cell.getColumnIndexDOM(), escalator.findRowContainer(cell.getElement())); } @@ -2034,7 +2038,7 @@ public class Grid extends ResizeComposite implements --newRow; break; case KeyCodes.KEY_RIGHT: - if (cellFocusRange.getEnd() >= getColumns().size()) { + if (cellFocusRange.getEnd() >= getVisibleColumns().size()) { return; } newColumn = cellFocusRange.getEnd(); @@ -3271,6 +3275,9 @@ public class Grid extends ResizeComposite implements /** How much the grid is being auto scrolled while dragging. */ private int autoScrollX; + /** Captures the value of the focused column before reordering */ + private int focusedColumnIndex; + private void initHeaderDragElementDOM() { if (table == null) { tableHeader = DOM.createTHead(); @@ -3427,6 +3434,14 @@ public class Grid extends ResizeComposite implements reordered.remove(selectionColumn); // since setColumnOrder will // add it anyway! + // capture focused cell column before reorder + Cell focusedCell = cellFocusHandler.getFocusedCell(); + if (focusedCell != null) { + // take hidden columns into account + focusedColumnIndex = getColumns().indexOf( + getVisibleColumn(focusedCell.getColumn())); + } + Column[] array = reordered.toArray(new Column[reordered .size()]); setColumnOrder(array); @@ -3437,26 +3452,29 @@ public class Grid extends ResizeComposite implements private void transferCellFocusOnDrop() { final Cell focusedCell = cellFocusHandler.getFocusedCell(); if (focusedCell != null) { - final int focusedCellColumnIndex = focusedCell.getColumn(); + final int focusedColumnIndexDOM = focusedCell.getColumn(); final int focusedRowIndex = focusedCell.getRow(); final int draggedColumnIndex = eventCell.getColumnIndex(); // transfer focus if it was effected by the new column order final RowContainer rowContainer = escalator .findRowContainer(focusedCell.getElement()); - if (focusedCellColumnIndex == draggedColumnIndex) { + if (focusedColumnIndex == draggedColumnIndex) { // move with the dragged column - final int adjustedDropIndex = latestColumnDropIndex > draggedColumnIndex ? latestColumnDropIndex - 1 + int adjustedDropIndex = latestColumnDropIndex > draggedColumnIndex ? latestColumnDropIndex - 1 : latestColumnDropIndex; + // remove hidden columns from indexing + adjustedDropIndex = getVisibleColumns().indexOf( + getColumn(adjustedDropIndex)); cellFocusHandler.setCellFocus(focusedRowIndex, adjustedDropIndex, rowContainer); - } else if (latestColumnDropIndex <= focusedCellColumnIndex - && draggedColumnIndex > focusedCellColumnIndex) { + } else if (latestColumnDropIndex <= focusedColumnIndex + && draggedColumnIndex > focusedColumnIndex) { cellFocusHandler.setCellFocus(focusedRowIndex, - focusedCellColumnIndex + 1, rowContainer); - } else if (latestColumnDropIndex > focusedCellColumnIndex - && draggedColumnIndex < focusedCellColumnIndex) { + focusedColumnIndexDOM + 1, rowContainer); + } else if (latestColumnDropIndex > focusedColumnIndex + && draggedColumnIndex < focusedColumnIndex) { cellFocusHandler.setCellFocus(focusedRowIndex, - focusedCellColumnIndex - 1, rowContainer); + focusedColumnIndexDOM - 1, rowContainer); } } } @@ -3522,15 +3540,15 @@ public class Grid extends ResizeComposite implements private void calculatePossibleDropPositions() { possibleDropPositions.clear(); - final int draggedCellIndex = eventCell.getColumnIndex(); + final int draggedColumnIndex = eventCell.getColumnIndex(); final StaticRow draggedCellRow = header.getRow(eventCell .getRowIndex()); - final int draggedCellRightIndex = draggedCellIndex + final int draggedColumnRightIndex = draggedColumnIndex + draggedCellRow.getCell(eventCell.getColumn()) .getColspan(); final int frozenColumns = getSelectionAndFrozenColumnCount(); - final Range draggedCellRange = Range.between(draggedCellIndex, - draggedCellRightIndex); + final Range draggedCellRange = Range.between(draggedColumnIndex, + draggedColumnRightIndex); /* * If the dragged cell intersects with a spanned cell in any other * header or footer row, then the drag is limited inside that @@ -3579,7 +3597,7 @@ public class Grid extends ResizeComposite implements } // the spanned cell overlaps the dragged cell (but is // not the dragged cell) - if (cellColumnIndex <= draggedCellIndex + if (cellColumnIndex <= draggedColumnIndex && cellColumnIndex > leftBound) { leftBound = cellColumnIndex; } @@ -3607,7 +3625,9 @@ public class Grid extends ResizeComposite implements double position = getFrozenColumnsWidth(); // iterate column indices and add possible drop positions for (int i = frozenColumns; i < getColumnCount(); i++) { - if (!unavailableColumnDropIndices.contains(i)) { + Column column = getColumn(i); + if (!unavailableColumnDropIndices.contains(i) + && !column.isHidden()) { if (leftBound != -1) { if (i >= leftBound && i <= rightBound) { possibleDropPositions.put(position, i); @@ -3616,8 +3636,9 @@ public class Grid extends ResizeComposite implements possibleDropPositions.put(position, i); } } - position += getColumn(i).getWidthActual(); + position += column.getWidthActual(); } + if (leftBound == -1) { // add the right side of the last column as columns.size() possibleDropPositions.put(position, getColumnCount()); @@ -4311,8 +4332,9 @@ public class Grid extends ResizeComposite implements Renderer renderer = findRenderer(cell); if (renderer instanceof ComplexRenderer) { try { + Column column = getVisibleColumn(cell.getColumn()); rendererCellReference.set(cell, - getColumn(cell.getColumn())); + getColumns().indexOf(column), column); ((ComplexRenderer) renderer) .init(rendererCellReference); } catch (RuntimeException e) { @@ -4408,7 +4430,8 @@ public class Grid extends ResizeComposite implements cellFocusHandler.updateFocusedRowStyle(row); for (FlyweightCell cell : cellsToUpdate) { - Column column = getColumn(cell.getColumn()); + Column column = getVisibleColumn(cell.getColumn()); + final int columnIndex = getColumns().indexOf(column); assert column != null : "Column was not found from cell (" + cell.getColumn() + "," + cell.getRow() + ")"; @@ -4418,7 +4441,8 @@ public class Grid extends ResizeComposite implements if (hasData && cellStyleGenerator != null) { try { - cellReference.set(cell.getColumn(), column); + cellReference + .set(cell.getColumn(), columnIndex, column); String generatedStyle = cellStyleGenerator .getStyle(cellReference); setCustomStyleName(cell.getElement(), generatedStyle); @@ -4435,7 +4459,7 @@ public class Grid extends ResizeComposite implements Renderer renderer = column.getRenderer(); try { - rendererCellReference.set(cell, column); + rendererCellReference.set(cell, columnIndex, column); if (renderer instanceof ComplexRenderer) { // Hide cell content if needed ComplexRenderer clxRenderer = (ComplexRenderer) renderer; @@ -4509,8 +4533,9 @@ public class Grid extends ResizeComposite implements Renderer renderer = findRenderer(cell); if (renderer instanceof ComplexRenderer) { try { + Column column = getVisibleColumn(cell.getColumn()); rendererCellReference.set(cell, - getColumn(cell.getColumn())); + getColumns().indexOf(column), column); ((ComplexRenderer) renderer) .destroy(rendererCellReference); } catch (RuntimeException e) { @@ -4539,7 +4564,7 @@ public class Grid extends ResizeComposite implements @Override public void update(Row row, Iterable cellsToUpdate) { StaticSection.StaticRow staticRow = section.getRow(row.getRow()); - final List> columns = getColumns(); + final List> columns = getVisibleColumns(); setCustomStyleName(row.getElement(), staticRow.getStyleName()); @@ -4580,7 +4605,7 @@ public class Grid extends ResizeComposite implements cleanup(cell); - Column column = getColumn(cell.getColumn()); + Column column = getVisibleColumn(cell.getColumn()); SortOrder sortingOrder = getSortOrder(column); if (!headerRow.isDefault() || !column.isSortable() || sortingOrder == null) { @@ -4632,7 +4657,7 @@ public class Grid extends ResizeComposite implements @Override public void postAttach(Row row, Iterable attachedCells) { StaticSection.StaticRow gridRow = section.getRow(row.getRow()); - List> columns = getColumns(); + List> columns = getVisibleColumns(); for (FlyweightCell cell : attachedCells) { StaticSection.StaticCell metadata = gridRow.getCell(columns @@ -4662,7 +4687,7 @@ public class Grid extends ResizeComposite implements if (section.getRowCount() > row.getRow()) { StaticSection.StaticRow gridRow = section.getRow(row .getRow()); - List> columns = getColumns(); + List> columns = getVisibleColumns(); for (FlyweightCell cell : cellsToDetach) { StaticSection.StaticCell metadata = gridRow.getCell(columns .get(cell.getColumn())); @@ -4999,7 +5024,7 @@ public class Grid extends ResizeComposite implements } private Renderer findRenderer(FlyweightCell cell) { - Column column = getColumn(cell.getColumn()); + Column column = getVisibleColumn(cell.getColumn()); assert column != null : "Could not find column at index:" + cell.getColumn(); return column.getRenderer(); @@ -5081,6 +5106,8 @@ public class Grid extends ResizeComposite implements /** * Returns a column by its index in the grid. + *

+ * NOTE: The indexing includes hidden columns. * * @param index * the index of the column @@ -5095,6 +5122,15 @@ public class Grid extends ResizeComposite implements return columns.get(index); } + private Column getVisibleColumn(int index) + throws IllegalArgumentException { + List> visibleColumns = getVisibleColumns(); + if (index < 0 || index >= visibleColumns.size()) { + throw new IllegalStateException("Column not found."); + } + return visibleColumns.get(index); + } + /** * Returns the header section of this grid. The default header contains a * single row displaying the column captions. @@ -6878,8 +6914,10 @@ public class Grid extends ResizeComposite implements } columns = newOrder; + List> visibleColumns = getVisibleColumns(); + // Do ComplexRenderer.init and render new content - conf.insertColumns(0, columns.size()); + conf.insertColumns(0, visibleColumns.size()); // Update column widths. for (Column column : columns) { diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnHidingTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnHidingTest.java index 87af43af12..aca7689a0a 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnHidingTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnHidingTest.java @@ -27,8 +27,11 @@ import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.openqa.selenium.By; +import org.openqa.selenium.Keys; import org.openqa.selenium.WebElement; +import org.openqa.selenium.interactions.Actions; +import com.vaadin.testbench.elements.GridElement.GridCellElement; import com.vaadin.testbench.parallel.TestCategory; @TestCategory("grid") @@ -403,10 +406,9 @@ public class GridColumnHidingTest extends GridBasicClientFeaturesTest { verifyColumnHidingTogglesOrder(4, 3, 1, 0); } - // know issue, will be fixed in next patch @Test - @Ignore - public void testColumnHidingAndReorder_reorderingOverHiddenColumns_orderIsKept() { + public void testColumnHidingAndReorder_reorderingOverHiddenColumn_orderIsKept() { + selectMenuPath("Component", "State", "Width", "1000px"); toggleColumnReorder(); toggleHideColumnAPI(0); assertColumnHeaderOrder(1, 2, 3, 4, 5); @@ -416,6 +418,142 @@ public class GridColumnHidingTest extends GridBasicClientFeaturesTest { toggleHideColumnAPI(0); assertColumnHeaderOrder(0, 2, 1, 3, 4, 5); + + toggleHideColumnAPI(1); + assertColumnHeaderOrder(0, 2, 3, 4, 5); + + // right side of hidden column + dragAndDropColumnHeader(0, 0, 2, 5); + assertColumnHeaderOrder(2, 0, 3, 4, 5); + + toggleHideColumnAPI(1); + assertColumnHeaderOrder(2, 1, 0, 3, 4, 5); + + toggleHideColumnAPI(0); + assertColumnHeaderOrder(2, 1, 3, 4, 5); + + // left side of hidden column + dragAndDropColumnHeader(0, 0, 1, 100); + assertColumnHeaderOrder(1, 2, 3, 4, 5); + + toggleHideColumnAPI(0); + assertColumnHeaderOrder(1, 0, 2, 3, 4, 5); + } + + @Test + public void testColumnHidingAndReorder_reorderingWithMultipleHiddenColumns_works() { + selectMenuPath("Component", "State", "Width", "1000px"); + toggleColumnReorder(); + toggleHideColumnAPI(2); + toggleHideColumnAPI(3); + assertColumnHeaderOrder(0, 1, 4, 5, 6); + + dragAndDropDefaultColumnHeader(0, 2, 5); + assertColumnHeaderOrder(1, 0, 4, 5, 6); + + toggleHideColumnAPI(3); + assertColumnHeaderOrder(1, 3, 0, 4, 5, 6); + + toggleHideColumnAPI(2); + assertColumnHeaderOrder(1, 2, 3, 0, 4, 5, 6); + + toggleHideColumnAPI(0); + toggleHideColumnAPI(4); + assertColumnHeaderOrder(1, 2, 3, 5, 6); + + dragAndDropDefaultColumnHeader(4, 3, 2); + assertColumnHeaderOrder(1, 2, 3, 6, 5); + + dragAndDropDefaultColumnHeader(4, 2, 100); + assertColumnHeaderOrder(1, 2, 3, 5, 6); + + toggleHideColumnAPI(0); + assertColumnHeaderOrder(1, 2, 3, 0, 5, 6); + + toggleHideColumnAPI(4); + assertColumnHeaderOrder(1, 2, 3, 0, 4, 5, 6); + } + + @Test + public void testReorderingHiddenColumns_movingHiddenColumn_indexIsUpdated() { + selectMenuPath("Component", "State", "Width", "1000px"); + toggleHideColumnAPI(2); + toggleHideColumnAPI(3); + assertColumnHeaderOrder(0, 1, 4, 5, 6); + + moveColumnLeft(3); + assertColumnHeaderOrder(0, 1, 4, 5, 6); + + toggleHideColumnAPI(3); + assertColumnHeaderOrder(0, 1, 3, 4, 5, 6); + toggleHideColumnAPI(2); + assertColumnHeaderOrder(0, 1, 3, 2, 4, 5, 6); + + toggleHideColumnAPI(2); + toggleHideColumnAPI(3); + assertColumnHeaderOrder(0, 1, 4, 5, 6); + + moveColumnLeft(2); + moveColumnLeft(2); + moveColumnLeft(2); + assertColumnHeaderOrder(0, 1, 4, 5, 6); + + toggleHideColumnAPI(2); + assertColumnHeaderOrder(2, 0, 1, 4, 5, 6); + toggleHideColumnAPI(3); + assertColumnHeaderOrder(2, 0, 1, 3, 4, 5, 6); + } + + // keyboard actions not working in client side test case? + @Test + @Ignore + public void testNavigationWithHiddenColumns_navigatingOverHiddenColumn_goesToNextVisibleColumn() { + selectMenuPath("Component", "State", "Width", "1000px"); + toggleHideColumnAPI(2); + toggleHideColumnAPI(3); + assertColumnHeaderOrder(0, 1, 4, 5, 6); + + getGridElement().getCell(2, 4).click(); + GridCellElement cell = getGridElement().getCell(2, 4); + assertTrue(cell.isFocused()); + + new Actions(getDriver()).sendKeys(Keys.ARROW_LEFT); + cell = getGridElement().getCell(2, 1); + assertTrue(cell.isFocused()); + + new Actions(getDriver()).sendKeys(Keys.ARROW_RIGHT); + cell = getGridElement().getCell(2, 4); + assertTrue(cell.isFocused()); + } + + @Test + public void testNavigationWithHiddenColumns_hiddenFirstAndLastColumn_keepsNavigation() { + selectMenuPath("Component", "State", "Width", "1000px"); + toggleHideColumnAPI(0); + assertColumnHeaderOrder(1, 2, 3, 4, 5, 6); + + getGridElement().getCell(2, 1).click(); + assertTrue(getGridElement().getCell(2, 1).isFocused()); + + new Actions(getDriver()).sendKeys(Keys.ARROW_LEFT); + GridCellElement cell = getGridElement().getCell(2, 1); + assertTrue(cell.isFocused()); + + scrollGridHorizontallyTo(10000); + + // + getGridElement().getHeaderCell(0, 9).click(); + cell = getGridElement().getHeaderCell(0, 9); + assertTrue(cell.isFocused()); + toggleHideColumnAPI(10); + toggleHideColumnAPI(11); + + new Actions(getDriver()).sendKeys(Keys.ARROW_RIGHT); + new Actions(getDriver()).sendKeys(Keys.ARROW_RIGHT); + toggleHideColumnAPI(10); + toggleHideColumnAPI(11); + cell = getGridElement().getHeaderCell(0, 9); + assertTrue(cell.isFocused()); } private void verifyColumnHidingTogglesOrder(int... indices) { @@ -487,6 +625,11 @@ public class GridColumnHidingTest extends GridBasicClientFeaturesTest { getSidebarOpenButton().click(); } + private void moveColumnLeft(int index) { + selectMenuPath("Component", "Columns", "Column " + index, + "Move column left"); + } + private void toggleHidableColumnAPI(int columnIndex) { selectMenuPath("Component", "Columns", "Column " + columnIndex, "Hidable"); diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderTest.java index a79a954faa..84f3f9a62f 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderTest.java @@ -29,7 +29,6 @@ import com.vaadin.testbench.parallel.TestCategory; /** * - * @since * @author Vaadin Ltd */ @TestCategory("grid") @@ -179,6 +178,27 @@ public class GridColumnReorderTest extends GridBasicClientFeaturesTest { assertFocusedCell(2, 0); } + @Test + public void testColumnReorderWithHiddenColumn_draggingFocusedCellColumnOverHiddenColumn_focusIsKeptOnCell() { + // given + toggleColumnReorder(); + selectMenuPath("Component", "Columns", "Column 1", "Hidden"); + focusCell(2, 2); + assertFocusedCell(2, 2); + + // when + dragAndDropDefaultColumnHeader(1, 0, 10); + + // then + assertFocusedCell(2, 2); + + // when + dragAndDropDefaultColumnHeader(0, 2, 10); + + // then + assertFocusedCell(2, 2); + } + @Test public void testColumnReorder_dragColumnFromRightToLeftOfFocusedCellColumn_focusIsKept() { // given @@ -339,7 +359,7 @@ public class GridColumnReorderTest extends GridBasicClientFeaturesTest { selectMenuPath("Component", "Header", "Row 2", "Join columns 3, 4, 5"); dragAndDropColumnHeader(0, 0, 4, 100); scrollGridHorizontallyTo(0); - dragAndDropColumnHeader(0, 1, 4, 80); + dragAndDropColumnHeader(0, 1, 4, 100); scrollGridHorizontallyTo(0); selectMenuPath("Component", "Header", "Row 3", "Join columns 1, 2"); assertColumnHeaderOrder(1, 3, 4, 5, 2); 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 97d1a50ff6..9b28b75ac1 100644 --- a/uitest/src/com/vaadin/tests/widgetset/client/grid/GridBasicClientFeaturesWidget.java +++ b/uitest/src/com/vaadin/tests/widgetset/client/grid/GridBasicClientFeaturesWidget.java @@ -908,6 +908,7 @@ public class GridBasicClientFeaturesWidget extends public void execute() { List>> cols = grid.getColumns(); ArrayList reordered = new ArrayList(cols); + final int index = cols.indexOf(column); if (index == 0) { Column> col = reordered.remove(0); reordered.add(col); -- cgit v1.2.3 From 2a711a6e8c393b7457b9fe029d30a782945c8076 Mon Sep 17 00:00:00 2001 From: Leif Åstrand Date: Fri, 20 Mar 2015 15:50:51 +0200 Subject: Disable Sidebar API access since it will still be reworked (#17023) Change-Id: I86900955497ba8e713f48457a7b858f50f9eeb73 --- client/src/com/vaadin/client/widgets/Grid.java | 4 +- .../client/GridSidebarContentTest.java | 48 ---------------------- .../client/grid/GridBasicClientFeaturesWidget.java | 40 ------------------ 3 files changed, 2 insertions(+), 90 deletions(-) (limited to 'client/src') diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index 5091d8882f..3e201277a0 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -2901,7 +2901,7 @@ public class Grid extends ResizeComposite implements * * @since */ - public static class Sidebar extends Composite { + private static class Sidebar extends Composite { private final ClickHandler openCloseButtonHandler = new ClickHandler() { @@ -7414,7 +7414,7 @@ public class Grid extends ResizeComposite implements * @since * @return the sidebar widget for this grid */ - public Sidebar getSidebar() { + private Sidebar getSidebar() { return sidebar; } diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridSidebarContentTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridSidebarContentTest.java index 563fe890ec..fb647c7a41 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridSidebarContentTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridSidebarContentTest.java @@ -49,54 +49,6 @@ public class GridSidebarContentTest extends GridBasicClientFeaturesTest { } - @Test - public void testSidebarWithCustomContent() { - openTestURL(); - CustomGridElement gridElement = getGridElement(); - - Assert.assertEquals("Sidebar should not be initially present", 0, - countBySelector(".v-grid-sidebar")); - - selectMenuPath("Component", "Sidebar", "Toggle sidebar entry"); - - gridElement.findElement(By.className("v-grid-sidebar-button")).click(); - - WebElement sidebarButton = gridElement.findElement(By - .cssSelector(".v-grid-sidebar-content button")); - - Assert.assertEquals("Sidebar button", sidebarButton.getText()); - - sidebarButton.click(); - - Assert.assertEquals("Click count: 1", sidebarButton.getText()); - - selectMenuPath("Component", "Sidebar", "Toggle sidebar entry"); - - Assert.assertEquals("Sidebar should disappear after content remove", 0, - countBySelector(".v-grid-sidebar")); - } - - @Test - public void testProgrammaticSidebarToggle() { - openTestURL(); - - selectMenuPath("Component", "Sidebar", "Toggle sidebar visibility"); - - Assert.assertEquals("Toggling without content should't show anything", - 0, countBySelector(".v-grid-sidebar-content button")); - - selectMenuPath("Component", "Sidebar", "Toggle sidebar entry"); - selectMenuPath("Component", "Sidebar", "Toggle sidebar visibility"); - - Assert.assertEquals("Toggling with content should show sidebar", 1, - countBySelector(".v-grid-sidebar-content button")); - - selectMenuPath("Component", "Sidebar", "Toggle sidebar visibility"); - - Assert.assertEquals("Toggling again should hide sidebar", 0, - countBySelector(".v-grid-sidebar-content button")); - } - private int countBySelector(String cssSelector) { return getGridElement().findElements(By.cssSelector(cssSelector)) .size(); 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 9b28b75ac1..1fd926d726 100644 --- a/uitest/src/com/vaadin/tests/widgetset/client/grid/GridBasicClientFeaturesWidget.java +++ b/uitest/src/com/vaadin/tests/widgetset/client/grid/GridBasicClientFeaturesWidget.java @@ -80,7 +80,6 @@ import com.vaadin.client.widgets.Grid.Column; import com.vaadin.client.widgets.Grid.FooterRow; import com.vaadin.client.widgets.Grid.HeaderRow; import com.vaadin.client.widgets.Grid.SelectionMode; -import com.vaadin.client.widgets.Grid.Sidebar; import com.vaadin.tests.widgetset.client.grid.GridBasicClientFeaturesWidget.Data; /** @@ -200,17 +199,6 @@ public class GridBasicClientFeaturesWidget extends private boolean secondEditorError = false; - private Button sidebarEntry = new Button("Sidebar button", - new ClickHandler() { - private int count = 0; - - @Override - public void onClick(ClickEvent event) { - count++; - sidebarEntry.setText("Click count: " + count); - } - }); - /** * Our basic data object */ @@ -420,7 +408,6 @@ public class GridBasicClientFeaturesWidget extends createInternalsMenu(); createDataSourceMenu(); createDetailsMenu(); - createSidebarMenu(); grid.getElement().getStyle().setZIndex(0); @@ -1456,31 +1443,4 @@ public class GridBasicClientFeaturesWidget extends } }, menupath); } - - private void createSidebarMenu() { - String[] menupath = new String[] { "Component", "Sidebar" }; - - addMenuCommand("Toggle sidebar visibility", new ScheduledCommand() { - @Override - public void execute() { - Sidebar sidebar = grid.getSidebar(); - if (sidebar.isOpen()) { - sidebar.close(); - } else { - sidebar.open(); - } - } - }, menupath); - - addMenuCommand("Toggle sidebar entry", new ScheduledCommand() { - @Override - public void execute() { - if (sidebarEntry.getParent() != null) { - sidebarEntry.removeFromParent(); - } else { - grid.getSidebar().add(sidebarEntry); - } - } - }, menupath); - } } -- cgit v1.2.3 From 16c67cfab9b3dd2dbf324caa612fa3a2d15550d0 Mon Sep 17 00:00:00 2001 From: Pekka Hyvönen Date: Sun, 22 Mar 2015 22:20:57 +0200 Subject: Grid column hiding info from client to server #(17023) Fixes mismatched client-server API regarding ColumnVisibilityChangeEvent Adds and removes the column hiding toggle as needed when columns added / removed. Known bug when a hidable column added, column toggle won't get the caption of column. Change-Id: I708e19432dc822f713bf11f5b8e6eadb528a3961 --- WebContent/VAADIN/themes/base/grid/grid.scss | 20 +++- .../vaadin/client/connectors/GridConnector.java | 35 +++++- .../grid/events/ColumnVisibilityChangeEvent.java | 2 +- client/src/com/vaadin/client/widgets/Grid.java | 37 +++++-- server/src/com/vaadin/ui/Grid.java | 65 +++++++++-- .../com/vaadin/shared/ui/grid/GridServerRpc.java | 15 +++ .../grid/basicfeatures/GridBasicFeatures.java | 6 + .../server/GridColumnVisibilityTest.java | 121 ++++++++++++++++++++- 8 files changed, 278 insertions(+), 23 deletions(-) (limited to 'client/src') diff --git a/WebContent/VAADIN/themes/base/grid/grid.scss b/WebContent/VAADIN/themes/base/grid/grid.scss index 87b936a1b9..ccb7043c50 100644 --- a/WebContent/VAADIN/themes/base/grid/grid.scss +++ b/WebContent/VAADIN/themes/base/grid/grid.scss @@ -84,16 +84,17 @@ $v-grid-editor-background-color: $v-grid-row-background-color !default; .#{$primaryStyleName}-sidebar { position: absolute; top: 1px; - right : 0; - + right : 1px; + background-color: $v-grid-header-background-color; border-left: $v-grid-header-border; border-bottom: $v-grid-header-border; z-index: 5; - + .#{$primaryStyleName}-sidebar-button { height: $v-grid-header-row-height; - + text-align: right; + &:after { content: "\f0c9"; font-family: FontAwesome, sans-serif; @@ -102,6 +103,17 @@ $v-grid-editor-background-color: $v-grid-row-background-color !default; padding: 0 $v-grid-cell-padding-horizontal; } } + + .#{$primaryStyleName}-sidebar-content { + + .column-hiding-panel { + display: block; + .column-hiding-toggle { + display: block; + padding: 3px 12px; + } + } + } } // Common cell styles diff --git a/client/src/com/vaadin/client/connectors/GridConnector.java b/client/src/com/vaadin/client/connectors/GridConnector.java index 0807690023..7c568e02e5 100644 --- a/client/src/com/vaadin/client/connectors/GridConnector.java +++ b/client/src/com/vaadin/client/connectors/GridConnector.java @@ -56,6 +56,8 @@ import com.vaadin.client.widget.grid.events.BodyClickHandler; import com.vaadin.client.widget.grid.events.BodyDoubleClickHandler; import com.vaadin.client.widget.grid.events.ColumnReorderEvent; import com.vaadin.client.widget.grid.events.ColumnReorderHandler; +import com.vaadin.client.widget.grid.events.ColumnVisibilityChangeEvent; +import com.vaadin.client.widget.grid.events.ColumnVisibilityChangeHandler; import com.vaadin.client.widget.grid.events.GridClickEvent; import com.vaadin.client.widget.grid.events.GridDoubleClickEvent; import com.vaadin.client.widget.grid.events.SelectAllEvent; @@ -388,6 +390,33 @@ public class GridConnector extends AbstractHasComponentsConnector implements } }; + private ColumnVisibilityChangeHandler columnVisibilityChangeHandler = new ColumnVisibilityChangeHandler() { + + @Override + public void onVisibilityChange( + ColumnVisibilityChangeEvent event) { + if (!columnsUpdatedFromState) { + Column column = event.getColumn(); + if (column instanceof CustomGridColumn) { + getRpcProxy(GridServerRpc.class).columnVisibilityChanged( + ((CustomGridColumn) column).id, column.isHidden(), + event.isUserOriginated()); + for (GridColumnState state : getState().columns) { + if (state.id.equals(((CustomGridColumn) column).id)) { + state.hidden = event.isHidden(); + break; + } + } + } else { + getLogger().warning( + "Visibility changed for a unknown column type in Grid: " + + column.toString() + ", type " + + column.getClass()); + } + } + } + }; + private static class CustomDetailsGenerator implements DetailsGenerator { private final Map indexToDetailsMap = new HashMap(); @@ -713,6 +742,8 @@ public class GridConnector extends AbstractHasComponentsConnector implements getWidget().setEditorHandler(new CustomEditorHandler()); getWidget().addColumnReorderHandler(columnReorderHandler); + getWidget().addColumnVisibilityChangeHandler( + columnVisibilityChangeHandler); getWidget().setDetailsGenerator(customDetailsGenerator); getLayoutManager().registerDependency(this, getWidget().getElement()); @@ -734,7 +765,7 @@ public class GridConnector extends AbstractHasComponentsConnector implements if (!columnIdToColumn.containsKey(state.id)) { addColumnFromStateChangeEvent(state); } - updateColumnFromState(columnIdToColumn.get(state.id), state); + updateColumnFromStateChangeEvent(state); } } @@ -947,7 +978,9 @@ public class GridConnector extends AbstractHasComponentsConnector implements private void updateColumnFromStateChangeEvent(GridColumnState columnState) { CustomGridColumn column = columnIdToColumn.get(columnState.id); + columnsUpdatedFromState = true; updateColumnFromState(column, columnState); + columnsUpdatedFromState = false; if (columnState.rendererConnector != column.getRendererConnector()) { throw new UnsupportedOperationException( diff --git a/client/src/com/vaadin/client/widget/grid/events/ColumnVisibilityChangeEvent.java b/client/src/com/vaadin/client/widget/grid/events/ColumnVisibilityChangeEvent.java index 10bfbfad68..4c25f7a61b 100644 --- a/client/src/com/vaadin/client/widget/grid/events/ColumnVisibilityChangeEvent.java +++ b/client/src/com/vaadin/client/widget/grid/events/ColumnVisibilityChangeEvent.java @@ -60,7 +60,7 @@ public class ColumnVisibilityChangeEvent extends } /** - * Is the column hidden or visible. + * Was the column set hidden or visible. * * @return true if the column was hidden false if * it was set visible diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index 3e201277a0..3b26c8be57 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -68,7 +68,6 @@ import com.google.gwt.user.client.ui.HasEnabled; import com.google.gwt.user.client.ui.HasWidgets; import com.google.gwt.user.client.ui.ResizeComposite; import com.google.gwt.user.client.ui.ToggleButton; -import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; import com.vaadin.client.BrowserInfo; import com.vaadin.client.DeferredWorker; @@ -1926,7 +1925,8 @@ public class Grid extends ResizeComposite implements */ private void setCellFocus(int rowIndex, int columnIndexDOM, RowContainer container) { - if (rowIndex == rowWithFocus && cellFocusRange.contains(columnIndexDOM) + if (rowIndex == rowWithFocus + && cellFocusRange.contains(columnIndexDOM) && container == this.containerWithFocus) { refreshRow(rowWithFocus); return; @@ -1955,10 +1955,12 @@ public class Grid extends ResizeComposite implements ++i; } while (cell != null); } - int columnIndex = getColumns().indexOf(getVisibleColumn(columnIndexDOM)); + int columnIndex = getColumns().indexOf( + getVisibleColumn(columnIndexDOM)); if (columnIndex >= escalator.getColumnConfiguration() .getFrozenColumnCount()) { - escalator.scrollToColumn(columnIndexDOM, ScrollDestination.ANY, 10); + escalator.scrollToColumn(columnIndexDOM, ScrollDestination.ANY, + 10); } if (this.containerWithFocus == container) { @@ -3048,7 +3050,7 @@ public class Grid extends ResizeComposite implements * UI and functionality related to hiding columns with toggles in the * sidebar. */ - private final class ColumnHider extends VerticalPanel { + private final class ColumnHider extends FlowPanel { ColumnHider() { setStyleName("column-hiding-panel"); @@ -3073,7 +3075,7 @@ public class Grid extends ResizeComposite implements } private ToggleButton createToggle(final Column column) { - ToggleButton toggle = new ToggleButton(column.headerCaption); + ToggleButton toggle = new ToggleButton(); toggle.addStyleName("column-hiding-toggle"); toggle.addValueChangeHandler(new ValueChangeHandler() { @@ -3082,6 +3084,7 @@ public class Grid extends ResizeComposite implements column.setHidden(!event.getValue(), true); } }); + updateColumnHidingToggleCaption(column, toggle); columnToHidingToggleMap.put(column, toggle); return toggle; } @@ -3119,7 +3122,19 @@ public class Grid extends ResizeComposite implements } private void updateColumnHidingToggleCaption(Column column) { - columnToHidingToggleMap.get(column).setText(column.headerCaption); + updateColumnHidingToggleCaption(column, + columnToHidingToggleMap.get(column)); + } + + private void updateColumnHidingToggleCaption(Column column, + ToggleButton toggle) { + String caption = column.headerCaption; + if (caption == null || caption.isEmpty()) { + // TODO what if the content is a widget? + HeaderCell cell = getDefaultHeaderRow().getCell(column); + caption = cell.getText(); + } + toggle.setText(caption); } } @@ -5001,6 +5016,10 @@ public class Grid extends ResizeComposite implements Set events = new HashSet(); events.addAll(getConsumedEventsForRenderer(column.getRenderer())); + if (column.isHidable()) { + columnHider.updateColumnHidable(column); + } + sinkEvents(events); } @@ -5061,6 +5080,10 @@ public class Grid extends ResizeComposite implements ((Column) column).setGrid(null); columns.remove(columnIndex); + + if (column.isHidable()) { + columnHider.updateColumnHidable(column); + } } /** diff --git a/server/src/com/vaadin/ui/Grid.java b/server/src/com/vaadin/ui/Grid.java index 4a52dba173..e093a99159 100644 --- a/server/src/com/vaadin/ui/Grid.java +++ b/server/src/com/vaadin/ui/Grid.java @@ -182,7 +182,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier, } /** - * An event that is fired when a column becomes hidden or unhidden. + * An event that is fired when a column's visibility changes. * * @since */ @@ -190,6 +190,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier, private final Column column; private final boolean userOriginated; + private final boolean hidden; /** * Constructor for a column visibility change event. @@ -198,27 +199,41 @@ public class Grid extends AbstractComponent implements SelectionNotifier, * the grid from which this event originates * @param column * the column that changed its visibility + * @param hidden + * true if the column was hidden, + * false if it became visible * @param isUserOriginated * true iff the event was triggered by an UI * interaction */ public ColumnVisibilityChangeEvent(Grid source, Column column, - boolean isUserOriginated) { + boolean hidden, boolean isUserOriginated) { super(source); this.column = column; + this.hidden = hidden; userOriginated = isUserOriginated; } /** - * Gets the column that became hidden or unhidden. + * Gets the column that became hidden or visible. * - * @return the column that became hidden or unhidden. + * @return the column that became hidden or visible. * @see Column#isHidden() */ public Column getColumn() { return column; } + /** + * Was the column set hidden or visible. + * + * @return true if the column was hidden false + * if it was set visible + */ + public boolean isHidden() { + return hidden; + } + /** * Returns true if the column reorder was done by the user, * false if not and it was triggered by server side code. @@ -2828,7 +2843,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier, if (hidden != getState().hidden) { getState().hidden = hidden; grid.markAsDirty(); - grid.fireColumnVisibilityChangeEvent(this, false); + grid.fireColumnVisibilityChangeEvent(this, hidden, false); } } @@ -3358,6 +3373,42 @@ public class Grid extends AbstractComponent implements SelectionNotifier, } } + @Override + public void columnVisibilityChanged(String id, boolean hidden, + boolean userOriginated) { + final Column column = getColumnByColumnId(id); + final GridColumnState columnState = column.getState(); + + if (columnState.hidden != hidden) { + columnState.hidden = hidden; + + final String diffStateKey = "columns"; + ConnectorTracker connectorTracker = getUI() + .getConnectorTracker(); + JsonObject diffState = connectorTracker + .getDiffState(Grid.this); + + assert diffState.hasKey(diffStateKey) : "Field name has changed"; + Type type = null; + try { + type = (getState(false).getClass().getDeclaredField( + diffStateKey).getGenericType()); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } catch (SecurityException e) { + e.printStackTrace(); + } + EncodeResult encodeResult = JsonCodec.encode( + getState(false).columns, diffState, type, + connectorTracker); + + diffState.put(diffStateKey, encodeResult.getEncodedValue()); + + fireColumnVisibilityChangeEvent(column, hidden, + userOriginated); + } + } + @Override public void sendDetailsComponents(int fetchId) { getRpcProxy(GridClientRpc.class).setDetailsConnectorChanges( @@ -5455,9 +5506,9 @@ public class Grid extends AbstractComponent implements SelectionNotifier, COLUMN_VISIBILITY_METHOD); } - private void fireColumnVisibilityChangeEvent(Column column, + private void fireColumnVisibilityChangeEvent(Column column, boolean hidden, boolean isUserOriginated) { - fireEvent(new ColumnVisibilityChangeEvent(this, column, + fireEvent(new ColumnVisibilityChangeEvent(this, column, hidden, isUserOriginated)); } diff --git a/shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java b/shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java index 28f59ea93a..2b2308fe84 100644 --- a/shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java +++ b/shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java @@ -76,4 +76,19 @@ public interface GridServerRpc extends ServerRpc { * @see com.vaadin.ui.Grid#setDetailsVisible(Object, boolean) */ void sendDetailsComponents(int fetchId); + + /** + * Informs the server that the column's visibility has been changed. + * + * @since + * @param id + * the id of the column + * @param hidden + * true if hidden, false if unhidden + * @param userOriginated + * true if triggered by user, false if + * by code + */ + void columnVisibilityChanged(String id, boolean hidden, + boolean userOriginated); } diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java index c8c0e54123..d3b1237cf9 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java @@ -767,12 +767,18 @@ public class GridBasicFeatures extends AbstractComponentTest { createClickAction("Add / Remove", getColumnProperty(c), new Command() { + boolean wasHidable; + @Override public void execute(Grid grid, String value, Object data) { String columnProperty = getColumnProperty((Integer) data); if (grid.getColumn(columnProperty) == null) { grid.addColumn(columnProperty); + grid.getColumn(columnProperty).setHidable( + wasHidable); } else { + wasHidable = grid.getColumn(columnProperty) + .isHidable(); grid.removeColumn(columnProperty); } } diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridColumnVisibilityTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridColumnVisibilityTest.java index 8fb733dfa0..22a08d6748 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridColumnVisibilityTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridColumnVisibilityTest.java @@ -17,18 +17,22 @@ 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.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import java.util.List; + import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; -import com.vaadin.testbench.annotations.RunLocally; -import com.vaadin.testbench.parallel.Browser; import com.vaadin.testbench.parallel.TestCategory; import com.vaadin.tests.components.grid.basicfeatures.GridBasicFeaturesTest; @TestCategory("grid") -@RunLocally(Browser.PHANTOMJS) public class GridColumnVisibilityTest extends GridBasicFeaturesTest { private static final String[] TOGGLE_LISTENER = new String[] { "Component", @@ -40,6 +44,8 @@ public class GridColumnVisibilityTest extends GridBasicFeaturesTest { + "changed: propertyId: Column 0, isHidden: true"; private static final String COLUMN_0_BECAME_UNHIDDEN_MSG = "Visibility " + "changed: propertyId: Column 0, isHidden: false"; + private static final String USER_ORIGINATED_TRUE = "userOriginated: true"; + private static final String USER_ORIGINATED_FALSE = "userOriginated: false"; @Before public void setUp() { @@ -72,9 +78,11 @@ public class GridColumnVisibilityTest extends GridBasicFeaturesTest { selectMenuPath(TOGGLE_HIDE_COLUMN_0); assertTrue(logContainsText(COLUMN_0_BECAME_HIDDEN_MSG)); + assertTrue(logContainsText(USER_ORIGINATED_FALSE)); selectMenuPath(TOGGLE_HIDE_COLUMN_0); assertTrue(logContainsText(COLUMN_0_BECAME_UNHIDDEN_MSG)); + assertTrue(logContainsText(USER_ORIGINATED_FALSE)); } @Test @@ -86,4 +94,111 @@ public class GridColumnVisibilityTest extends GridBasicFeaturesTest { selectMenuPath(TOGGLE_HIDE_COLUMN_0); assertFalse(logContainsText(COLUMN_0_BECAME_UNHIDDEN_MSG)); } + + @Test + public void testColumnHiding_userOriginated_correctParams() { + selectMenuPath(TOGGLE_LISTENER); + toggleColumnHidable(0); + assertColumnHeaderOrder(0, 1, 2, 3); + + getSidebarOpenButton().click(); + getColumnHidingToggle(0).click(); + getSidebarOpenButton().click(); + + assertColumnHeaderOrder(1, 2, 3); + assertTrue(logContainsText(COLUMN_0_BECAME_HIDDEN_MSG)); + assertTrue(logContainsText(USER_ORIGINATED_TRUE)); + + getSidebarOpenButton().click(); + getColumnHidingToggle(0).click(); + getSidebarOpenButton().click(); + + assertColumnHeaderOrder(0, 1, 2, 3); + assertTrue(logContainsText(COLUMN_0_BECAME_UNHIDDEN_MSG)); + assertTrue(logContainsText(USER_ORIGINATED_TRUE)); + + getSidebarOpenButton().click(); + getColumnHidingToggle(0).click(); + getSidebarOpenButton().click(); + + assertColumnHeaderOrder(1, 2, 3); + assertTrue(logContainsText(COLUMN_0_BECAME_HIDDEN_MSG)); + assertTrue(logContainsText(USER_ORIGINATED_TRUE)); + } + + @Test + public void testColumnHiding_whenHidableColumnRemoved_toggleRemoved() { + toggleColumnHidable(0); + toggleColumnHidable(1); + getSidebarOpenButton().click(); + assertNotNull(getColumnHidingToggle(0)); + + addRemoveColumn(0); + + assertNull(getColumnHidingToggle(0)); + } + + @Test + @Ignore + // known issue, column caption not passed to toggle when added again + public void testColumnHiding_whenHidableColumnAdded_toggleAdded() { + selectMenuPath("Component", "Size", "Width", "100%"); + toggleColumnHidable(0); + toggleColumnHidable(1); + addRemoveColumn(0); + addRemoveColumn(4); + addRemoveColumn(5); + addRemoveColumn(6); + addRemoveColumn(7); + addRemoveColumn(8); + addRemoveColumn(9); + addRemoveColumn(10); + assertColumnHeaderOrder(1, 2, 3, 11); + + getSidebarOpenButton().click(); + assertNull(getColumnHidingToggle(0)); + getSidebarOpenButton().click(); + + addRemoveColumn(0); + assertColumnHeaderOrder(1, 2, 3, 11, 0); + + getSidebarOpenButton().click(); + assertNotNull(getColumnHidingToggle(0)); + } + + private void toggleColumnHidable(int index) { + selectMenuPath("Component", "Columns", "Column " + index, "Hidable"); + } + + private void addRemoveColumn(int index) { + selectMenuPath("Component", "Columns", "Column " + index, + "Add / Remove"); + } + + private WebElement getSidebar() { + List elements = findElements(By.className("v-grid-sidebar")); + return elements.isEmpty() ? null : elements.get(0); + } + + private WebElement getSidebarOpenButton() { + List elements = findElements(By + .className("v-grid-sidebar-button")); + return elements.isEmpty() ? null : elements.get(0); + } + + /** + * Returns the toggle inside the sidebar for hiding the column at the given + * index, or null if not found. + */ + private WebElement getColumnHidingToggle(int columnIndex) { + WebElement sidebar = getSidebar(); + List elements = sidebar.findElements(By + .className("column-hiding-toggle")); + for (WebElement e : elements) { + if (("Column " + columnIndex).equalsIgnoreCase(e.getText())) { + return e; + } + } + return null; + } } -- cgit v1.2.3 From 1575b9cb003db8c336166c8a717413c9418cd2e2 Mon Sep 17 00:00:00 2001 From: Pekka Hyvönen Date: Mon, 23 Mar 2015 15:47:50 +0200 Subject: Fixes Grid column reordering for IE8 (#17023) Change-Id: I44373a22fb279d5fd260fec0cfaf52d4a3f4e9a3 --- .../vaadin/client/widget/grid/AutoScroller.java | 25 +++++++--------------- 1 file changed, 8 insertions(+), 17 deletions(-) (limited to 'client/src') diff --git a/client/src/com/vaadin/client/widget/grid/AutoScroller.java b/client/src/com/vaadin/client/widget/grid/AutoScroller.java index 773dc012c8..83d8bf0b62 100644 --- a/client/src/com/vaadin/client/widget/grid/AutoScroller.java +++ b/client/src/com/vaadin/client/widget/grid/AutoScroller.java @@ -18,7 +18,6 @@ package com.vaadin.client.widget.grid; import com.google.gwt.animation.client.AnimationScheduler; import com.google.gwt.animation.client.AnimationScheduler.AnimationCallback; import com.google.gwt.animation.client.AnimationScheduler.AnimationHandle; -import com.google.gwt.dom.client.BrowserEvents; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.dom.client.TableElement; @@ -456,8 +455,7 @@ public class AutoScroller { } /** - * Starts the automatic scrolling detection. The given event must be a touch - * start or a left mouse triggered mouse down event. + * Starts the automatic scrolling detection. * * @param startEvent * the event that starts the automatic scroll @@ -468,19 +466,12 @@ public class AutoScroller { */ public void start(final NativeEvent startEvent, ScrollAxis scrollAxis, AutoScrollerCallback callback) { - if (BrowserEvents.TOUCHSTART.equals(startEvent.getType()) - || (BrowserEvents.MOUSEDOWN.equals(startEvent.getType()) && startEvent - .getButton() == NativeEvent.BUTTON_LEFT)) { - scrollDirection = scrollAxis; - this.callback = callback; - injectNativeHandler(); - start(startEvent); - startEvent.preventDefault(); - startEvent.stopPropagation(); - } else { - throw new IllegalStateException("received unexpected event: " - + startEvent.getType()); - } + scrollDirection = scrollAxis; + this.callback = callback; + injectNativeHandler(); + start(); + startEvent.preventDefault(); + startEvent.stopPropagation(); } /** @@ -526,7 +517,7 @@ public class AutoScroller { return scrollAreaPX; } - private void start(final NativeEvent event) { + private void start() { /* * bounds are updated whenever the autoscroll cycle starts, to make sure * that the widget hasn't changed in size, moved around, or whatnot. -- cgit v1.2.3 From 754caf060f1473f2367c421ecd70f3a8966f6f10 Mon Sep 17 00:00:00 2001 From: Henrik Paul Date: Mon, 23 Mar 2015 15:35:26 +0200 Subject: Adds Escalator.scrollToSpacer (#17270) Change-Id: Ib420e8da6c167fdba9d3023a73cb242643c7af67 --- .../src/com/vaadin/client/widgets/Escalator.java | 159 +++++++++++++++------ .../EscalatorBasicClientFeaturesTest.java | 2 + .../escalator/EscalatorSpacerTest.java | 37 +++++ .../grid/EscalatorBasicClientFeaturesWidget.java | 13 ++ 4 files changed, 171 insertions(+), 40 deletions(-) (limited to 'client/src') diff --git a/client/src/com/vaadin/client/widgets/Escalator.java b/client/src/com/vaadin/client/widgets/Escalator.java index 75b797eb1f..87f19d2ded 100644 --- a/client/src/com/vaadin/client/widgets/Escalator.java +++ b/client/src/com/vaadin/client/widgets/Escalator.java @@ -827,8 +827,8 @@ public class Escalator extends Widget implements RequiresResize, boolean verticalScrollNeeded = scrollContentHeight > tableWrapperHeight + WidgetUtil.PIXEL_EPSILON - - header.heightOfSection - - footer.heightOfSection; + - header.getHeightOfSection() + - footer.getHeightOfSection(); boolean horizontalScrollNeeded = scrollContentWidth > tableWrapperWidth + WidgetUtil.PIXEL_EPSILON; @@ -837,8 +837,8 @@ public class Escalator extends Widget implements RequiresResize, if (!verticalScrollNeeded && horizontalScrollNeeded) { verticalScrollNeeded = scrollContentHeight > tableWrapperHeight + WidgetUtil.PIXEL_EPSILON - - header.heightOfSection - - footer.heightOfSection + - header.getHeightOfSection() + - footer.getHeightOfSection() - horizontalScrollbar.getScrollbarThickness(); } else { horizontalScrollNeeded = scrollContentWidth > tableWrapperWidth @@ -860,8 +860,10 @@ public class Escalator extends Widget implements RequiresResize, tableWrapper.getStyle().setHeight(tableWrapperHeight, Unit.PX); tableWrapper.getStyle().setWidth(tableWrapperWidth, Unit.PX); + double footerHeight = footer.getHeightOfSection(); + double headerHeight = header.getHeightOfSection(); double vScrollbarHeight = Math.max(0, tableWrapperHeight - - footer.heightOfSection - header.heightOfSection); + - footerHeight - headerHeight); verticalScrollbar.setOffsetSize(vScrollbarHeight); verticalScrollbar.setScrollSize(scrollContentHeight); @@ -1158,7 +1160,7 @@ public class Escalator extends Widget implements RequiresResize, final double viewportStartPx = getScrollTop(); final double viewportEndPx = viewportStartPx - + body.calculateHeight(); + + body.getHeightOfSection(); final double scrollTop = getScrollPos(destination, targetStartPx, targetEndPx, viewportStartPx, viewportEndPx, padding); @@ -1183,9 +1185,6 @@ public class Escalator extends Widget implements RequiresResize, */ protected final TableSectionElement root; - /** The height of the combined rows in the DOM. Never negative. */ - protected double heightOfSection = 0; - /** * The primary style name of the escalator. Most commonly provided by * Escalator as "v-escalator". @@ -2097,10 +2096,24 @@ public class Escalator extends Widget implements RequiresResize, refreshCells(rowRange, colRange); } } + + /** + * The height of this table section. + *

+ * Note that {@link Escalator#getBody() the body} will calculate its + * height, while the others will return a precomputed value. + * + * @return the height of this table section + */ + protected abstract double getHeightOfSection(); } private abstract class AbstractStaticRowContainer extends AbstractRowContainer { + + /** The height of the combined rows in the DOM. Never negative. */ + private double heightOfSection = 0; + public AbstractStaticRowContainer(final TableSectionElement headElement) { super(headElement); } @@ -2194,7 +2207,8 @@ public class Escalator extends Widget implements RequiresResize, * indices are calculated from the scrollbar position. */ verticalScrollbar.setOffsetSize(heightOfEscalator - - header.heightOfSection - footer.heightOfSection); + - header.getHeightOfSection() + - footer.getHeightOfSection()); body.verifyEscalatorCount(); } @@ -2246,6 +2260,11 @@ public class Escalator extends Widget implements RequiresResize, assert root.isOrHasChild(tr) : "Row does not belong to this table section"; return true; } + + @Override + protected double getHeightOfSection() { + return Math.max(0, heightOfSection); + } } private class HeaderRowContainer extends AbstractStaticRowContainer { @@ -2255,10 +2274,10 @@ public class Escalator extends Widget implements RequiresResize, @Override protected void sectionHeightCalculated() { - bodyElem.getStyle().setMarginTop(heightOfSection, Unit.PX); + bodyElem.getStyle().setMarginTop(getHeightOfSection(), Unit.PX); verticalScrollbar.getElement().getStyle() - .setTop(heightOfSection, Unit.PX); - headerDeco.getStyle().setHeight(heightOfSection, Unit.PX); + .setTop(getHeightOfSection(), Unit.PX); + headerDeco.getStyle().setHeight(getHeightOfSection(), Unit.PX); } @Override @@ -2291,8 +2310,10 @@ public class Escalator extends Widget implements RequiresResize, @Override protected void sectionHeightCalculated() { + double headerHeight = header.getHeightOfSection(); + double footerHeight = footer.getHeightOfSection(); int vscrollHeight = (int) Math.floor(heightOfEscalator - - header.heightOfSection - footer.heightOfSection); + - headerHeight - footerHeight); final boolean horizontalScrollbarNeeded = columnConfiguration .calculateRowWidth() > widthOfEscalator; @@ -2300,7 +2321,8 @@ public class Escalator extends Widget implements RequiresResize, vscrollHeight -= horizontalScrollbar.getScrollbarThickness(); } - footerDeco.getStyle().setHeight(footer.heightOfSection, Unit.PX); + footerDeco.getStyle().setHeight(footer.getHeightOfSection(), + Unit.PX); verticalScrollbar.setOffsetSize(vscrollHeight); } @@ -2633,7 +2655,7 @@ public class Escalator extends Widget implements RequiresResize, * getDefaultRowHeight() < getScrollTop(); final boolean addedRowsBelowCurrentViewport = index * getDefaultRowHeight() > getScrollTop() - + calculateHeight(); + + getHeightOfSection(); if (addedRowsAboveCurrentViewport) { /* @@ -2917,7 +2939,7 @@ public class Escalator extends Widget implements RequiresResize, private int getMaxEscalatorRowCapacity() { final int maxEscalatorRowCapacity = (int) Math - .ceil(calculateHeight() / getDefaultRowHeight()) + 1; + .ceil(getHeightOfSection() / getDefaultRowHeight()) + 1; /* * maxEscalatorRowCapacity can become negative if the headers and @@ -3087,7 +3109,7 @@ public class Escalator extends Widget implements RequiresResize, final double contentBottom = getRowCount() * getDefaultRowHeight(); final double viewportBottom = tBodyScrollTop - + calculateHeight(); + + getHeightOfSection(); if (viewportBottom <= contentBottom) { /* * We're in the middle of the row container, everything @@ -3219,7 +3241,7 @@ public class Escalator extends Widget implements RequiresResize, * 5 5 */ final double newScrollTop = contentBottom - - calculateHeight(); + - getHeightOfSection(); setScrollTop(newScrollTop); /* * Manually call the scroll handler, so we get immediate @@ -3406,15 +3428,14 @@ public class Escalator extends Widget implements RequiresResize, return "td"; } - /** - * Calculates the height of the {@code } as it should be rendered - * in the DOM. - */ - private double calculateHeight() { + @Override + protected double getHeightOfSection() { final int tableHeight = tableWrapper.getOffsetHeight(); - final double footerHeight = footer.heightOfSection; - final double headerHeight = header.heightOfSection; - return tableHeight - footerHeight - headerHeight; + final double footerHeight = footer.getHeightOfSection(); + final double headerHeight = header.getHeightOfSection(); + + double heightOfSection = tableHeight - footerHeight - headerHeight; + return Math.max(0, heightOfSection); } @Override @@ -3862,9 +3883,14 @@ public class Escalator extends Widget implements RequiresResize, return visualRowOrder.contains(tr); } - public void reapplySpacerWidths() { + void reapplySpacerWidths() { spacerContainer.reapplySpacerWidths(); } + + void scrollToSpacer(int spacerIndex, ScrollDestination destination, + int padding) { + spacerContainer.scrollToSpacer(spacerIndex, destination, padding); + } } private class ColumnConfigurationImpl implements ColumnConfiguration { @@ -4723,6 +4749,32 @@ public class Escalator extends Widget implements RequiresResize, } } + @SuppressWarnings("boxing") + void scrollToSpacer(int spacerIndex, ScrollDestination destination, + int padding) { + + assert !destination.equals(ScrollDestination.MIDDLE) + || padding != 0 : "destination/padding check should be done before this method"; + + if (!rowIndexToSpacer.containsKey(spacerIndex)) { + throw new IllegalArgumentException("No spacer open at index " + + spacerIndex); + } + + SpacerImpl spacer = rowIndexToSpacer.get(spacerIndex); + double targetStartPx = spacer.getTop(); + double targetEndPx = targetStartPx + spacer.getHeight(); + + Range viewportPixels = getViewportPixels(); + double viewportStartPx = viewportPixels.getStart(); + double viewportEndPx = viewportPixels.getEnd(); + + double scrollTop = getScrollPos(destination, targetStartPx, + targetEndPx, viewportStartPx, viewportEndPx, padding); + + setScrollTop(scrollTop); + } + public void reapplySpacerWidths() { for (SpacerImpl spacer : rowIndexToSpacer.values()) { spacer.getRootElement().getStyle() @@ -5812,10 +5864,7 @@ public class Escalator extends Widget implements RequiresResize, public void scrollToColumn(final int columnIndex, final ScrollDestination destination, final int padding) throws IndexOutOfBoundsException, IllegalArgumentException { - if (destination == ScrollDestination.MIDDLE && padding != 0) { - throw new IllegalArgumentException( - "You cannot have a padding with a MIDDLE destination"); - } + validateScrollDestination(destination, padding); verifyValidColumnIndex(columnIndex); if (columnIndex < columnConfiguration.frozenColumns) { @@ -5856,10 +5905,7 @@ public class Escalator extends Widget implements RequiresResize, public void scrollToRow(final int rowIndex, final ScrollDestination destination, final int padding) throws IndexOutOfBoundsException, IllegalArgumentException { - if (destination == ScrollDestination.MIDDLE && padding != 0) { - throw new IllegalArgumentException( - "You cannot have a padding with a MIDDLE destination"); - } + validateScrollDestination(destination, padding); verifyValidRowIndex(rowIndex); scroller.scrollToRow(rowIndex, destination, padding); @@ -5872,6 +5918,39 @@ public class Escalator extends Widget implements RequiresResize, } } + /** + * Scrolls the body vertically so that the spacer at the given row index is + * visible and there is at least {@literal padding} pixesl to the given + * scroll destination + * + * @since + * @param spacerIndex + * the row index of the spacer to scroll to + * @param destination + * where the spacer should be aligned visually after scrolling + * @param padding + * the number of pixels to place between the scrolled-to spacer + * and the viewport edge + * @throws IllegalArgumentException + * if {@code spacerIndex} is not an opened spacer if + * {@code destination} is {@link ScrollDestination#MIDDLE} and + * padding is nonzero + */ + public void scrollToSpacer(final int spacerIndex, + ScrollDestination destination, final int padding) + throws IllegalArgumentException { + validateScrollDestination(destination, padding); + body.scrollToSpacer(spacerIndex, destination, padding); + } + + private static void validateScrollDestination( + final ScrollDestination destination, final int padding) { + if (destination == ScrollDestination.MIDDLE && padding != 0) { + throw new IllegalArgumentException( + "You cannot have a padding with a MIDDLE destination"); + } + } + /** * Recalculates the dimensions for all elements that require manual * calculations. Also updates the dimension caches. @@ -6074,8 +6153,8 @@ public class Escalator extends Widget implements RequiresResize, return; } - double headerHeight = header.heightOfSection; - double footerHeight = footer.heightOfSection; + double headerHeight = header.getHeightOfSection(); + double footerHeight = footer.getHeightOfSection(); double bodyHeight = body.getDefaultRowHeight() * heightByRows; double scrollbar = horizontalScrollbar.showsScrollHandle() ? horizontalScrollbar .getScrollbarThickness() : 0; @@ -6271,8 +6350,8 @@ public class Escalator extends Widget implements RequiresResize, private Range getViewportPixels() { int from = (int) Math.floor(verticalScrollbar.getScrollPos()); - int to = (int) Math.ceil(body.heightOfSection); - return Range.between(from, to); + int to = (int) body.getHeightOfSection(); + return Range.withLength(from, to); } @Override 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 04c0933866..de254b4deb 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/EscalatorBasicClientFeaturesTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/EscalatorBasicClientFeaturesTest.java @@ -77,11 +77,13 @@ public abstract class EscalatorBasicClientFeaturesTest extends MultiBrowserTest protected static final String COLSPAN_NONE = "Apply no colspan"; protected static final String SET_100PX = "Set 100px"; protected static final String SPACERS = "Spacers"; + protected static final String SCROLL_HERE_ANY_0PADDING = "Scroll here (ANY, 0)"; protected static final String REMOVE = "Remove"; protected static final String ROW_MINUS1 = "Row -1"; protected static final String ROW_1 = "Row 1"; protected static final String ROW_25 = "Row 25"; + protected static final String ROW_50 = "Row 50"; protected static final String ROW_75 = "Row 75"; protected static final String ROW_99 = "Row 99"; 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 index 1e9771ab13..a0fa961ad2 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/escalator/EscalatorSpacerTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/escalator/EscalatorSpacerTest.java @@ -30,6 +30,7 @@ import org.junit.Test; import org.openqa.selenium.WebElement; import com.vaadin.client.WidgetUtil; +import com.vaadin.shared.ui.grid.Range; import com.vaadin.testbench.elements.NotificationElement; import com.vaadin.tests.components.grid.basicfeatures.EscalatorBasicClientFeaturesTest; @@ -96,6 +97,7 @@ public class EscalatorSpacerTest extends EscalatorBasicClientFeaturesTest { @Before public void before() { openTestURL(); + selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, "Set 20px default height"); populate(); } @@ -299,6 +301,41 @@ public class EscalatorSpacerTest extends EscalatorBasicClientFeaturesTest { } } + @Test + public void scrollToSpacerFromAbove() throws Exception { + selectMenuPath(FEATURES, SPACERS, ROW_50, SET_100PX); + selectMenuPath(FEATURES, SPACERS, ROW_50, SCROLL_HERE_ANY_0PADDING); + + // Browsers might vary with a few pixels. + Range allowableScrollRange = Range.between(765, 780); + int scrollTop = (int) getScrollTop(); + assertTrue("Scroll position was not " + allowableScrollRange + ", but " + + scrollTop, allowableScrollRange.contains(scrollTop)); + } + + @Test + public void scrollToSpacerFromBelow() throws Exception { + selectMenuPath(FEATURES, SPACERS, ROW_50, SET_100PX); + scrollVerticallyTo(999999); + selectMenuPath(FEATURES, SPACERS, ROW_50, SCROLL_HERE_ANY_0PADDING); + + // Browsers might vary with a few pixels. + Range allowableScrollRange = Range.between(1015, 1025); + int scrollTop = (int) getScrollTop(); + assertTrue("Scroll position was not " + allowableScrollRange + ", but " + + scrollTop, allowableScrollRange.contains(scrollTop)); + } + + @Test + public void scrollToSpacerAlreadyInViewport() throws Exception { + selectMenuPath(FEATURES, SPACERS, ROW_50, SET_100PX); + scrollVerticallyTo(1000); + selectMenuPath(FEATURES, SPACERS, ROW_50, SCROLL_HERE_ANY_0PADDING); + + // Browsers might vary with a few pixels. + assertEquals(getScrollTop(), 1000); + } + private static double[] getElementDimensions(WebElement element) { /* * we need to parse the style attribute, since using getCssValue gets a 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 0d4aa305d9..4cf4726c28 100644 --- a/uitest/src/com/vaadin/tests/widgetset/client/grid/EscalatorBasicClientFeaturesWidget.java +++ b/uitest/src/com/vaadin/tests/widgetset/client/grid/EscalatorBasicClientFeaturesWidget.java @@ -585,6 +585,13 @@ public class EscalatorBasicClientFeaturesWidget extends } }, scrollToRowMenuPath); } + + addMenuCommand("Set 20px default height", new ScheduledCommand() { + @Override + public void execute() { + escalator.getBody().setDefaultRowHeight(20); + } + }, menupath); } private void createRowsMenu(final RowContainer container, String[] menupath) { @@ -685,6 +692,12 @@ public class EscalatorBasicClientFeaturesWidget extends escalator.getBody().setSpacer(rowIndex, -1); } }, menupath); + addMenuCommand("Scroll here (ANY, 0)", new ScheduledCommand() { + @Override + public void execute() { + escalator.scrollToSpacer(rowIndex, ScrollDestination.ANY, 0); + } + }, menupath); } private void insertRows(final RowContainer container, int offset, int number) { -- cgit v1.2.3 From ff5f4e29731e534a85c5c22fab292eea0476237b Mon Sep 17 00:00:00 2001 From: Leif Åstrand Date: Wed, 25 Mar 2015 14:19:43 +0200 Subject: Avoid using VButton because of vaadin-widgets dependencies (#17023) Change-Id: Id1d42ee9555600eb117feadddcc92151dc2c5e1c --- client/src/com/vaadin/client/widgets/Grid.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'client/src') diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index 3b26c8be57..f980285b6b 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -78,7 +78,6 @@ import com.vaadin.client.renderers.ComplexRenderer; import com.vaadin.client.renderers.Renderer; import com.vaadin.client.renderers.WidgetRenderer; import com.vaadin.client.ui.SubPartAware; -import com.vaadin.client.ui.VButton; import com.vaadin.client.ui.dd.DragAndDropHandler; import com.vaadin.client.ui.dd.DragAndDropHandler.DragAndDropCallback; import com.vaadin.client.widget.escalator.Cell; @@ -2921,7 +2920,7 @@ public class Grid extends ResizeComposite implements private final FlowPanel content; - private final VButton openCloseButton; + private final Button openCloseButton; private final Grid grid; @@ -2931,7 +2930,7 @@ public class Grid extends ResizeComposite implements rootContainer = new FlowPanel(); initWidget(rootContainer); - openCloseButton = new VButton(); + openCloseButton = new Button(); openCloseButton.addClickHandler(openCloseButtonHandler); rootContainer.add(openCloseButton); -- cgit v1.2.3 From 6a7437cc96da860e50297e064abe7aef387c9e2c Mon Sep 17 00:00:00 2001 From: Henrik Paul Date: Tue, 24 Mar 2015 15:11:23 +0200 Subject: Fixes edge case in null details generation for Grid (#17274) Change-Id: I1bf4c2f0600baea8b925bd31dcd42c1e901a7c8b --- .../vaadin/client/connectors/GridConnector.java | 28 +++++++++++--- .../com/vaadin/data/RpcDataProviderExtension.java | 5 ++- .../shared/ui/grid/DetailsConnectorChange.java | 45 +++++++++++++++++++++- .../server/GridDetailsServerTest.java | 26 ++++++++++++- 4 files changed, 93 insertions(+), 11 deletions(-) (limited to 'client/src') diff --git a/client/src/com/vaadin/client/connectors/GridConnector.java b/client/src/com/vaadin/client/connectors/GridConnector.java index 7c568e02e5..51e986933c 100644 --- a/client/src/com/vaadin/client/connectors/GridConnector.java +++ b/client/src/com/vaadin/client/connectors/GridConnector.java @@ -19,6 +19,7 @@ package com.vaadin.client.connectors; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -677,8 +678,13 @@ public class GridConnector extends AbstractHasComponentsConnector implements customDetailsGenerator .setDetailsConnectorChanges(connectorChanges); + List removedFirst = new ArrayList( + connectorChanges); + Collections.sort(removedFirst, + DetailsConnectorChange.REMOVED_FIRST_COMPARATOR); + // refresh moved/added details rows - for (DetailsConnectorChange change : connectorChanges) { + for (DetailsConnectorChange change : removedFirst) { Integer oldIndex = change.getOldIndex(); Integer newIndex = change.getNewIndex(); @@ -689,13 +695,23 @@ public class GridConnector extends AbstractHasComponentsConnector implements + "invalid new index: " + newIndex + " (connector: " + change.getConnector() + ")"; - Integer index = newIndex; - if (index == null) { - index = oldIndex; + if (oldIndex != null) { + /* Close the old/removed index */ + getWidget().setDetailsVisible(oldIndex, false); + + if (change.isShouldStillBeVisible()) { + getWidget().setDetailsVisible(oldIndex, true); + } } - getWidget().setDetailsVisible(index, false); - getWidget().setDetailsVisible(index, true); + if (newIndex != null) { + /* + * Since the component was lazy loaded, we need to + * refresh the details by toggling it. + */ + getWidget().setDetailsVisible(newIndex, false); + getWidget().setDetailsVisible(newIndex, true); + } } detailsConnectorFetcher.responseReceived(fetchId); } diff --git a/server/src/com/vaadin/data/RpcDataProviderExtension.java b/server/src/com/vaadin/data/RpcDataProviderExtension.java index 66c17c4afa..a21a81244a 100644 --- a/server/src/com/vaadin/data/RpcDataProviderExtension.java +++ b/server/src/com/vaadin/data/RpcDataProviderExtension.java @@ -787,7 +787,7 @@ public class RpcDataProviderExtension extends AbstractExtension { if (!SharedUtil.equals(oldIndex, newIndex)) { changes.add(new DetailsConnectorChange(component, oldIndex, - newIndex)); + newIndex, emptyDetails.containsKey(component))); } } @@ -798,7 +798,8 @@ public class RpcDataProviderExtension extends AbstractExtension { Component component = entry.getValue(); Integer newIndex = rowIndexToDetails.inverse().get(component); if (newIndex == null) { - changes.add(new DetailsConnectorChange(null, oldIndex, null)); + changes.add(new DetailsConnectorChange(null, oldIndex, + null, emptyDetails.containsValue(oldIndex))); } } diff --git a/shared/src/com/vaadin/shared/ui/grid/DetailsConnectorChange.java b/shared/src/com/vaadin/shared/ui/grid/DetailsConnectorChange.java index 5b80f27b1e..171a7738a6 100644 --- a/shared/src/com/vaadin/shared/ui/grid/DetailsConnectorChange.java +++ b/shared/src/com/vaadin/shared/ui/grid/DetailsConnectorChange.java @@ -16,6 +16,7 @@ package com.vaadin.shared.ui.grid; import java.io.Serializable; +import java.util.Comparator; import com.vaadin.shared.Connector; @@ -28,9 +29,25 @@ import com.vaadin.shared.Connector; */ public class DetailsConnectorChange implements Serializable { + public static final Comparator REMOVED_FIRST_COMPARATOR = new Comparator() { + @Override + public int compare(DetailsConnectorChange a, DetailsConnectorChange b) { + boolean deleteA = a.getNewIndex() == null; + boolean deleteB = b.getNewIndex() == null; + if (deleteA && !deleteB) { + return -1; + } else if (!deleteA && deleteB) { + return 1; + } else { + return 0; + } + } + }; + private Connector connector; private Integer oldIndex; private Integer newIndex; + private boolean shouldStillBeVisible; /** Create a new connector index change */ public DetailsConnectorChange() { @@ -48,12 +65,15 @@ public class DetailsConnectorChange implements Serializable { * the old index * @param newIndex * the new index + * @param shouldStillBeVisible + * details should be visible regardless of {@code connector} */ public DetailsConnectorChange(Connector connector, Integer oldIndex, - Integer newIndex) { + Integer newIndex, boolean shouldStillBeVisible) { this.connector = connector; this.oldIndex = oldIndex; this.newIndex = newIndex; + this.shouldStillBeVisible = shouldStillBeVisible; assert assertStateIsOk(); } @@ -144,4 +164,27 @@ public class DetailsConnectorChange implements Serializable { public void setNewIndex(Integer newIndex) { this.newIndex = newIndex; } + + /** + * Checks whether whether the details should remain open, even if connector + * might be null. + * + * @return true iff the details should remain open, even if + * connector might be null + */ + public boolean isShouldStillBeVisible() { + return shouldStillBeVisible; + } + + /** + * Sets whether the details should remain open, even if connector might be + * null. + * + * @param shouldStillBeVisible + * true iff the details should remain open, even if + * connector might be null + */ + public void setShouldStillBeVisible(boolean shouldStillBeVisible) { + this.shouldStillBeVisible = shouldStillBeVisible; + } } diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridDetailsServerTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridDetailsServerTest.java index 7ddd903161..cfaf79ea05 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridDetailsServerTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridDetailsServerTest.java @@ -80,6 +80,22 @@ public class GridDetailsServerTest extends GridBasicFeaturesTest { getGridElement().getDetails(0); } + @Test + public void openVisiblePopulatedDetails() { + selectMenuPath(DETAILS_GENERATOR_WATCHING); + selectMenuPath(OPEN_FIRST_ITEM_DETAILS); + assertNotNull("details should've populated", getGridElement() + .getDetails(0).findElement(By.className("v-widget"))); + } + + @Test(expected = NoSuchElementException.class) + public void closeVisiblePopulatedDetails() { + selectMenuPath(DETAILS_GENERATOR_WATCHING); + selectMenuPath(OPEN_FIRST_ITEM_DETAILS); + selectMenuPath(OPEN_FIRST_ITEM_DETAILS); + getGridElement().getDetails(0); + } + @Test public void openDetailsOutsideOfActiveRange() throws InterruptedException { getGridElement().scroll(10000); @@ -260,8 +276,14 @@ public class GridDetailsServerTest extends GridBasicFeaturesTest { selectMenuPath(OPEN_FIRST_ITEM_DETAILS); selectMenuPath(DETAILS_GENERATOR_WATCHING); selectMenuPath(DETAILS_GENERATOR_NULL); - assertTrue("Details should be empty with null component", - getGridElement().getDetails(0).getText().isEmpty()); + + try { + assertTrue("Details should be empty with null component", + getGridElement().getDetails(0).getText().isEmpty()); + } catch (NoSuchElementException e) { + fail("Expected to find a details row with empty content"); + } + selectMenuPath(DETAILS_GENERATOR_WATCHING); assertFalse("Details should be not empty with details component", getGridElement().getDetails(0).getText().isEmpty()); -- cgit v1.2.3 From dd2c4c9e7b6f1b4f0a930cfd5c5e24e536b7e7b6 Mon Sep 17 00:00:00 2001 From: Leif Åstrand Date: Wed, 25 Mar 2015 14:35:06 +0200 Subject: Hide DragAndDropHandler in Grid to make vaadin-widgets compile (#16643) Change-Id: I231affa4a4242d3b958583c9b79a4f08be0d5de5 --- .../vaadin/client/ui/dd/DragAndDropHandler.java | 248 --------------------- client/src/com/vaadin/client/widgets/Grid.java | 227 ++++++++++++++++++- 2 files changed, 224 insertions(+), 251 deletions(-) delete mode 100644 client/src/com/vaadin/client/ui/dd/DragAndDropHandler.java (limited to 'client/src') diff --git a/client/src/com/vaadin/client/ui/dd/DragAndDropHandler.java b/client/src/com/vaadin/client/ui/dd/DragAndDropHandler.java deleted file mode 100644 index 0710606818..0000000000 --- a/client/src/com/vaadin/client/ui/dd/DragAndDropHandler.java +++ /dev/null @@ -1,248 +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.client.ui.dd; - -import java.util.logging.Logger; - -import com.google.gwt.dom.client.NativeEvent; -import com.google.gwt.event.dom.client.KeyCodes; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.Event.NativePreviewEvent; -import com.google.gwt.user.client.Event.NativePreviewHandler; -import com.google.gwt.user.client.ui.RootPanel; -import com.vaadin.client.WidgetUtil; -import com.vaadin.client.widgets.Grid; - -/** - * A simple event handler for elements that can be drag and dropped. Loosely - * based on {@link VDragAndDropManager}, but without any Vaadin related stuff. - * Properly handles drag start, cancel and end. For example, used in - * {@link Grid} column header reordering. - *

- * The showing of the dragged element, drag hints and reacting to drop/cancel is - * delegated to {@link DragAndDropCallback} implementation. - * - * @since - * @author Vaadin Ltd - */ -public class DragAndDropHandler { - - /** - * Callback interface for drag and drop. - */ - public interface DragAndDropCallback { - /** - * Called when the drag has started. The drag can be canceled by - * returning {@code false}. - * - * @param startEvent - * the original event that started the drag - * @return {@code true} if the drag is OK to start, {@code false} to - * cancel - */ - boolean onDragStart(NativeEvent startEvent); - - /** - * Called on drag. - * - * @param event - * the event related to the drag - */ - void onDragUpdate(NativePreviewEvent event); - - /** - * Called after the has ended on a drop or cancel. - */ - void onDragEnd(); - - /** - * Called when the drag has ended on a drop. - */ - void onDrop(); - - /** - * Called when the drag has been canceled. - */ - void onDragCancel(); - } - - private HandlerRegistration dragStartNativePreviewHandlerRegistration; - private HandlerRegistration dragHandlerRegistration; - - private boolean dragging; - - private DragAndDropCallback callback; - - private final NativePreviewHandler dragHandler = new NativePreviewHandler() { - - @Override - public void onPreviewNativeEvent(NativePreviewEvent event) { - if (dragging) { - final int typeInt = event.getTypeInt(); - switch (typeInt) { - case Event.ONKEYDOWN: - int keyCode = event.getNativeEvent().getKeyCode(); - if (keyCode == KeyCodes.KEY_ESCAPE) { - // end drag if ESC is hit - cancelDrag(event); - } - break; - case Event.ONMOUSEMOVE: - case Event.ONTOUCHMOVE: - callback.onDragUpdate(event); - // prevent text selection on IE - event.getNativeEvent().preventDefault(); - break; - case Event.ONTOUCHCANCEL: - cancelDrag(event); - break; - case Event.ONTOUCHEND: - /* Avoid simulated event on drag end */ - event.getNativeEvent().preventDefault(); - //$FALL-THROUGH$ - case Event.ONMOUSEUP: - callback.onDragUpdate(event); - callback.onDrop(); - stopDrag(); - event.cancel(); - break; - default: - break; - } - } else { - stopDrag(); - } - } - - }; - - private static Logger getLogger() { - return Logger.getLogger(DragAndDropHandler.class.getName()); - } - - /** - * This method can be called to trigger drag and drop on any grid element - * that can be dragged and dropped. - * - * @param dragStartingEvent - * the drag triggering event, usually a {@link Event#ONMOUSEDOWN} - * or {@link Event#ONTOUCHSTART} event on the draggable element - * - * @param callback - * the callback that will handle actual drag and drop related - * operations - */ - public void onDragStartOnDraggableElement( - final NativeEvent dragStartingEvent, - final DragAndDropCallback callback) { - dragStartNativePreviewHandlerRegistration = Event - .addNativePreviewHandler(new NativePreviewHandler() { - - private int startX = WidgetUtil - .getTouchOrMouseClientX(dragStartingEvent); - private int startY = WidgetUtil - .getTouchOrMouseClientY(dragStartingEvent); - - @Override - public void onPreviewNativeEvent(NativePreviewEvent event) { - final int typeInt = event.getTypeInt(); - if (typeInt == -1 - && event.getNativeEvent().getType() - .toLowerCase().contains("pointer")) { - /* - * Ignore PointerEvents since IE10 and IE11 send - * also MouseEvents for backwards compatibility. - */ - return; - } - switch (typeInt) { - case Event.ONMOUSEOVER: - case Event.ONMOUSEOUT: - // we don't care - break; - case Event.ONKEYDOWN: - case Event.ONKEYPRESS: - case Event.ONKEYUP: - case Event.ONBLUR: - case Event.ONFOCUS: - // don't cancel possible drag start - break; - case Event.ONMOUSEMOVE: - case Event.ONTOUCHMOVE: - int currentX = WidgetUtil - .getTouchOrMouseClientX(event - .getNativeEvent()); - int currentY = WidgetUtil - .getTouchOrMouseClientY(event - .getNativeEvent()); - if (Math.abs(startX - currentX) > 3 - || Math.abs(startY - currentY) > 3) { - removeNativePreviewHandlerRegistration(); - startDrag(dragStartingEvent, event, callback); - } - break; - default: - // on any other events, clean up this preview - // listener - removeNativePreviewHandlerRegistration(); - break; - } - } - }); - } - - private void startDrag(NativeEvent startEvent, - NativePreviewEvent triggerEvent, DragAndDropCallback callback) { - if (callback.onDragStart(startEvent)) { - dragging = true; - // just capture something to prevent text selection in IE - Event.setCapture(RootPanel.getBodyElement()); - this.callback = callback; - dragHandlerRegistration = Event - .addNativePreviewHandler(dragHandler); - callback.onDragUpdate(triggerEvent); - } - } - - private void stopDrag() { - dragging = false; - if (dragHandlerRegistration != null) { - dragHandlerRegistration.removeHandler(); - dragHandlerRegistration = null; - } - Event.releaseCapture(RootPanel.getBodyElement()); - if (callback != null) { - callback.onDragEnd(); - callback = null; - } - } - - private void cancelDrag(NativePreviewEvent event) { - callback.onDragCancel(); - callback.onDragEnd(); - stopDrag(); - event.cancel(); - event.getNativeEvent().preventDefault(); - } - - private void removeNativePreviewHandlerRegistration() { - if (dragStartNativePreviewHandlerRegistration != null) { - dragStartNativePreviewHandlerRegistration.removeHandler(); - dragStartNativePreviewHandlerRegistration = null; - } - } -} diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index f980285b6b..15dd45ec21 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -67,6 +67,7 @@ import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.HasEnabled; import com.google.gwt.user.client.ui.HasWidgets; import com.google.gwt.user.client.ui.ResizeComposite; +import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.ToggleButton; import com.google.gwt.user.client.ui.Widget; import com.vaadin.client.BrowserInfo; @@ -78,8 +79,6 @@ import com.vaadin.client.renderers.ComplexRenderer; import com.vaadin.client.renderers.Renderer; import com.vaadin.client.renderers.WidgetRenderer; import com.vaadin.client.ui.SubPartAware; -import com.vaadin.client.ui.dd.DragAndDropHandler; -import com.vaadin.client.ui.dd.DragAndDropHandler.DragAndDropCallback; import com.vaadin.client.widget.escalator.Cell; import com.vaadin.client.widget.escalator.ColumnConfiguration; import com.vaadin.client.widget.escalator.EscalatorUpdater; @@ -211,6 +210,228 @@ public class Grid extends ResizeComposite implements HEADER, BODY, FOOTER } + /** + * A simple event handler for elements that can be drag and dropped. Loosely + * based on {@link com.vaadin.client.ui.dd.VDragAndDropManager}, but without + * any Vaadin related stuff. Properly handles drag start, cancel and end. + * For example, used in {@link Grid} column header reordering. + *

+ * The showing of the dragged element, drag hints and reacting to + * drop/cancel is delegated to {@link DragAndDropCallback} implementation. + * + * @author Vaadin Ltd + */ + private static class DragAndDropHandler { + + /** + * Callback interface for drag and drop. + */ + public interface DragAndDropCallback { + /** + * Called when the drag has started. The drag can be canceled by + * returning {@code false}. + * + * @param startEvent + * the original event that started the drag + * @return {@code true} if the drag is OK to start, {@code false} to + * cancel + */ + boolean onDragStart(NativeEvent startEvent); + + /** + * Called on drag. + * + * @param event + * the event related to the drag + */ + void onDragUpdate(NativePreviewEvent event); + + /** + * Called after the has ended on a drop or cancel. + */ + void onDragEnd(); + + /** + * Called when the drag has ended on a drop. + */ + void onDrop(); + + /** + * Called when the drag has been canceled. + */ + void onDragCancel(); + } + + private HandlerRegistration dragStartNativePreviewHandlerRegistration; + private HandlerRegistration dragHandlerRegistration; + + private boolean dragging; + + private DragAndDropCallback callback; + + private final NativePreviewHandler dragHandler = new NativePreviewHandler() { + + @Override + public void onPreviewNativeEvent(NativePreviewEvent event) { + if (dragging) { + final int typeInt = event.getTypeInt(); + switch (typeInt) { + case Event.ONKEYDOWN: + int keyCode = event.getNativeEvent().getKeyCode(); + if (keyCode == KeyCodes.KEY_ESCAPE) { + // end drag if ESC is hit + cancelDrag(event); + } + break; + case Event.ONMOUSEMOVE: + case Event.ONTOUCHMOVE: + callback.onDragUpdate(event); + // prevent text selection on IE + event.getNativeEvent().preventDefault(); + break; + case Event.ONTOUCHCANCEL: + cancelDrag(event); + break; + case Event.ONTOUCHEND: + /* Avoid simulated event on drag end */ + event.getNativeEvent().preventDefault(); + //$FALL-THROUGH$ + case Event.ONMOUSEUP: + callback.onDragUpdate(event); + callback.onDrop(); + stopDrag(); + event.cancel(); + break; + default: + break; + } + } else { + stopDrag(); + } + } + + }; + + private static Logger getLogger() { + return Logger.getLogger(DragAndDropHandler.class.getName()); + } + + /** + * This method can be called to trigger drag and drop on any grid + * element that can be dragged and dropped. + * + * @param dragStartingEvent + * the drag triggering event, usually a + * {@link Event#ONMOUSEDOWN} or {@link Event#ONTOUCHSTART} + * event on the draggable element + * + * @param callback + * the callback that will handle actual drag and drop related + * operations + */ + public void onDragStartOnDraggableElement( + final NativeEvent dragStartingEvent, + final DragAndDropCallback callback) { + dragStartNativePreviewHandlerRegistration = Event + .addNativePreviewHandler(new NativePreviewHandler() { + + private int startX = WidgetUtil + .getTouchOrMouseClientX(dragStartingEvent); + private int startY = WidgetUtil + .getTouchOrMouseClientY(dragStartingEvent); + + @Override + public void onPreviewNativeEvent( + NativePreviewEvent event) { + final int typeInt = event.getTypeInt(); + if (typeInt == -1 + && event.getNativeEvent().getType() + .toLowerCase().contains("pointer")) { + /* + * Ignore PointerEvents since IE10 and IE11 send + * also MouseEvents for backwards compatibility. + */ + return; + } + switch (typeInt) { + case Event.ONMOUSEOVER: + case Event.ONMOUSEOUT: + // we don't care + break; + case Event.ONKEYDOWN: + case Event.ONKEYPRESS: + case Event.ONKEYUP: + case Event.ONBLUR: + case Event.ONFOCUS: + // don't cancel possible drag start + break; + case Event.ONMOUSEMOVE: + case Event.ONTOUCHMOVE: + int currentX = WidgetUtil + .getTouchOrMouseClientX(event + .getNativeEvent()); + int currentY = WidgetUtil + .getTouchOrMouseClientY(event + .getNativeEvent()); + if (Math.abs(startX - currentX) > 3 + || Math.abs(startY - currentY) > 3) { + removeNativePreviewHandlerRegistration(); + startDrag(dragStartingEvent, event, + callback); + } + break; + default: + // on any other events, clean up this preview + // listener + removeNativePreviewHandlerRegistration(); + break; + } + } + }); + } + + private void startDrag(NativeEvent startEvent, + NativePreviewEvent triggerEvent, DragAndDropCallback callback) { + if (callback.onDragStart(startEvent)) { + dragging = true; + // just capture something to prevent text selection in IE + Event.setCapture(RootPanel.getBodyElement()); + this.callback = callback; + dragHandlerRegistration = Event + .addNativePreviewHandler(dragHandler); + callback.onDragUpdate(triggerEvent); + } + } + + private void stopDrag() { + dragging = false; + if (dragHandlerRegistration != null) { + dragHandlerRegistration.removeHandler(); + dragHandlerRegistration = null; + } + Event.releaseCapture(RootPanel.getBodyElement()); + if (callback != null) { + callback.onDragEnd(); + callback = null; + } + } + + private void cancelDrag(NativePreviewEvent event) { + callback.onDragCancel(); + callback.onDragEnd(); + stopDrag(); + event.cancel(); + event.getNativeEvent().preventDefault(); + } + + private void removeNativePreviewHandlerRegistration() { + if (dragStartNativePreviewHandlerRegistration != null) { + dragStartNativePreviewHandlerRegistration.removeHandler(); + dragStartNativePreviewHandlerRegistration = null; + } + } + } + /** * Abstract base class for Grid header and footer sections. * @@ -3238,7 +3459,7 @@ public class Grid extends ResizeComposite implements private AutoScroller autoScroller = new AutoScroller(this); - private DragAndDropCallback headerCellDndCallback = new DragAndDropCallback() { + private DragAndDropHandler.DragAndDropCallback headerCellDndCallback = new DragAndDropHandler.DragAndDropCallback() { private final AutoScrollerCallback autoScrollerCallback = new AutoScrollerCallback() { -- cgit v1.2.3 From da95f395add4916d0544858c87b448ddc5d03881 Mon Sep 17 00:00:00 2001 From: Pekka Hyvönen Date: Tue, 24 Mar 2015 15:19:35 +0200 Subject: Fix Grid's frozen column count with hidden columns #17273 Change-Id: I4f8a893eec3cf7c32da34cb364a4d56589cbf3e2 --- client/src/com/vaadin/client/widgets/Grid.java | 28 +++++++- server/src/com/vaadin/ui/Grid.java | 3 + .../grid/basicfeatures/GridColumnHidingTest.java | 75 ++++++++++++++++++++++ .../server/GridColumnVisibilityTest.java | 69 ++++++++++++++++++++ 4 files changed, 174 insertions(+), 1 deletion(-) (limited to 'client/src') diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index 15dd45ec21..5ed2f2f949 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -4279,8 +4279,23 @@ public class Grid extends ResizeComposite implements this.hidden = hidden; } else { this.hidden = hidden; + + final int columnIndex = grid.getVisibleColumns().indexOf( + this); grid.escalator.getColumnConfiguration().insertColumns( - grid.getVisibleColumns().indexOf(this), 1); + columnIndex, 1); + + // make sure column is set to frozen if it needs to be, + // escalator doesn't handle situation where the added column + // would be the last frozen column + int gridFrozenColumns = grid.getFrozenColumnCount(); + int escalatorFrozenColumns = grid.escalator + .getColumnConfiguration().getFrozenColumnCount(); + if (gridFrozenColumns > escalatorFrozenColumns + && escalatorFrozenColumns == columnIndex) { + grid.escalator.getColumnConfiguration() + .setFrozenColumnCount(++escalatorFrozenColumns); + } } grid.columnHider.updateToggleValue(this); scheduleColumnWidthRecalculator(); @@ -5826,6 +5841,14 @@ public class Grid extends ResizeComposite implements private void updateFrozenColumns() { int numberOfColumns = frozenColumnCount; + // for the escalator the hidden columns are not in the frozen column + // count, but for grid they are. thus need to convert the index + for (int i = 0; i < frozenColumnCount; i++) { + if (getColumn(i).isHidden()) { + numberOfColumns--; + } + } + if (numberOfColumns == -1) { numberOfColumns = 0; } else if (selectionColumn != null) { @@ -5842,6 +5865,9 @@ public class Grid extends ResizeComposite implements * columns will be frozen, but the built-in selection checkbox column will * still be frozen if it's in use. -1 means that not even the selection * column is frozen. + *

+ * NOTE: This includes {@link Column#isHidden() hidden columns} in + * the count. * * @return the number of frozen columns */ diff --git a/server/src/com/vaadin/ui/Grid.java b/server/src/com/vaadin/ui/Grid.java index e093a99159..62b3a74f05 100644 --- a/server/src/com/vaadin/ui/Grid.java +++ b/server/src/com/vaadin/ui/Grid.java @@ -3954,6 +3954,9 @@ public class Grid extends AbstractComponent implements SelectionNotifier, * columns will be frozen, but the built-in selection checkbox column will * still be frozen if it's in use. -1 means that not even the selection * column is frozen. + *

+ * NOTE: this count includes {@link Column#isHidden() hidden + * columns} in the count. * * @see #setFrozenColumnCount(int) * diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnHidingTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnHidingTest.java index aca7689a0a..c12c769c63 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnHidingTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnHidingTest.java @@ -556,6 +556,81 @@ public class GridColumnHidingTest extends GridBasicClientFeaturesTest { assertTrue(cell.isFocused()); } + @Test + public void testFrozenColumnHiding_lastFrozenColumnHidden_isFrozenWhenMadeVisible() { + toggleFrozenColumns(2); + toggleHidableColumnAPI(0); + toggleHidableColumnAPI(1); + getSidebarOpenButton().click(); + verifyColumnIsFrozen(0); + verifyColumnIsFrozen(1); + verifyColumnIsNotFrozen(2); + assertColumnHeaderOrder(0, 1, 2, 3); + + getColumnHidingToggle(1).click(); + verifyColumnIsFrozen(0); + // the grid element indexing doesn't take hidden columns into account! + verifyColumnIsNotFrozen(1); + assertColumnHeaderOrder(0, 2, 3); + + getColumnHidingToggle(0).click(); + verifyColumnIsNotFrozen(0); + assertColumnHeaderOrder(2, 3, 4); + + getColumnHidingToggle(0).click(); + assertColumnHeaderOrder(0, 2, 3); + verifyColumnIsFrozen(0); + verifyColumnIsNotFrozen(1); + + getColumnHidingToggle(1).click(); + assertColumnHeaderOrder(0, 1, 2, 3); + verifyColumnIsFrozen(0); + verifyColumnIsFrozen(1); + verifyColumnIsNotFrozen(2); + } + + @Test + public void testFrozenColumnHiding_columnHiddenFrozenCountChanged_columnIsFrozenWhenVisible() { + toggleHidableColumnAPI(1); + toggleHidableColumnAPI(2); + getSidebarOpenButton().click(); + getColumnHidingToggle(1).click(); + getColumnHidingToggle(2).click(); + assertColumnHeaderOrder(0, 3, 4); + + toggleFrozenColumns(3); + verifyColumnIsFrozen(0); + // the grid element indexing doesn't take hidden columns into account! + verifyColumnIsNotFrozen(1); + verifyColumnIsNotFrozen(2); + + getColumnHidingToggle(2).click(); + verifyColumnIsFrozen(0); + verifyColumnIsFrozen(1); + verifyColumnIsNotFrozen(2); + verifyColumnIsNotFrozen(3); + + getColumnHidingToggle(1).click(); + verifyColumnIsFrozen(0); + verifyColumnIsFrozen(1); + verifyColumnIsFrozen(2); + verifyColumnIsNotFrozen(3); + verifyColumnIsNotFrozen(4); + } + + private void toggleFrozenColumns(int count) { + selectMenuPath("Component", "State", "Frozen column count", count + + " columns"); + } + + private void verifyColumnIsFrozen(int index) { + assertTrue(getGridElement().getHeaderCell(0, index).isFrozen()); + } + + private void verifyColumnIsNotFrozen(int index) { + assertFalse(getGridElement().getHeaderCell(0, index).isFrozen()); + } + private void verifyColumnHidingTogglesOrder(int... indices) { WebElement sidebar = getSidebar(); List elements = sidebar.findElements(By diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridColumnVisibilityTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridColumnVisibilityTest.java index 22a08d6748..37eda1c28f 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridColumnVisibilityTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridColumnVisibilityTest.java @@ -166,6 +166,75 @@ public class GridColumnVisibilityTest extends GridBasicFeaturesTest { assertNotNull(getColumnHidingToggle(0)); } + @Test + public void testFrozenColumnHiding_hiddenColumnMadeFrozen_frozenWhenMadeVisible() { + selectMenuPath("Component", "Size", "Width", "100%"); + toggleColumnHidable(0); + toggleColumnHidable(1); + getSidebarOpenButton().click(); + getColumnHidingToggle(0).click(); + getColumnHidingToggle(1).click(); + + assertColumnHeaderOrder(2, 3, 4, 5); + + setFrozenColumns(2); + verifyColumnNotFrozen(0); + verifyColumnNotFrozen(1); + + getColumnHidingToggle(0).click(); + assertColumnHeaderOrder(0, 2, 3, 4, 5); + verifyColumnFrozen(0); + verifyColumnNotFrozen(1); + + getColumnHidingToggle(1).click(); + assertColumnHeaderOrder(0, 1, 2, 3, 4, 5); + verifyColumnFrozen(0); + verifyColumnFrozen(1); + verifyColumnNotFrozen(2); + } + + @Test + public void testFrozenColumnHiding_hiddenFrozenColumnUnfrozen_notFrozenWhenMadeVisible() { + selectMenuPath("Component", "Size", "Width", "100%"); + toggleColumnHidable(0); + toggleColumnHidable(1); + setFrozenColumns(2); + verifyColumnFrozen(0); + verifyColumnFrozen(1); + verifyColumnNotFrozen(2); + verifyColumnNotFrozen(3); + + getSidebarOpenButton().click(); + getColumnHidingToggle(0).click(); + getColumnHidingToggle(1).click(); + assertColumnHeaderOrder(2, 3, 4, 5); + verifyColumnNotFrozen(0); + verifyColumnNotFrozen(1); + + setFrozenColumns(0); + verifyColumnNotFrozen(0); + verifyColumnNotFrozen(1); + + getColumnHidingToggle(0).click(); + assertColumnHeaderOrder(0, 2, 3, 4, 5); + verifyColumnNotFrozen(0); + verifyColumnNotFrozen(1); + + getColumnHidingToggle(1).click(); + assertColumnHeaderOrder(0, 1, 2, 3, 4, 5); + verifyColumnNotFrozen(0); + verifyColumnNotFrozen(1); + verifyColumnNotFrozen(2); + } + + private void verifyColumnFrozen(int index) { + assertTrue(getGridElement().getHeaderCell(0, index).isFrozen()); + } + + private void verifyColumnNotFrozen(int index) { + assertFalse(getGridElement().getHeaderCell(0, index).isFrozen()); + } + private void toggleColumnHidable(int index) { selectMenuPath("Component", "Columns", "Column " + index, "Hidable"); } -- cgit v1.2.3 From eb3406247e397c23d447bf4fd84a5052a0488876 Mon Sep 17 00:00:00 2001 From: Leif Åstrand Date: Thu, 26 Mar 2015 11:27:21 +0200 Subject: Update all empty since tags published in alpha1 Change-Id: I1afce7e69beb9a61354fd82fcda194d4277dfd36 --- .../client/connectors/RpcDataSourceConnector.java | 2 +- .../client/data/AbstractRemoteDataSource.java | 2 +- .../client/widget/escalator/RowContainer.java | 2 +- .../com/vaadin/client/widget/escalator/Spacer.java | 2 +- .../client/widget/escalator/SpacerUpdater.java | 2 +- .../vaadin/client/widget/grid/AutoScroller.java | 2 +- .../vaadin/client/widget/grid/CellReference.java | 2 +- .../client/widget/grid/DetailsGenerator.java | 2 +- .../widget/grid/events/ColumnReorderEvent.java | 2 +- .../widget/grid/events/ColumnReorderHandler.java | 2 +- .../grid/events/ColumnVisibilityChangeEvent.java | 2 +- .../grid/events/ColumnVisibilityChangeHandler.java | 2 +- .../src/com/vaadin/client/widgets/Escalator.java | 4 +-- client/src/com/vaadin/client/widgets/Grid.java | 38 +++++++++++----------- .../com/vaadin/data/RpcDataProviderExtension.java | 6 ++-- server/src/com/vaadin/ui/Grid.java | 38 +++++++++++----------- .../shared/ui/grid/DetailsConnectorChange.java | 2 +- .../com/vaadin/shared/ui/grid/GridClientRpc.java | 2 +- .../com/vaadin/shared/ui/grid/GridServerRpc.java | 6 ++-- 19 files changed, 60 insertions(+), 60 deletions(-) (limited to 'client/src') diff --git a/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java b/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java index e8c7ee5286..627ee74eca 100644 --- a/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java +++ b/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java @@ -48,7 +48,7 @@ public class RpcDataSourceConnector extends AbstractExtensionConnector { * A callback interface to let {@link GridConnector} know that detail * visibilities might have changed. * - * @since + * @since 7.5.0 * @author Vaadin Ltd */ interface DetailsListener { diff --git a/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java b/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java index 152b66f2ca..88977d85ec 100644 --- a/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java +++ b/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java @@ -341,7 +341,7 @@ public abstract class AbstractRemoteDataSource implements DataSource { * A hook that can be overridden to do something whenever a row is dropped * from the cache. * - * @since + * @since 7.5.0 * @param rowIndex * the index of the dropped row */ diff --git a/client/src/com/vaadin/client/widget/escalator/RowContainer.java b/client/src/com/vaadin/client/widget/escalator/RowContainer.java index b7aae1f4f3..abab25046c 100644 --- a/client/src/com/vaadin/client/widget/escalator/RowContainer.java +++ b/client/src/com/vaadin/client/widget/escalator/RowContainer.java @@ -39,7 +39,7 @@ public interface RowContainer { *

* The body section can contain both rows and spacers. * - * @since + * @since 7.5.0 * @author Vaadin Ltd * @see com.vaadin.client.widgets.Escalator#getBody() */ diff --git a/client/src/com/vaadin/client/widget/escalator/Spacer.java b/client/src/com/vaadin/client/widget/escalator/Spacer.java index 1e9985d6c1..6ccef88f0d 100644 --- a/client/src/com/vaadin/client/widget/escalator/Spacer.java +++ b/client/src/com/vaadin/client/widget/escalator/Spacer.java @@ -21,7 +21,7 @@ import com.google.gwt.dom.client.Element; * A representation of a spacer element in a * {@link com.vaadin.client.widget.escalator.RowContainer.BodyRowContainer}. * - * @since + * @since 7.5.0 * @author Vaadin Ltd */ public interface Spacer { diff --git a/client/src/com/vaadin/client/widget/escalator/SpacerUpdater.java b/client/src/com/vaadin/client/widget/escalator/SpacerUpdater.java index 01d715fdd0..49adefd536 100644 --- a/client/src/com/vaadin/client/widget/escalator/SpacerUpdater.java +++ b/client/src/com/vaadin/client/widget/escalator/SpacerUpdater.java @@ -23,7 +23,7 @@ import com.vaadin.client.widget.escalator.RowContainer.BodyRowContainer; * The updater is responsible for making sure all elements are properly * constructed and cleaned up. * - * @since + * @since 7.5.0 * @author Vaadin Ltd * @see Spacer * @see BodyRowContainer diff --git a/client/src/com/vaadin/client/widget/grid/AutoScroller.java b/client/src/com/vaadin/client/widget/grid/AutoScroller.java index 83d8bf0b62..f2e44196ec 100644 --- a/client/src/com/vaadin/client/widget/grid/AutoScroller.java +++ b/client/src/com/vaadin/client/widget/grid/AutoScroller.java @@ -34,7 +34,7 @@ import com.vaadin.client.widgets.Grid; * Grid when the cursor is close enough the edge of the body of the grid, * depending on the scroll direction chosen. * - * @since + * @since 7.5.0 * @author Vaadin Ltd */ public class AutoScroller { diff --git a/client/src/com/vaadin/client/widget/grid/CellReference.java b/client/src/com/vaadin/client/widget/grid/CellReference.java index 2e50480646..e783cb92ae 100644 --- a/client/src/com/vaadin/client/widget/grid/CellReference.java +++ b/client/src/com/vaadin/client/widget/grid/CellReference.java @@ -105,7 +105,7 @@ public class CellReference { * Gets the index of the cell in the DOM. The difference to * {@link #getColumnIndex()} is caused by hidden columns. * - * @since + * @since 7.5.0 * @return the index of the column in the DOM */ public int getColumnIndexDOM() { diff --git a/client/src/com/vaadin/client/widget/grid/DetailsGenerator.java b/client/src/com/vaadin/client/widget/grid/DetailsGenerator.java index 103bf96291..b9427091a7 100644 --- a/client/src/com/vaadin/client/widget/grid/DetailsGenerator.java +++ b/client/src/com/vaadin/client/widget/grid/DetailsGenerator.java @@ -20,7 +20,7 @@ import com.google.gwt.user.client.ui.Widget; /** * A callback interface for generating details for a particular row in Grid. * - * @since + * @since 7.5.0 * @author Vaadin Ltd */ public interface DetailsGenerator { diff --git a/client/src/com/vaadin/client/widget/grid/events/ColumnReorderEvent.java b/client/src/com/vaadin/client/widget/grid/events/ColumnReorderEvent.java index c72da0c48e..1712871089 100644 --- a/client/src/com/vaadin/client/widget/grid/events/ColumnReorderEvent.java +++ b/client/src/com/vaadin/client/widget/grid/events/ColumnReorderEvent.java @@ -23,7 +23,7 @@ import com.google.gwt.event.shared.GwtEvent; * @param * The row type of the grid. The row type is the POJO type from where * the data is retrieved into the column cells. - * @since + * @since 7.5.0 * @author Vaadin Ltd */ public class ColumnReorderEvent extends GwtEvent> { diff --git a/client/src/com/vaadin/client/widget/grid/events/ColumnReorderHandler.java b/client/src/com/vaadin/client/widget/grid/events/ColumnReorderHandler.java index 4733ed8bc0..29c476058e 100644 --- a/client/src/com/vaadin/client/widget/grid/events/ColumnReorderHandler.java +++ b/client/src/com/vaadin/client/widget/grid/events/ColumnReorderHandler.java @@ -24,7 +24,7 @@ import com.google.gwt.event.shared.EventHandler; * @param * The row type of the grid. The row type is the POJO type from where * the data is retrieved into the column cells. - * @since + * @since 7.5.0 * @author Vaadin Ltd */ public interface ColumnReorderHandler extends EventHandler { diff --git a/client/src/com/vaadin/client/widget/grid/events/ColumnVisibilityChangeEvent.java b/client/src/com/vaadin/client/widget/grid/events/ColumnVisibilityChangeEvent.java index 4c25f7a61b..63b788bcf2 100644 --- a/client/src/com/vaadin/client/widget/grid/events/ColumnVisibilityChangeEvent.java +++ b/client/src/com/vaadin/client/widget/grid/events/ColumnVisibilityChangeEvent.java @@ -25,7 +25,7 @@ import com.vaadin.client.widgets.Grid.Column; * @param * The row type of the grid. The row type is the POJO type from where * the data is retrieved into the column cells. - * @since + * @since 7.5.0 * @author Vaadin Ltd */ public class ColumnVisibilityChangeEvent extends diff --git a/client/src/com/vaadin/client/widget/grid/events/ColumnVisibilityChangeHandler.java b/client/src/com/vaadin/client/widget/grid/events/ColumnVisibilityChangeHandler.java index 10a7660954..542fe4e3c1 100644 --- a/client/src/com/vaadin/client/widget/grid/events/ColumnVisibilityChangeHandler.java +++ b/client/src/com/vaadin/client/widget/grid/events/ColumnVisibilityChangeHandler.java @@ -23,7 +23,7 @@ import com.google.gwt.event.shared.EventHandler; * * @param The row type of the grid. The row type is the POJO type from where * the data is retrieved into the column cells. - * @since + * @since 7.5.0 * @author Vaadin Ltd */ public interface ColumnVisibilityChangeHandler extends EventHandler { diff --git a/client/src/com/vaadin/client/widgets/Escalator.java b/client/src/com/vaadin/client/widgets/Escalator.java index 87f19d2ded..2b3a254b52 100644 --- a/client/src/com/vaadin/client/widgets/Escalator.java +++ b/client/src/com/vaadin/client/widgets/Escalator.java @@ -5824,7 +5824,7 @@ public class Escalator extends Widget implements RequiresResize, * Returns the scroll width for the escalator. Note that this is not * necessary the same as {@code Element.scrollWidth} in the DOM. * - * @since + * @since 7.5.0 * @return the scroll width in pixels */ public double getScrollWidth() { @@ -5835,7 +5835,7 @@ public class Escalator extends Widget implements RequiresResize, * Returns the scroll height for the escalator. Note that this is not * necessary the same as {@code Element.scrollHeight} in the DOM. * - * @since + * @since 7.5.0 * @return the scroll height in pixels */ public double getScrollHeight() { diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index 5ed2f2f949..8316cb8b45 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -653,7 +653,7 @@ public class Grid extends ResizeComposite implements /** * Returns true if this row contains spanned cells. * - * @since + * @since 7.5.0 * @return does this row contain spanned cells */ public boolean hasSpannedCells() { @@ -3121,7 +3121,7 @@ public class Grid extends ResizeComposite implements * column hiding toggles and custom widgets become visible once the sidebar * has been opened. * - * @since + * @since 7.5.0 */ private static class Sidebar extends Composite { @@ -4262,7 +4262,7 @@ public class Grid extends ResizeComposite implements * Hides or shows the column. By default columns are visible before * explicitly hiding them. * - * @since + * @since 7.5.0 * @param hidden * true to hide the column, false * to show @@ -4307,7 +4307,7 @@ public class Grid extends ResizeComposite implements /** * Is this column hidden. Default is {@code false}. * - * @since + * @since 7.5.0 * @return true if the column is currently hidden, * false otherwise */ @@ -4322,7 +4322,7 @@ public class Grid extends ResizeComposite implements * Note: it is still possible to hide the column * programmatically using {@link #setHidden(boolean)}. * - * @since + * @since 7.5.0 * @param hidable * true if the user can hide this column, * false if not @@ -4341,7 +4341,7 @@ public class Grid extends ResizeComposite implements * Note: the column can be programmatically hidden using * {@link #setHidden(boolean)} regardless of the returned value. * - * @since + * @since 7.5.0 * @return true if the user can hide the column, * false if not */ @@ -5349,7 +5349,7 @@ public class Grid extends ResizeComposite implements *

* No {@link Column#isHidden() hidden} columns included. * - * @since + * @since 7.5.0 * @return A unmodifiable list of the currently visible columns in the grid */ public List> getVisibleColumns() { @@ -5989,7 +5989,7 @@ public class Grid extends ResizeComposite implements /** * Sets the horizontal scroll offset * - * @since + * @since 7.5.0 * @param px * the number of pixels this grid should be scrolled right */ @@ -6009,7 +6009,7 @@ public class Grid extends ResizeComposite implements /** * Returns the height of the scrollable area in pixels. * - * @since + * @since 7.5.0 * @return the height of the scrollable area in pixels */ public double getScrollHeight() { @@ -6019,7 +6019,7 @@ public class Grid extends ResizeComposite implements /** * Returns the width of the scrollable area in pixels. * - * @since + * @since 7.5.0 * @return the width of the scrollable area in pixels. */ public double getScrollWidth() { @@ -7051,7 +7051,7 @@ public class Grid extends ResizeComposite implements * Register a column reorder handler to this Grid. The event for this * handler is fired when the Grid's columns are reordered. * - * @since + * @since 7.5.0 * @param handler * the handler for the event * @return the registration for the event @@ -7065,7 +7065,7 @@ public class Grid extends ResizeComposite implements * Register a column visibility change handler to this Grid. The event for * this handler is fired when the Grid's columns change visibility. * - * @since + * @since 7.5.0 * @param handler * the handler for the event * @return the registration for the event @@ -7129,7 +7129,7 @@ public class Grid extends ResizeComposite implements /** * Returns whether columns can be reordered with drag and drop. * - * @since + * @since 7.5.0 * @return true if columns can be reordered, false otherwise */ public boolean isColumnReorderingAllowed() { @@ -7139,7 +7139,7 @@ public class Grid extends ResizeComposite implements /** * Sets whether column reordering with drag and drop is allowed or not. * - * @since + * @since 7.5.0 * @param columnReorderingAllowed * specifies whether column reordering is allowed */ @@ -7572,7 +7572,7 @@ public class Grid extends ResizeComposite implements *

* The currently opened row details will be re-rendered. * - * @since + * @since 7.5.0 * @param detailsGenerator * the details generator to set * @throws IllegalArgumentException @@ -7595,7 +7595,7 @@ public class Grid extends ResizeComposite implements /** * Gets the current details generator for row details. * - * @since + * @since 7.5.0 * @return the detailsGenerator the current details generator */ public DetailsGenerator getDetailsGenerator() { @@ -7608,7 +7608,7 @@ public class Grid extends ResizeComposite implements * This method does nothing if trying to set show already-visible details, * or hide already-hidden details. * - * @since + * @since 7.5.0 * @param rowIndex * the index of the affected row * @param visible @@ -7651,7 +7651,7 @@ public class Grid extends ResizeComposite implements /** * Check whether the details for a row is visible or not. * - * @since + * @since 7.5.0 * @param rowIndex * the index of the row for which to check details * @return true iff the details for the given row is visible @@ -7680,7 +7680,7 @@ public class Grid extends ResizeComposite implements * The grid's sidebar shows the column hiding options for those columns that * have been set as {@link Column#setHidable(boolean) hidable}. * - * @since + * @since 7.5.0 * @return the sidebar widget for this grid */ private Sidebar getSidebar() { diff --git a/server/src/com/vaadin/data/RpcDataProviderExtension.java b/server/src/com/vaadin/data/RpcDataProviderExtension.java index a21a81244a..217b2fe392 100644 --- a/server/src/com/vaadin/data/RpcDataProviderExtension.java +++ b/server/src/com/vaadin/data/RpcDataProviderExtension.java @@ -588,7 +588,7 @@ public class RpcDataProviderExtension extends AbstractExtension { * A class that makes detail component related internal communication * possible between {@link RpcDataProviderExtension} and grid. * - * @since + * @since 7.5.0 * @author Vaadin Ltd */ public static final class DetailComponentManager implements Serializable { @@ -1357,7 +1357,7 @@ public class RpcDataProviderExtension extends AbstractExtension { * If that row is currently in the client side's cache, this information * will be sent over to the client. * - * @since + * @since 7.5.0 * @param itemId * the id of the item of which to change the details visibility * @param visible @@ -1401,7 +1401,7 @@ public class RpcDataProviderExtension extends AbstractExtension { /** * Checks whether the details for a row is marked as visible. * - * @since + * @since 7.5.0 * @param itemId * the id of the item of which to check the visibility * @return true iff the detials are visible for the item. This diff --git a/server/src/com/vaadin/ui/Grid.java b/server/src/com/vaadin/ui/Grid.java index 62b3a74f05..69fbb41fc2 100644 --- a/server/src/com/vaadin/ui/Grid.java +++ b/server/src/com/vaadin/ui/Grid.java @@ -170,7 +170,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier, /** * An event listener for column visibility change events in the Grid. * - * @since + * @since 7.5.0 */ public interface ColumnVisibilityChangeListener extends Serializable { /** @@ -184,7 +184,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier, /** * An event that is fired when a column's visibility changes. * - * @since + * @since 7.5.0 */ public static class ColumnVisibilityChangeEvent extends Component.Event { @@ -248,7 +248,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier, /** * A callback interface for generating details for a particular row in Grid. * - * @since + * @since 7.5.0 * @author Vaadin Ltd */ public interface DetailsGenerator extends Serializable { @@ -452,7 +452,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier, /** * An event listener for column reorder events in the Grid. * - * @since + * @since 7.5.0 */ public interface ColumnReorderListener extends Serializable { /** @@ -467,7 +467,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier, /** * An event that is fired when the columns are reordered. * - * @since + * @since 7.5.0 */ public static class ColumnReorderEvent extends Component.Event { @@ -2834,7 +2834,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier, * Hides or shows the column. By default columns are visible before * explicitly hiding them. * - * @since + * @since 7.5.0 * @param hidden * true to hide the column, false * to show @@ -2850,7 +2850,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier, /** * Is this column hidden. Default is {@code false}. * - * @since + * @since 7.5.0 * @return true if the column is currently hidden, * false otherwise */ @@ -2865,7 +2865,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier, * Note: it is still possible to hide the column * programmatically using {@link #setHidden(boolean)} * - * @since + * @since 7.5.0 * @param hidable * true iff the column may be hidable by the * user via UI interaction @@ -2882,7 +2882,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier, * Note: the column can be programmatically hidden using * {@link #setHidden(boolean)} regardless of the returned value. * - * @since + * @since 7.5.0 * @return true if the user can hide the column, * false if not */ @@ -3803,7 +3803,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier, * Returns whether column reordering is allowed. Default value is * false. * - * @since + * @since 7.5.0 * @return true if reordering is allowed */ public boolean isColumnReorderingAllowed() { @@ -3814,7 +3814,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier, * Sets whether or not column reordering is allowed. Default value is * false. * - * @since + * @since 7.5.0 * @param columnReorderingAllowed * specifies whether column reordering is allowed */ @@ -4399,7 +4399,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier, /** * Registers a new column reorder listener. * - * @since + * @since 7.5.0 * @param listener * the listener to register */ @@ -4410,7 +4410,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier, /** * Removes a previously registered column reorder listener. * - * @since + * @since 7.5.0 * @param listener * the listener to remove */ @@ -5486,7 +5486,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier, /** * Registers a new column visibility change listener * - * @since + * @since 7.5.0 * @param listener * the listener to register */ @@ -5499,7 +5499,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier, /** * Removes a previously registered column visibility change listener * - * @since + * @since 7.5.0 * @param listener * the listener to remove */ @@ -5520,7 +5520,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier, *

* The currently opened row details will be re-rendered. * - * @since + * @since 7.5.0 * @param detailsGenerator * the details generator to set * @throws IllegalArgumentException @@ -5545,7 +5545,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier, /** * Gets the current details generator for row details. * - * @since + * @since 7.5.0 * @return the detailsGenerator the current details generator */ public DetailsGenerator getDetailsGenerator() { @@ -5555,7 +5555,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier, /** * Shows or hides the details for a specific item. * - * @since + * @since 7.5.0 * @param itemId * the id of the item for which to set details visibility * @param visible @@ -5569,7 +5569,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier, /** * Checks whether details are visible for the given item. * - * @since + * @since 7.5.0 * @param itemId * the id of the item for which to check details visibility * @return true iff the details are visible diff --git a/shared/src/com/vaadin/shared/ui/grid/DetailsConnectorChange.java b/shared/src/com/vaadin/shared/ui/grid/DetailsConnectorChange.java index 171a7738a6..8b64d22423 100644 --- a/shared/src/com/vaadin/shared/ui/grid/DetailsConnectorChange.java +++ b/shared/src/com/vaadin/shared/ui/grid/DetailsConnectorChange.java @@ -24,7 +24,7 @@ import com.vaadin.shared.Connector; * A description of an indexing modification for a connector. This is used by * Grid for internal bookkeeping updates. * - * @since + * @since 7.5.0 * @author Vaadin Ltd */ public class DetailsConnectorChange implements Serializable { diff --git a/shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java b/shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java index 98e7fac567..d4707f9ba5 100644 --- a/shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java +++ b/shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java @@ -61,7 +61,7 @@ public interface GridClientRpc extends ClientRpc { * Informs the GridConnector on how the indexing of details connectors has * changed. * - * @since + * @since 7.5.0 * @param connectorChanges * the indexing changes of details connectors * @param fetchId diff --git a/shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java b/shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java index 2b2308fe84..dca55c11c4 100644 --- a/shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java +++ b/shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java @@ -51,7 +51,7 @@ public interface GridServerRpc extends ServerRpc { /** * Informs the server that the columns of the Grid have been reordered. * - * @since + * @since 7.5.0 * @param newColumnOrder * a list of column ids in the new order * @param oldColumnOrder @@ -70,7 +70,7 @@ public interface GridServerRpc extends ServerRpc { * , and that is too late to change the hierarchy. So we need this * round-trip to work around that limitation. * - * @since + * @since 7.5.0 * @param fetchId * an unique identifier for the request * @see com.vaadin.ui.Grid#setDetailsVisible(Object, boolean) @@ -80,7 +80,7 @@ public interface GridServerRpc extends ServerRpc { /** * Informs the server that the column's visibility has been changed. * - * @since + * @since 7.5.0 * @param id * the id of the column * @param hidden -- cgit v1.2.3 From 4416a1bdd36ed1ce9a68b40eca6f1f15f802324b Mon Sep 17 00:00:00 2001 From: Henrik Paul Date: Wed, 25 Mar 2015 15:28:16 +0200 Subject: Client Grid.scrollToRow takes details into account (#17270) Change-Id: I8a63f51f3b444c3c4342c46b7b58ad152f4bd0e1 --- .../src/com/vaadin/client/widgets/Escalator.java | 94 ++++++++++++++++++++-- client/src/com/vaadin/client/widgets/Grid.java | 12 ++- .../EscalatorBasicClientFeaturesTest.java | 1 + .../client/GridDetailsClientTest.java | 76 +++++++++++++---- .../escalator/EscalatorSpacerTest.java | 38 ++++++++- .../grid/EscalatorBasicClientFeaturesWidget.java | 8 ++ .../client/grid/GridBasicClientFeaturesWidget.java | 69 ++++++++++++---- 7 files changed, 258 insertions(+), 40 deletions(-) (limited to 'client/src') diff --git a/client/src/com/vaadin/client/widgets/Escalator.java b/client/src/com/vaadin/client/widgets/Escalator.java index 2b3a254b52..e02b4e9f83 100644 --- a/client/src/com/vaadin/client/widgets/Escalator.java +++ b/client/src/com/vaadin/client/widgets/Escalator.java @@ -5859,7 +5859,8 @@ public class Escalator extends Widget implements RequiresResize, * column * @throws IllegalArgumentException * if {@code destination} is {@link ScrollDestination#MIDDLE} - * and padding is nonzero, or if the indicated column is frozen + * and padding is nonzero; or if the indicated column is frozen; + * or if {@code destination == null} */ public void scrollToColumn(final int columnIndex, final ScrollDestination destination, final int padding) @@ -5900,7 +5901,9 @@ public class Escalator extends Widget implements RequiresResize, * if {@code rowIndex} is not a valid index for an existing row * @throws IllegalArgumentException * if {@code destination} is {@link ScrollDestination#MIDDLE} - * and padding is nonzero + * and padding is nonzero; or if {@code destination == null} + * @see #scrollToRowAndSpacer(int, ScrollDestination, int) + * @see #scrollToSpacer(int, ScrollDestination, int) */ public void scrollToRow(final int rowIndex, final ScrollDestination destination, final int padding) @@ -5921,7 +5924,7 @@ public class Escalator extends Widget implements RequiresResize, /** * Scrolls the body vertically so that the spacer at the given row index is * visible and there is at least {@literal padding} pixesl to the given - * scroll destination + * scroll destination. * * @since * @param spacerIndex @@ -5932,9 +5935,11 @@ public class Escalator extends Widget implements RequiresResize, * the number of pixels to place between the scrolled-to spacer * and the viewport edge * @throws IllegalArgumentException - * if {@code spacerIndex} is not an opened spacer if + * if {@code spacerIndex} is not an opened spacer; or if * {@code destination} is {@link ScrollDestination#MIDDLE} and - * padding is nonzero + * padding is nonzero; or if {@code destination == null} + * @see #scrollToRow(int, ScrollDestination, int) + * @see #scrollToRowAndSpacer(int, ScrollDestination, int) */ public void scrollToSpacer(final int spacerIndex, ScrollDestination destination, final int padding) @@ -5943,8 +5948,87 @@ public class Escalator extends Widget implements RequiresResize, body.scrollToSpacer(spacerIndex, destination, padding); } + /** + * Scrolls vertically to a row and the spacer below it. + *

+ * If a spacer is not open at that index, this method behaves like + * {@link #scrollToRow(int, ScrollDestination, int)} + * + * @since + * @param rowIndex + * the index of the logical row to scroll to. -1 takes the + * topmost spacer into account as well. + * @param destination + * where the row should be aligned visually after scrolling + * @param padding + * the number pixels to place between the scrolled-to row and the + * viewport edge. + * @see #scrollToRow(int, ScrollDestination, int) + * @see #scrollToSpacer(int, ScrollDestination, int) + * @throws IllegalArgumentException + * if {@code destination} is {@link ScrollDestination#MIDDLE} + * and {@code padding} is not zero; or if {@code rowIndex} is + * not a valid row index, or -1; or if + * {@code destination == null}; or if {@code rowIndex == -1} and + * there is no spacer open at that index. + */ + public void scrollToRowAndSpacer(int rowIndex, + ScrollDestination destination, int padding) + throws IllegalArgumentException { + validateScrollDestination(destination, padding); + if (rowIndex != -1) { + verifyValidRowIndex(rowIndex); + } + + // row range + final Range rowRange; + if (rowIndex != -1) { + int rowTop = (int) Math.floor(body.getRowTop(rowIndex)); + int rowHeight = (int) Math.ceil(body.getDefaultRowHeight()); + rowRange = Range.withLength(rowTop, rowHeight); + } else { + rowRange = Range.withLength(0, 0); + } + + // get spacer + final SpacerContainer.SpacerImpl spacer = body.spacerContainer + .getSpacer(rowIndex); + + if (rowIndex == -1 && spacer == null) { + throw new IllegalArgumentException("Cannot scroll to row index " + + "-1, as there is no spacer open at that index."); + } + + // make into target range + final Range targetRange; + if (spacer != null) { + final int spacerTop = (int) Math.floor(spacer.getTop()); + final int spacerHeight = (int) Math.ceil(spacer.getHeight()); + Range spacerRange = Range.withLength(spacerTop, spacerHeight); + + targetRange = rowRange.combineWith(spacerRange); + } else { + targetRange = rowRange; + } + + // get params + int targetStart = targetRange.getStart(); + int targetEnd = targetRange.getEnd(); + double viewportStart = getScrollTop(); + double viewportEnd = viewportStart + body.getHeightOfSection(); + + double scrollPos = getScrollPos(destination, targetStart, targetEnd, + viewportStart, viewportEnd, padding); + + setScrollTop(scrollPos); + } + private static void validateScrollDestination( final ScrollDestination destination, final int padding) { + if (destination == null) { + throw new IllegalArgumentException("Destination cannot be null"); + } + if (destination == ScrollDestination.MIDDLE && padding != 0) { throw new IllegalArgumentException( "You cannot have a padding with a MIDDLE destination"); diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index 8316cb8b45..9fcdd20296 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -5887,6 +5887,9 @@ public class Grid extends ResizeComposite implements /** * Scrolls to a certain row, using {@link ScrollDestination#ANY}. + *

+ * If the details for that row are visible, those will be taken into account + * as well. * * @param rowIndex * zero-based index of the row to scroll to. @@ -5901,6 +5904,9 @@ public class Grid extends ResizeComposite implements /** * Scrolls to a certain row, using user-specified scroll destination. + *

+ * If the details for that row are visible, those will be taken into account + * as well. * * @param rowIndex * zero-based index of the row to scroll to. @@ -5920,6 +5926,9 @@ public class Grid extends ResizeComposite implements /** * Scrolls to a certain row using only user-specified parameters. + *

+ * If the details for that row are visible, those will be taken into account + * as well. * * @param rowIndex * zero-based index of the row to scroll to. @@ -5949,7 +5958,7 @@ public class Grid extends ResizeComposite implements + ") is above maximum (" + maxsize + ")!"); } - escalator.scrollToRow(rowIndex, destination, paddingPx); + escalator.scrollToRowAndSpacer(rowIndex, destination, paddingPx); } /** @@ -7686,5 +7695,4 @@ public class Grid extends ResizeComposite implements private Sidebar getSidebar() { return sidebar; } - } 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 de254b4deb..9037b29bb1 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/EscalatorBasicClientFeaturesTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/EscalatorBasicClientFeaturesTest.java @@ -78,6 +78,7 @@ public abstract class EscalatorBasicClientFeaturesTest extends MultiBrowserTest protected static final String SET_100PX = "Set 100px"; protected static final String SPACERS = "Spacers"; protected static final String SCROLL_HERE_ANY_0PADDING = "Scroll here (ANY, 0)"; + protected static final String SCROLL_HERE_SPACERBELOW_ANY_0PADDING = "Scroll here row+spacer below (ANY, 0)"; protected static final String REMOVE = "Remove"; protected static final String ROW_MINUS1 = "Row -1"; diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridDetailsClientTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridDetailsClientTest.java index d32b731cf1..5ab0c238e5 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridDetailsClientTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridDetailsClientTest.java @@ -28,12 +28,17 @@ import org.junit.Test; import org.openqa.selenium.NoSuchElementException; import org.openqa.selenium.WebElement; +import com.vaadin.shared.ui.grid.Range; +import com.vaadin.shared.ui.grid.ScrollDestination; import com.vaadin.testbench.By; import com.vaadin.testbench.ElementQuery; import com.vaadin.testbench.TestBenchElement; +import com.vaadin.testbench.annotations.RunLocally; import com.vaadin.testbench.elements.NotificationElement; +import com.vaadin.testbench.parallel.Browser; import com.vaadin.tests.components.grid.basicfeatures.GridBasicClientFeaturesTest; +@RunLocally(Browser.PHANTOMJS) public class GridDetailsClientTest extends GridBasicClientFeaturesTest { private static final String[] SET_GENERATOR = new String[] { "Component", @@ -42,10 +47,6 @@ public class GridDetailsClientTest extends GridBasicClientFeaturesTest { "Component", "Row details", "Set faulty generator" }; private static final String[] SET_EMPTY_GENERATOR = new String[] { "Component", "Row details", "Set empty generator" }; - private static final String[] TOGGLE_DETAILS_FOR_ROW_1 = new String[] { - "Component", "Row details", "Toggle details for row 1" }; - private static final String[] TOGGLE_DETAILS_FOR_ROW_100 = new String[] { - "Component", "Row details", "Toggle details for row 100" }; @Before public void setUp() { @@ -61,7 +62,7 @@ public class GridDetailsClientTest extends GridBasicClientFeaturesTest { @Test public void nullRendererShowsDetailsPlaceholder() { - selectMenuPath(TOGGLE_DETAILS_FOR_ROW_1); + toggleDetailsFor(1); TestBenchElement details = getGridElement().getDetails(1); assertNotNull("details for row 1 should not exist at the start", details); @@ -72,7 +73,7 @@ public class GridDetailsClientTest extends GridBasicClientFeaturesTest { @Test public void applyRendererThenOpenDetails() { selectMenuPath(SET_GENERATOR); - selectMenuPath(TOGGLE_DETAILS_FOR_ROW_1); + toggleDetailsFor(1); TestBenchElement details = getGridElement().getDetails(1); assertTrue("Unexpected details content", @@ -81,7 +82,7 @@ public class GridDetailsClientTest extends GridBasicClientFeaturesTest { @Test public void openDetailsThenAppyRenderer() { - selectMenuPath(TOGGLE_DETAILS_FOR_ROW_1); + toggleDetailsFor(1); selectMenuPath(SET_GENERATOR); TestBenchElement details = getGridElement().getDetails(1); @@ -99,7 +100,7 @@ public class GridDetailsClientTest extends GridBasicClientFeaturesTest { } selectMenuPath(SET_GENERATOR); - selectMenuPath(TOGGLE_DETAILS_FOR_ROW_100); + toggleDetailsFor(100); // scroll a bit beyond so we see below. getGridElement().scrollToRow(101); @@ -114,7 +115,7 @@ public class GridDetailsClientTest extends GridBasicClientFeaturesTest { assertFalse("No notifications should've been at the start", $(NotificationElement.class).exists()); - selectMenuPath(TOGGLE_DETAILS_FOR_ROW_1); + toggleDetailsFor(1); selectMenuPath(SET_FAULTY_GENERATOR); ElementQuery notification = $(NotificationElement.class); @@ -128,7 +129,7 @@ public class GridDetailsClientTest extends GridBasicClientFeaturesTest { @Test public void updaterStillWorksAfterError() { - selectMenuPath(TOGGLE_DETAILS_FOR_ROW_1); + toggleDetailsFor(1); selectMenuPath(SET_FAULTY_GENERATOR); $(NotificationElement.class).first().close(); @@ -142,7 +143,7 @@ public class GridDetailsClientTest extends GridBasicClientFeaturesTest { @Test public void updaterRendersExpectedWidgets() { selectMenuPath(SET_GENERATOR); - selectMenuPath(TOGGLE_DETAILS_FOR_ROW_1); + toggleDetailsFor(1); TestBenchElement detailsElement = getGridElement().getDetails(1); assertNotNull(detailsElement.findElement(By.className("gwt-Label"))); @@ -152,7 +153,7 @@ public class GridDetailsClientTest extends GridBasicClientFeaturesTest { @Test public void widgetsInUpdaterWorkAsExpected() { selectMenuPath(SET_GENERATOR); - selectMenuPath(TOGGLE_DETAILS_FOR_ROW_1); + toggleDetailsFor(1); TestBenchElement detailsElement = getGridElement().getDetails(1); WebElement button = detailsElement.findElement(By @@ -167,7 +168,7 @@ public class GridDetailsClientTest extends GridBasicClientFeaturesTest { @Test public void emptyGenerator() { selectMenuPath(SET_EMPTY_GENERATOR); - selectMenuPath(TOGGLE_DETAILS_FOR_ROW_1); + toggleDetailsFor(1); assertEquals("empty generator did not produce an empty details row", "", getGridElement().getDetails(1).getText()); @@ -176,9 +177,54 @@ public class GridDetailsClientTest extends GridBasicClientFeaturesTest { @Test(expected = NoSuchElementException.class) public void removeDetailsRow() { selectMenuPath(SET_GENERATOR); - selectMenuPath(TOGGLE_DETAILS_FOR_ROW_1); - selectMenuPath(TOGGLE_DETAILS_FOR_ROW_1); + toggleDetailsFor(1); + toggleDetailsFor(1); getGridElement().getDetails(1); } + + @Test + public void scrollDownToRowWithDetails() { + toggleDetailsFor(100); + scrollToRow(100, ScrollDestination.ANY); + + Range validScrollRange = Range.between(1700, 1715); + assertTrue(validScrollRange.contains(getGridVerticalScrollPos())); + } + + @Test + public void scrollUpToRowWithDetails() { + toggleDetailsFor(100); + scrollGridVerticallyTo(999999); + scrollToRow(100, ScrollDestination.ANY); + + Range validScrollRange = Range.between(1990, 2010); + assertTrue(validScrollRange.contains(getGridVerticalScrollPos())); + } + + @Test + public void cannotScrollBeforeTop() { + toggleDetailsFor(1); + scrollToRow(0, ScrollDestination.END); + assertEquals(0, getGridVerticalScrollPos()); + } + + @Test + public void cannotScrollAfterBottom() { + toggleDetailsFor(999); + scrollToRow(999, ScrollDestination.START); + + Range expectedRange = Range.withLength(19680, 20); + assertTrue(expectedRange.contains(getGridVerticalScrollPos())); + } + + private void scrollToRow(int rowIndex, ScrollDestination destination) { + selectMenuPath(new String[] { "Component", "State", "Scroll to...", + "Row " + rowIndex + "...", "Destination " + destination }); + } + + private void toggleDetailsFor(int rowIndex) { + selectMenuPath(new String[] { "Component", "Row details", + "Toggle details for...", "Row " + rowIndex }); + } } 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 index a0fa961ad2..ce8fcd233e 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/escalator/EscalatorSpacerTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/escalator/EscalatorSpacerTest.java @@ -332,10 +332,46 @@ public class EscalatorSpacerTest extends EscalatorBasicClientFeaturesTest { scrollVerticallyTo(1000); selectMenuPath(FEATURES, SPACERS, ROW_50, SCROLL_HERE_ANY_0PADDING); - // Browsers might vary with a few pixels. assertEquals(getScrollTop(), 1000); } + @Test + public void scrollToRowAndSpacerFromAbove() throws Exception { + selectMenuPath(FEATURES, SPACERS, ROW_50, SET_100PX); + selectMenuPath(FEATURES, SPACERS, ROW_50, + SCROLL_HERE_SPACERBELOW_ANY_0PADDING); + + // Browsers might vary with a few pixels. + Range allowableScrollRange = Range.between(765, 780); + int scrollTop = (int) getScrollTop(); + assertTrue("Scroll position was not " + allowableScrollRange + ", but " + + scrollTop, allowableScrollRange.contains(scrollTop)); + } + + @Test + public void scrollToRowAndSpacerFromBelow() throws Exception { + selectMenuPath(FEATURES, SPACERS, ROW_50, SET_100PX); + scrollVerticallyTo(999999); + selectMenuPath(FEATURES, SPACERS, ROW_50, + SCROLL_HERE_SPACERBELOW_ANY_0PADDING); + + // Browsers might vary with a few pixels. + Range allowableScrollRange = Range.between(995, 1005); + int scrollTop = (int) getScrollTop(); + assertTrue("Scroll position was not " + allowableScrollRange + ", but " + + scrollTop, allowableScrollRange.contains(scrollTop)); + } + + @Test + public void scrollToRowAndSpacerAlreadyInViewport() throws Exception { + selectMenuPath(FEATURES, SPACERS, ROW_50, SET_100PX); + scrollVerticallyTo(950); + selectMenuPath(FEATURES, SPACERS, ROW_50, + SCROLL_HERE_SPACERBELOW_ANY_0PADDING); + + assertEquals(getScrollTop(), 950); + } + private static double[] getElementDimensions(WebElement element) { /* * we need to parse the style attribute, since using getCssValue gets a 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 4cf4726c28..ec9f214748 100644 --- a/uitest/src/com/vaadin/tests/widgetset/client/grid/EscalatorBasicClientFeaturesWidget.java +++ b/uitest/src/com/vaadin/tests/widgetset/client/grid/EscalatorBasicClientFeaturesWidget.java @@ -698,6 +698,14 @@ public class EscalatorBasicClientFeaturesWidget extends escalator.scrollToSpacer(rowIndex, ScrollDestination.ANY, 0); } }, menupath); + addMenuCommand("Scroll here row+spacer below (ANY, 0)", + new ScheduledCommand() { + @Override + public void execute() { + escalator.scrollToRowAndSpacer(rowIndex, + ScrollDestination.ANY, 0); + } + }, menupath); } private void insertRows(final RowContainer container, int offset, int number) { 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 1fd926d726..196428822c 100644 --- a/uitest/src/com/vaadin/tests/widgetset/client/grid/GridBasicClientFeaturesWidget.java +++ b/uitest/src/com/vaadin/tests/widgetset/client/grid/GridBasicClientFeaturesWidget.java @@ -80,6 +80,7 @@ import com.vaadin.client.widgets.Grid.Column; import com.vaadin.client.widgets.Grid.FooterRow; import com.vaadin.client.widgets.Grid.HeaderRow; import com.vaadin.client.widgets.Grid.SelectionMode; +import com.vaadin.shared.ui.grid.ScrollDestination; import com.vaadin.tests.widgetset.client.grid.GridBasicClientFeaturesWidget.Data; /** @@ -767,6 +768,43 @@ public class GridBasicClientFeaturesWidget extends grid.setWidth("1000px"); } }, "Component", "State", "Width"); + + createScrollToRowMenu(); + } + + private void createScrollToRowMenu() { + String[] menupath = new String[] { "Component", "State", + "Scroll to...", null }; + + for (int i = 0; i < ROWS; i += 100) { + menupath[3] = "Row " + i + "..."; + for (final ScrollDestination scrollDestination : ScrollDestination + .values()) { + final int row = i; + addMenuCommand("Destination " + scrollDestination, + new ScheduledCommand() { + @Override + public void execute() { + grid.scrollToRow(row, scrollDestination); + } + }, menupath); + } + } + + int i = ROWS - 1; + menupath[3] = "Row " + i + "..."; + for (final ScrollDestination scrollDestination : ScrollDestination + .values()) { + final int row = i; + addMenuCommand("Destination " + scrollDestination, + new ScheduledCommand() { + @Override + public void execute() { + grid.scrollToRow(row, scrollDestination); + } + }, menupath); + } + } private void createColumnsMenu() { @@ -1423,24 +1461,21 @@ public class GridBasicClientFeaturesWidget extends } }, menupath); - addMenuCommand("Toggle details for row 1", new ScheduledCommand() { - boolean visible = false; + String[] togglemenupath = new String[] { menupath[0], menupath[1], + "Toggle details for..." }; + for (int i : new int[] { 0, 1, 100, 200, 300, 400, 500, 600, 700, 800, + 900, 999 }) { + final int rowIndex = i; + addMenuCommand("Row " + rowIndex, 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(rowIndex, visible); + } + }, togglemenupath); + } - @Override - public void execute() { - visible = !visible; - grid.setDetailsVisible(100, visible); - } - }, menupath); } } -- cgit v1.2.3 From eeb5e8c89df6e043d20c4e0ac6b094334970b2b4 Mon Sep 17 00:00:00 2001 From: Pekka Hyvönen Date: Mon, 30 Mar 2015 12:04:28 +0300 Subject: Moves DragAndDropHandler from Grid to separate class Includes the class in the widgets package build. Change-Id: If72430d3005f7af0029070a72976853baac195a1 --- .../vaadin/client/ui/dd/DragAndDropHandler.java | 241 +++++++++++++++++++++ client/src/com/vaadin/client/widgets/Grid.java | 224 +------------------ widgets/build.xml | 1 + 3 files changed, 243 insertions(+), 223 deletions(-) create mode 100644 client/src/com/vaadin/client/ui/dd/DragAndDropHandler.java (limited to 'client/src') diff --git a/client/src/com/vaadin/client/ui/dd/DragAndDropHandler.java b/client/src/com/vaadin/client/ui/dd/DragAndDropHandler.java new file mode 100644 index 0000000000..61fdd2850e --- /dev/null +++ b/client/src/com/vaadin/client/ui/dd/DragAndDropHandler.java @@ -0,0 +1,241 @@ +/* + * 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.ui.dd; + +import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.event.dom.client.KeyCodes; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.Event.NativePreviewEvent; +import com.google.gwt.user.client.Event.NativePreviewHandler; +import com.google.gwt.user.client.ui.RootPanel; +import com.vaadin.client.WidgetUtil; +import com.vaadin.client.widgets.Grid; + +/** + * A simple event handler for elements that can be drag and dropped. Properly + * handles drag start, cancel and end. For example, used in {@link Grid} column + * header reordering. + *

+ * The showing of the dragged element, drag hints and reacting to drop/cancel is + * delegated to {@link DragAndDropCallback} implementation. + * + * @since + * @author Vaadin Ltd + */ +public class DragAndDropHandler { + + /** + * Callback interface for drag and drop. + */ + public interface DragAndDropCallback { + /** + * Called when the drag has started. The drag can be canceled by + * returning {@code false}. + * + * @param startEvent + * the original event that started the drag + * @return {@code true} if the drag is OK to start, {@code false} to + * cancel + */ + boolean onDragStart(NativeEvent startEvent); + + /** + * Called on drag. + * + * @param event + * the event related to the drag + */ + void onDragUpdate(NativePreviewEvent event); + + /** + * Called after the has ended on a drop or cancel. + */ + void onDragEnd(); + + /** + * Called when the drag has ended on a drop. + */ + void onDrop(); + + /** + * Called when the drag has been canceled. + */ + void onDragCancel(); + } + + private HandlerRegistration dragStartNativePreviewHandlerRegistration; + private HandlerRegistration dragHandlerRegistration; + + private boolean dragging; + + private DragAndDropCallback callback; + + private final NativePreviewHandler dragHandler = new NativePreviewHandler() { + + @Override + public void onPreviewNativeEvent(NativePreviewEvent event) { + if (dragging) { + final int typeInt = event.getTypeInt(); + switch (typeInt) { + case Event.ONKEYDOWN: + int keyCode = event.getNativeEvent().getKeyCode(); + if (keyCode == KeyCodes.KEY_ESCAPE) { + // end drag if ESC is hit + cancelDrag(event); + } + break; + case Event.ONMOUSEMOVE: + case Event.ONTOUCHMOVE: + callback.onDragUpdate(event); + // prevent text selection on IE + event.getNativeEvent().preventDefault(); + break; + case Event.ONTOUCHCANCEL: + cancelDrag(event); + break; + case Event.ONTOUCHEND: + /* Avoid simulated event on drag end */ + event.getNativeEvent().preventDefault(); + //$FALL-THROUGH$ + case Event.ONMOUSEUP: + callback.onDragUpdate(event); + callback.onDrop(); + stopDrag(); + event.cancel(); + break; + default: + break; + } + } else { + stopDrag(); + } + } + + }; + + /** + * This method can be called to trigger drag and drop on any grid element + * that can be dragged and dropped. + * + * @param dragStartingEvent + * the drag triggering event, usually a {@link Event#ONMOUSEDOWN} + * or {@link Event#ONTOUCHSTART} event on the draggable element + * + * @param callback + * the callback that will handle actual drag and drop related + * operations + */ + public void onDragStartOnDraggableElement( + final NativeEvent dragStartingEvent, + final DragAndDropCallback callback) { + dragStartNativePreviewHandlerRegistration = Event + .addNativePreviewHandler(new NativePreviewHandler() { + + private int startX = WidgetUtil + .getTouchOrMouseClientX(dragStartingEvent); + private int startY = WidgetUtil + .getTouchOrMouseClientY(dragStartingEvent); + + @Override + public void onPreviewNativeEvent(NativePreviewEvent event) { + final int typeInt = event.getTypeInt(); + if (typeInt == -1 + && event.getNativeEvent().getType() + .toLowerCase().contains("pointer")) { + /* + * Ignore PointerEvents since IE10 and IE11 send + * also MouseEvents for backwards compatibility. + */ + return; + } + switch (typeInt) { + case Event.ONMOUSEOVER: + case Event.ONMOUSEOUT: + // we don't care + break; + case Event.ONKEYDOWN: + case Event.ONKEYPRESS: + case Event.ONKEYUP: + case Event.ONBLUR: + case Event.ONFOCUS: + // don't cancel possible drag start + break; + case Event.ONMOUSEMOVE: + case Event.ONTOUCHMOVE: + int currentX = WidgetUtil + .getTouchOrMouseClientX(event + .getNativeEvent()); + int currentY = WidgetUtil + .getTouchOrMouseClientY(event + .getNativeEvent()); + if (Math.abs(startX - currentX) > 3 + || Math.abs(startY - currentY) > 3) { + removeNativePreviewHandlerRegistration(); + startDrag(dragStartingEvent, event, callback); + } + break; + default: + // on any other events, clean up this preview + // listener + removeNativePreviewHandlerRegistration(); + break; + } + } + }); + } + + private void startDrag(NativeEvent startEvent, + NativePreviewEvent triggerEvent, DragAndDropCallback callback) { + if (callback.onDragStart(startEvent)) { + dragging = true; + // just capture something to prevent text selection in IE + Event.setCapture(RootPanel.getBodyElement()); + this.callback = callback; + dragHandlerRegistration = Event + .addNativePreviewHandler(dragHandler); + callback.onDragUpdate(triggerEvent); + } + } + + private void stopDrag() { + dragging = false; + if (dragHandlerRegistration != null) { + dragHandlerRegistration.removeHandler(); + dragHandlerRegistration = null; + } + Event.releaseCapture(RootPanel.getBodyElement()); + if (callback != null) { + callback.onDragEnd(); + callback = null; + } + } + + private void cancelDrag(NativePreviewEvent event) { + callback.onDragCancel(); + callback.onDragEnd(); + stopDrag(); + event.cancel(); + event.getNativeEvent().preventDefault(); + } + + private void removeNativePreviewHandlerRegistration() { + if (dragStartNativePreviewHandlerRegistration != null) { + dragStartNativePreviewHandlerRegistration.removeHandler(); + dragStartNativePreviewHandlerRegistration = null; + } + } +} diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index 9fcdd20296..dad6f0fcfc 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -67,7 +67,6 @@ import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.HasEnabled; import com.google.gwt.user.client.ui.HasWidgets; import com.google.gwt.user.client.ui.ResizeComposite; -import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.ToggleButton; import com.google.gwt.user.client.ui.Widget; import com.vaadin.client.BrowserInfo; @@ -79,6 +78,7 @@ import com.vaadin.client.renderers.ComplexRenderer; import com.vaadin.client.renderers.Renderer; import com.vaadin.client.renderers.WidgetRenderer; import com.vaadin.client.ui.SubPartAware; +import com.vaadin.client.ui.dd.DragAndDropHandler; import com.vaadin.client.widget.escalator.Cell; import com.vaadin.client.widget.escalator.ColumnConfiguration; import com.vaadin.client.widget.escalator.EscalatorUpdater; @@ -210,228 +210,6 @@ public class Grid extends ResizeComposite implements HEADER, BODY, FOOTER } - /** - * A simple event handler for elements that can be drag and dropped. Loosely - * based on {@link com.vaadin.client.ui.dd.VDragAndDropManager}, but without - * any Vaadin related stuff. Properly handles drag start, cancel and end. - * For example, used in {@link Grid} column header reordering. - *

- * The showing of the dragged element, drag hints and reacting to - * drop/cancel is delegated to {@link DragAndDropCallback} implementation. - * - * @author Vaadin Ltd - */ - private static class DragAndDropHandler { - - /** - * Callback interface for drag and drop. - */ - public interface DragAndDropCallback { - /** - * Called when the drag has started. The drag can be canceled by - * returning {@code false}. - * - * @param startEvent - * the original event that started the drag - * @return {@code true} if the drag is OK to start, {@code false} to - * cancel - */ - boolean onDragStart(NativeEvent startEvent); - - /** - * Called on drag. - * - * @param event - * the event related to the drag - */ - void onDragUpdate(NativePreviewEvent event); - - /** - * Called after the has ended on a drop or cancel. - */ - void onDragEnd(); - - /** - * Called when the drag has ended on a drop. - */ - void onDrop(); - - /** - * Called when the drag has been canceled. - */ - void onDragCancel(); - } - - private HandlerRegistration dragStartNativePreviewHandlerRegistration; - private HandlerRegistration dragHandlerRegistration; - - private boolean dragging; - - private DragAndDropCallback callback; - - private final NativePreviewHandler dragHandler = new NativePreviewHandler() { - - @Override - public void onPreviewNativeEvent(NativePreviewEvent event) { - if (dragging) { - final int typeInt = event.getTypeInt(); - switch (typeInt) { - case Event.ONKEYDOWN: - int keyCode = event.getNativeEvent().getKeyCode(); - if (keyCode == KeyCodes.KEY_ESCAPE) { - // end drag if ESC is hit - cancelDrag(event); - } - break; - case Event.ONMOUSEMOVE: - case Event.ONTOUCHMOVE: - callback.onDragUpdate(event); - // prevent text selection on IE - event.getNativeEvent().preventDefault(); - break; - case Event.ONTOUCHCANCEL: - cancelDrag(event); - break; - case Event.ONTOUCHEND: - /* Avoid simulated event on drag end */ - event.getNativeEvent().preventDefault(); - //$FALL-THROUGH$ - case Event.ONMOUSEUP: - callback.onDragUpdate(event); - callback.onDrop(); - stopDrag(); - event.cancel(); - break; - default: - break; - } - } else { - stopDrag(); - } - } - - }; - - private static Logger getLogger() { - return Logger.getLogger(DragAndDropHandler.class.getName()); - } - - /** - * This method can be called to trigger drag and drop on any grid - * element that can be dragged and dropped. - * - * @param dragStartingEvent - * the drag triggering event, usually a - * {@link Event#ONMOUSEDOWN} or {@link Event#ONTOUCHSTART} - * event on the draggable element - * - * @param callback - * the callback that will handle actual drag and drop related - * operations - */ - public void onDragStartOnDraggableElement( - final NativeEvent dragStartingEvent, - final DragAndDropCallback callback) { - dragStartNativePreviewHandlerRegistration = Event - .addNativePreviewHandler(new NativePreviewHandler() { - - private int startX = WidgetUtil - .getTouchOrMouseClientX(dragStartingEvent); - private int startY = WidgetUtil - .getTouchOrMouseClientY(dragStartingEvent); - - @Override - public void onPreviewNativeEvent( - NativePreviewEvent event) { - final int typeInt = event.getTypeInt(); - if (typeInt == -1 - && event.getNativeEvent().getType() - .toLowerCase().contains("pointer")) { - /* - * Ignore PointerEvents since IE10 and IE11 send - * also MouseEvents for backwards compatibility. - */ - return; - } - switch (typeInt) { - case Event.ONMOUSEOVER: - case Event.ONMOUSEOUT: - // we don't care - break; - case Event.ONKEYDOWN: - case Event.ONKEYPRESS: - case Event.ONKEYUP: - case Event.ONBLUR: - case Event.ONFOCUS: - // don't cancel possible drag start - break; - case Event.ONMOUSEMOVE: - case Event.ONTOUCHMOVE: - int currentX = WidgetUtil - .getTouchOrMouseClientX(event - .getNativeEvent()); - int currentY = WidgetUtil - .getTouchOrMouseClientY(event - .getNativeEvent()); - if (Math.abs(startX - currentX) > 3 - || Math.abs(startY - currentY) > 3) { - removeNativePreviewHandlerRegistration(); - startDrag(dragStartingEvent, event, - callback); - } - break; - default: - // on any other events, clean up this preview - // listener - removeNativePreviewHandlerRegistration(); - break; - } - } - }); - } - - private void startDrag(NativeEvent startEvent, - NativePreviewEvent triggerEvent, DragAndDropCallback callback) { - if (callback.onDragStart(startEvent)) { - dragging = true; - // just capture something to prevent text selection in IE - Event.setCapture(RootPanel.getBodyElement()); - this.callback = callback; - dragHandlerRegistration = Event - .addNativePreviewHandler(dragHandler); - callback.onDragUpdate(triggerEvent); - } - } - - private void stopDrag() { - dragging = false; - if (dragHandlerRegistration != null) { - dragHandlerRegistration.removeHandler(); - dragHandlerRegistration = null; - } - Event.releaseCapture(RootPanel.getBodyElement()); - if (callback != null) { - callback.onDragEnd(); - callback = null; - } - } - - private void cancelDrag(NativePreviewEvent event) { - callback.onDragCancel(); - callback.onDragEnd(); - stopDrag(); - event.cancel(); - event.getNativeEvent().preventDefault(); - } - - private void removeNativePreviewHandlerRegistration() { - if (dragStartNativePreviewHandlerRegistration != null) { - dragStartNativePreviewHandlerRegistration.removeHandler(); - dragStartNativePreviewHandlerRegistration = null; - } - } - } - /** * Abstract base class for Grid header and footer sections. * diff --git a/widgets/build.xml b/widgets/build.xml index f8aba7fc81..52888cc9c8 100644 --- a/widgets/build.xml +++ b/widgets/build.xml @@ -55,6 +55,7 @@ + -- cgit v1.2.3 From 2080f86e03552c56d52f488e4dcd72282cd64f62 Mon Sep 17 00:00:00 2001 From: Henrik Paul Date: Mon, 30 Mar 2015 15:28:41 +0300 Subject: Server Grid.scrollToRow takes details into account (#17270) Change-Id: I7b6d67aeb4d625a53e6fe370b729016a84e33214 --- .../vaadin/client/connectors/GridConnector.java | 161 ++++++++++++++- .../src/com/vaadin/client/widgets/Escalator.java | 2 +- server/src/com/vaadin/ui/Grid.java | 7 + .../com/vaadin/shared/ui/grid/GridClientRpc.java | 3 +- .../grid/GridScrollToRowWithDetails.java | 133 ++++++++++++ .../grid/GridScrollToRowWithDetailsTest.java | 224 +++++++++++++++++++++ .../src/com/vaadin/tests/util/PersonContainer.java | 6 +- 7 files changed, 523 insertions(+), 13 deletions(-) create mode 100644 uitest/src/com/vaadin/tests/components/grid/GridScrollToRowWithDetails.java create mode 100644 uitest/src/com/vaadin/tests/components/grid/GridScrollToRowWithDetailsTest.java (limited to 'client/src') diff --git a/client/src/com/vaadin/client/connectors/GridConnector.java b/client/src/com/vaadin/client/connectors/GridConnector.java index 51e986933c..d17f378611 100644 --- a/client/src/com/vaadin/client/connectors/GridConnector.java +++ b/client/src/com/vaadin/client/connectors/GridConnector.java @@ -477,7 +477,15 @@ public class GridConnector extends AbstractHasComponentsConnector implements } @SuppressWarnings("boxing") - private class DetailsConnectorFetcher implements DeferredWorker { + private static class DetailsConnectorFetcher implements DeferredWorker { + + private static final int FETCH_TIMEOUT_MS = 5000; + + public interface Listener { + void fetchHasBeenScheduled(int id); + + void fetchHasReturned(int id); + } /** A flag making sure that we don't call scheduleFinally many times. */ private boolean fetcherHasBeenCalled = false; @@ -493,14 +501,26 @@ public class GridConnector extends AbstractHasComponentsConnector implements public void execute() { int currentFetchId = detailsFetchCounter++; pendingFetches.add(currentFetchId); - getRpcProxy(GridServerRpc.class).sendDetailsComponents( - currentFetchId); + rpc.sendDetailsComponents(currentFetchId); fetcherHasBeenCalled = false; + if (listener != null) { + listener.fetchHasBeenScheduled(currentFetchId); + } + assert assertRequestDoesNotTimeout(currentFetchId); } }; + private DetailsConnectorFetcher.Listener listener = null; + + private final GridServerRpc rpc; + + public DetailsConnectorFetcher(GridServerRpc rpc) { + assert rpc != null : "RPC was null"; + this.rpc = rpc; + } + public void schedule() { if (!fetcherHasBeenCalled) { Scheduler.get().scheduleFinally(lazyDetailsFetcher); @@ -509,10 +529,17 @@ public class GridConnector extends AbstractHasComponentsConnector implements } public void responseReceived(int fetchId) { - /* Ignore negative fetchIds (they're pushed, not fetched) */ - if (fetchId >= 0) { - boolean success = pendingFetches.remove(fetchId); - assert success : "Received a response with an unidentified fetch id"; + + if (fetchId < 0) { + /* Ignore negative fetchIds (they're pushed, not fetched) */ + return; + } + + boolean success = pendingFetches.remove(fetchId); + assert success : "Received a response with an unidentified fetch id"; + + if (listener != null) { + listener.fetchHasReturned(fetchId); } } @@ -534,9 +561,113 @@ public class GridConnector extends AbstractHasComponentsConnector implements assert !pendingFetches.contains(fetchId) : "Fetch id " + fetchId + " timed out."; } - }.schedule(1000); + }.schedule(FETCH_TIMEOUT_MS); return true; } + + public void setListener(DetailsConnectorFetcher.Listener listener) { + // if more are needed, feel free to convert this into a collection. + this.listener = listener; + } + } + + /** + * The functionality that makes sure that the scroll position is still kept + * up-to-date even if more details are being fetched lazily. + */ + private class LazyDetailsScrollAdjuster implements DeferredWorker { + + private static final int SCROLL_TO_END_ID = -2; + private static final int NO_SCROLL_SCHEDULED = -1; + + private class ScrollStopChecker implements DeferredWorker { + private final ScheduledCommand checkCommand = new ScheduledCommand() { + @Override + public void execute() { + isScheduled = false; + if (queuedFetches.isEmpty()) { + currentRow = NO_SCROLL_SCHEDULED; + destination = null; + } + } + }; + + private boolean isScheduled = false; + + public void schedule() { + if (isScheduled) { + return; + } + Scheduler.get().scheduleDeferred(checkCommand); + isScheduled = true; + } + + @Override + public boolean isWorkPending() { + return isScheduled; + } + } + + private DetailsConnectorFetcher.Listener fetcherListener = new DetailsConnectorFetcher.Listener() { + @Override + @SuppressWarnings("boxing") + public void fetchHasBeenScheduled(int id) { + if (currentRow != NO_SCROLL_SCHEDULED) { + queuedFetches.add(id); + } + } + + @Override + @SuppressWarnings("boxing") + public void fetchHasReturned(int id) { + if (currentRow == NO_SCROLL_SCHEDULED + || queuedFetches.isEmpty()) { + return; + } + + queuedFetches.remove(id); + if (currentRow == SCROLL_TO_END_ID) { + getWidget().scrollToEnd(); + } else { + getWidget().scrollToRow(currentRow, destination); + } + + /* + * Schedule a deferred call whether we should stop adjusting for + * scrolling. + * + * This is done deferredly just because we can't be absolutely + * certain whether this most recent scrolling won't cascade into + * further lazy details loading (perhaps deferredly). + */ + scrollStopChecker.schedule(); + } + }; + + private int currentRow = NO_SCROLL_SCHEDULED; + private final Set queuedFetches = new HashSet(); + private final ScrollStopChecker scrollStopChecker = new ScrollStopChecker(); + private ScrollDestination destination; + + public LazyDetailsScrollAdjuster() { + detailsConnectorFetcher.setListener(fetcherListener); + } + + public void adjustForEnd() { + currentRow = SCROLL_TO_END_ID; + } + + public void adjustFor(int row, ScrollDestination destination) { + currentRow = row; + this.destination = destination; + } + + @Override + public boolean isWorkPending() { + return currentRow != NO_SCROLL_SCHEDULED + || !queuedFetches.isEmpty() + || scrollStopChecker.isWorkPending(); + } } /** @@ -597,7 +728,8 @@ public class GridConnector extends AbstractHasComponentsConnector implements private final CustomDetailsGenerator customDetailsGenerator = new CustomDetailsGenerator(); - private final DetailsConnectorFetcher detailsConnectorFetcher = new DetailsConnectorFetcher(); + private final DetailsConnectorFetcher detailsConnectorFetcher = new DetailsConnectorFetcher( + getRpcProxy(GridServerRpc.class)); private final DetailsListener detailsListener = new DetailsListener() { @Override @@ -618,6 +750,8 @@ public class GridConnector extends AbstractHasComponentsConnector implements } }; + private final LazyDetailsScrollAdjuster lazyDetailsScrollAdjuster = new LazyDetailsScrollAdjuster(); + @Override @SuppressWarnings("unchecked") public Grid getWidget() { @@ -637,6 +771,10 @@ public class GridConnector extends AbstractHasComponentsConnector implements registerRpc(GridClientRpc.class, new GridClientRpc() { @Override public void scrollToStart() { + /* + * no need for lazyDetailsScrollAdjuster, because the start is + * always 0, won't change a bit. + */ Scheduler.get().scheduleFinally(new ScheduledCommand() { @Override public void execute() { @@ -647,6 +785,7 @@ public class GridConnector extends AbstractHasComponentsConnector implements @Override public void scrollToEnd() { + lazyDetailsScrollAdjuster.adjustForEnd(); Scheduler.get().scheduleFinally(new ScheduledCommand() { @Override public void execute() { @@ -658,6 +797,7 @@ public class GridConnector extends AbstractHasComponentsConnector implements @Override public void scrollToRow(final int row, final ScrollDestination destination) { + lazyDetailsScrollAdjuster.adjustFor(row, destination); Scheduler.get().scheduleFinally(new ScheduledCommand() { @Override public void execute() { @@ -1266,7 +1406,8 @@ public class GridConnector extends AbstractHasComponentsConnector implements @Override public boolean isWorkPending() { - return detailsConnectorFetcher.isWorkPending(); + return detailsConnectorFetcher.isWorkPending() + || lazyDetailsScrollAdjuster.isWorkPending(); } public DetailsListener getDetailsListener() { diff --git a/client/src/com/vaadin/client/widgets/Escalator.java b/client/src/com/vaadin/client/widgets/Escalator.java index e02b4e9f83..6d9b8ee70a 100644 --- a/client/src/com/vaadin/client/widgets/Escalator.java +++ b/client/src/com/vaadin/client/widgets/Escalator.java @@ -2042,7 +2042,7 @@ public class Escalator extends Widget implements RequiresResize, TableCellElement cellOriginal = rowElement.getCells().getItem( colIndex); - if (cellIsPartOfSpan(cellOriginal)) { + if (cellOriginal == null || cellIsPartOfSpan(cellOriginal)) { continue; } diff --git a/server/src/com/vaadin/ui/Grid.java b/server/src/com/vaadin/ui/Grid.java index 69fbb41fc2..2cb7ab352a 100644 --- a/server/src/com/vaadin/ui/Grid.java +++ b/server/src/com/vaadin/ui/Grid.java @@ -250,6 +250,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier, * * @since 7.5.0 * @author Vaadin Ltd + * @see DetailsGenerator#NULL */ public interface DetailsGenerator extends Serializable { @@ -3968,6 +3969,9 @@ public class Grid extends AbstractComponent implements SelectionNotifier, /** * Scrolls to a certain item, using {@link ScrollDestination#ANY}. + *

+ * If the item has visible details, its size will also be taken into + * account. * * @param itemId * id of item to scroll to. @@ -3980,6 +3984,9 @@ public class Grid extends AbstractComponent implements SelectionNotifier, /** * Scrolls to a certain item, using user-specified scroll destination. + *

+ * If the item has visible details, its size will also be taken into + * account. * * @param itemId * id of item to scroll to. diff --git a/shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java b/shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java index d4707f9ba5..3c6d993482 100644 --- a/shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java +++ b/shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java @@ -28,7 +28,8 @@ import com.vaadin.shared.communication.ClientRpc; public interface GridClientRpc extends ClientRpc { /** - * Command client Grid to scroll to a specific data row. + * Command client Grid to scroll to a specific data row and its (optional) + * details. * * @param row * zero-based row index. If the row index is below zero or above diff --git a/uitest/src/com/vaadin/tests/components/grid/GridScrollToRowWithDetails.java b/uitest/src/com/vaadin/tests/components/grid/GridScrollToRowWithDetails.java new file mode 100644 index 0000000000..5659f01bdd --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/grid/GridScrollToRowWithDetails.java @@ -0,0 +1,133 @@ +/* + * 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; + +import com.vaadin.annotations.Theme; +import com.vaadin.data.Property.ValueChangeEvent; +import com.vaadin.data.Property.ValueChangeListener; +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.util.Person; +import com.vaadin.tests.util.PersonContainer; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.CheckBox; +import com.vaadin.ui.Component; +import com.vaadin.ui.Grid; +import com.vaadin.ui.Grid.DetailsGenerator; +import com.vaadin.ui.Grid.RowReference; +import com.vaadin.ui.Grid.SelectionMode; +import com.vaadin.ui.Label; +import com.vaadin.ui.Layout; +import com.vaadin.ui.TextField; +import com.vaadin.ui.UI; +import com.vaadin.ui.VerticalLayout; +import com.vaadin.ui.themes.ValoTheme; + +@Theme(ValoTheme.THEME_NAME) +public class GridScrollToRowWithDetails extends UI { + + private final DetailsGenerator detailsGenerator = new DetailsGenerator() { + @Override + public Component getDetails(RowReference rowReference) { + Person person = (Person) rowReference.getItemId(); + Label label = new Label(person.getFirstName() + " " + + person.getLastName()); + label.setHeight("30px"); + return label; + } + }; + + private TextField numberTextField; + private Grid grid; + + @Override + protected void init(VaadinRequest request) { + + Layout layout = new VerticalLayout(); + + grid = new Grid(PersonContainer.createWithTestData(1000)); + grid.setSelectionMode(SelectionMode.NONE); + layout.addComponent(grid); + + final CheckBox checkbox = new CheckBox("Details generator"); + checkbox.addValueChangeListener(new ValueChangeListener() { + @Override + @SuppressWarnings("boxing") + public void valueChange(ValueChangeEvent event) { + if (checkbox.getValue()) { + grid.setDetailsGenerator(detailsGenerator); + } else { + grid.setDetailsGenerator(DetailsGenerator.NULL); + } + } + }); + layout.addComponent(checkbox); + + numberTextField = new TextField("Row"); + numberTextField.setImmediate(false); + layout.addComponent(numberTextField); + + layout.addComponent(new Button("Toggle", new Button.ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + toggle(); + } + })); + + layout.addComponent(new Button("Scroll to", new Button.ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + scrollTo(); + } + })); + + layout.addComponent(new Button("Toggle and scroll", + new Button.ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + toggle(); + scrollTo(); + } + })); + layout.addComponent(new Button("Scroll and toggle", + new Button.ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + scrollTo(); + toggle(); + } + })); + + setContent(layout); + } + + private void toggle() { + Object itemId = getItemId(); + boolean isVisible = grid.isDetailsVisible(itemId); + grid.setDetailsVisible(itemId, !isVisible); + } + + private void scrollTo() { + grid.scrollTo(getItemId()); + } + + private Object getItemId() { + int row = Integer.parseInt(numberTextField.getValue()); + Object itemId = grid.getContainerDataSource().getIdByIndex(row); + return itemId; + } + +} diff --git a/uitest/src/com/vaadin/tests/components/grid/GridScrollToRowWithDetailsTest.java b/uitest/src/com/vaadin/tests/components/grid/GridScrollToRowWithDetailsTest.java new file mode 100644 index 0000000000..b6ecd3f6e2 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/grid/GridScrollToRowWithDetailsTest.java @@ -0,0 +1,224 @@ +/* + * 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; + +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.JavascriptExecutor; +import org.openqa.selenium.WebElement; + +import com.vaadin.shared.ui.grid.Range; +import com.vaadin.testbench.elements.ButtonElement; +import com.vaadin.testbench.elements.CheckBoxElement; +import com.vaadin.testbench.elements.TextFieldElement; +import com.vaadin.tests.components.grid.basicfeatures.element.CustomGridElement; +import com.vaadin.tests.tb3.MultiBrowserTest; + +public class GridScrollToRowWithDetailsTest extends MultiBrowserTest { + + private static class Param { + private final int rowIndex; + private final boolean useGenerator; + private final boolean scrollFirstToBottom; + private final int scrollTarget; + + public Param(int rowIndex, boolean useGenerator, + boolean scrollFirstToBottom, int scrollTarget) { + this.rowIndex = rowIndex; + this.useGenerator = useGenerator; + this.scrollFirstToBottom = scrollFirstToBottom; + this.scrollTarget = Math.max(0, scrollTarget); + } + + public int getRowIndex() { + return rowIndex; + } + + public boolean useGenerator() { + return useGenerator; + } + + public boolean scrollFirstToBottom() { + return scrollFirstToBottom; + } + + public int getScrollTarget() { + return scrollTarget; + } + + @Override + public String toString() { + return "Param [rowIndex=" + getRowIndex() + ", useGenerator=" + + useGenerator() + ", scrollFirstToBottom=" + + scrollFirstToBottom() + ", scrollTarget=" + + getScrollTarget() + "]"; + } + } + + public static Collection parameters() { + List data = new ArrayList(); + + int[][] params = new int[][] {// @formatter:off + // row, top+noGen, top+gen, bot+noGen, bot+gen + { 0, 0, 0, 0, 0 }, + { 500, 18741, 18723, 19000, 19000 }, + { 999, 37703, 37685, 37703, 37685 }, + }; + // @formatter:on + + for (int i[] : params) { + int rowIndex = i[0]; + int targetTopScrollWithoutGenerator = i[1]; + int targetTopScrollWithGenerator = i[2]; + int targetBottomScrollWithoutGenerator = i[3]; + int targetBottomScrollWithGenerator = i[4]; + + data.add(new Param(rowIndex, false, false, + targetTopScrollWithoutGenerator)); + data.add(new Param(rowIndex, true, false, + targetTopScrollWithGenerator)); + data.add(new Param(rowIndex, false, true, + targetBottomScrollWithoutGenerator)); + data.add(new Param(rowIndex, true, true, + targetBottomScrollWithGenerator)); + } + + return data; + } + + @Before + public void setUp() { + setDebug(true); + } + + @Test + public void toggleAndScroll() throws Throwable { + for (Param param : parameters()) { + try { + openTestURL(); + useGenerator(param.useGenerator()); + scrollToBottom(param.scrollFirstToBottom()); + + // the tested method + toggleAndScroll(param.getRowIndex()); + + Range allowedRange = Range.withLength( + param.getScrollTarget() - 5, 10); + assertTrue( + allowedRange + " does not contain " + getScrollTop(), + allowedRange.contains(getScrollTop())); + } catch (Throwable t) { + throw new Throwable("" + param, t); + } + } + } + + @Test + public void scrollAndToggle() throws Throwable { + for (Param param : parameters()) { + try { + openTestURL(); + useGenerator(param.useGenerator()); + scrollToBottom(param.scrollFirstToBottom()); + + // the tested method + scrollAndToggle(param.getRowIndex()); + + Range allowedRange = Range.withLength( + param.getScrollTarget() - 5, 10); + assertTrue( + allowedRange + " does not contain " + getScrollTop(), + allowedRange.contains(getScrollTop())); + } catch (Throwable t) { + throw new Throwable("" + param, t); + } + } + } + + private void scrollToBottom(boolean scrollFirstToBottom) { + if (scrollFirstToBottom) { + executeScript("arguments[0].scrollTop = 9999999", + getVerticalScrollbar()); + } + } + + private void useGenerator(boolean use) { + CheckBoxElement checkBox = $(CheckBoxElement.class).first(); + boolean isChecked = isCheckedValo(checkBox); + if (use != isChecked) { + clickValo(checkBox); + } + } + + @SuppressWarnings("boxing") + private boolean isCheckedValo(CheckBoxElement checkBoxElement) { + WebElement checkbox = checkBoxElement.findElement(By.tagName("input")); + Object value = executeScript("return arguments[0].checked;", checkbox); + return (Boolean) value; + } + + private void clickValo(CheckBoxElement checkBoxElement) { + checkBoxElement.findElement(By.tagName("label")).click(); + } + + private Object executeScript(String string, Object... param) { + return ((JavascriptExecutor) getDriver()).executeScript(string, param); + } + + private void scrollAndToggle(int row) { + setRow(row); + getScrollAndToggle().click(); + } + + private void toggleAndScroll(int row) { + setRow(row); + getToggleAndScroll().click(); + } + + private ButtonElement getScrollAndToggle() { + return $(ButtonElement.class).caption("Scroll and toggle").first(); + } + + private ButtonElement getToggleAndScroll() { + return $(ButtonElement.class).caption("Toggle and scroll").first(); + } + + private void setRow(int row) { + $(TextFieldElement.class).first().setValue(String.valueOf(row)); + } + + private CustomGridElement getGrid() { + return $(CustomGridElement.class).first(); + } + + private int getScrollTop() { + return ((Long) executeScript("return arguments[0].scrollTop;", + getVerticalScrollbar())).intValue(); + } + + private WebElement getVerticalScrollbar() { + WebElement scrollBar = getGrid().findElement( + By.className("v-grid-scroller-vertical")); + return scrollBar; + } +} diff --git a/uitest/src/com/vaadin/tests/util/PersonContainer.java b/uitest/src/com/vaadin/tests/util/PersonContainer.java index 611e5d3adb..709086be29 100644 --- a/uitest/src/com/vaadin/tests/util/PersonContainer.java +++ b/uitest/src/com/vaadin/tests/util/PersonContainer.java @@ -32,10 +32,14 @@ public class PersonContainer extends BeanItemContainer implements } public static PersonContainer createWithTestData() { + return createWithTestData(100); + } + + public static PersonContainer createWithTestData(int size) { PersonContainer c = null; Random r = new Random(0); c = new PersonContainer(); - for (int i = 0; i < 100; i++) { + for (int i = 0; i < size; i++) { Person p = new Person(); p.setFirstName(TestDataGenerator.getFirstName(r)); p.setLastName(TestDataGenerator.getLastName(r)); -- cgit v1.2.3 From 0e7755958b46434185cb1e6e2ec8aa6932b32f34 Mon Sep 17 00:00:00 2001 From: Pekka Hyvönen Date: Mon, 23 Mar 2015 13:42:07 +0200 Subject: API for column hiding toggle caption in Grid (#17272) Fixes column toggle not getting a caption when a hidable column is added. Fixes column toggle not getting a caption on columns with widget in header. Change-Id: Ie10ada793a3635302603f684f232cadaef74a982 --- .../vaadin/client/connectors/GridConnector.java | 1 + client/src/com/vaadin/client/widgets/Grid.java | 52 +++++++++++++++++----- server/src/com/vaadin/ui/Grid.java | 46 ++++++++++++++++++- .../com/vaadin/shared/ui/grid/GridColumnState.java | 3 ++ .../grid/basicfeatures/GridBasicFeatures.java | 31 ++++++++++--- .../grid/basicfeatures/GridColumnReorderTest.java | 2 +- .../server/GridColumnVisibilityTest.java | 50 ++++++++++++++++++--- 7 files changed, 159 insertions(+), 26 deletions(-) (limited to 'client/src') diff --git a/client/src/com/vaadin/client/connectors/GridConnector.java b/client/src/com/vaadin/client/connectors/GridConnector.java index 0e2ee0046b..5554664566 100644 --- a/client/src/com/vaadin/client/connectors/GridConnector.java +++ b/client/src/com/vaadin/client/connectors/GridConnector.java @@ -1197,6 +1197,7 @@ public class GridConnector extends AbstractHasComponentsConnector implements column.setHidden(state.hidden); column.setHidable(state.hidable); + column.setHidingToggleCaption(state.hidingToggleCaption); column.setEditable(state.editable); column.setEditorConnector((AbstractFieldConnector) state.editorConnector); diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index 20b8844623..f9c6ed28fe 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -3096,7 +3096,7 @@ public class Grid extends ResizeComposite implements column.setHidden(!event.getValue(), true); } }); - updateColumnHidingToggleCaption(column, toggle); + updateHidingToggleCaption(column, toggle); columnToHidingToggleMap.put(column, toggle); return toggle; } @@ -3133,22 +3133,20 @@ public class Grid extends ResizeComposite implements hasValue.setStyleName("hidden", hidden); } - private void updateColumnHidingToggleCaption(Column column) { - updateColumnHidingToggleCaption(column, + private void updateHidingToggleCaption(Column column) { + updateHidingToggleCaption(column, columnToHidingToggleMap.get(column)); } - private void updateColumnHidingToggleCaption(Column column, + private void updateHidingToggleCaption(Column column, ToggleButton toggle) { - String caption = column.headerCaption; - if (caption == null || caption.isEmpty()) { - // TODO what if the content is a widget? - HeaderCell cell = getDefaultHeaderRow().getCell(column); - caption = cell.getText(); + String caption = column.getHidingToggleCaption(); + if (caption == null) { + caption = column.headerCaption; + // the caption might still be null, but that is the users fault } toggle.setText(caption); } - } /** @@ -3782,6 +3780,8 @@ public class Grid extends ResizeComposite implements private String headerCaption = ""; + private String hidingToggleCaption = null; + private double minimumWidthPx = GridConstants.DEFAULT_MIN_WIDTH; private double maximumWidthPx = GridConstants.DEFAULT_MAX_WIDTH; private int expandRatio = GridConstants.DEFAULT_EXPAND_RATIO; @@ -3891,7 +3891,7 @@ public class Grid extends ResizeComposite implements if (row != null) { row.getCell(this).setText(headerCaption); if (isHidable()) { - grid.columnHider.updateColumnHidingToggleCaption(this); + grid.columnHider.updateHidingToggleCaption(this); } } } @@ -4143,6 +4143,36 @@ public class Grid extends ResizeComposite implements return hidable; } + /** + * Sets the hiding toggle's caption for this column. Shown in the toggle + * for this column in the grid's sidebar when the column is + * {@link #isHidable() hidable}. + *

+ * Defaults to null, when will use whatever is set with + * {@link #setHeaderCaption(String)}. + * + * @since + * @param hidingToggleCaption + * the caption for the hiding toggle for this column + */ + public void setHidingToggleCaption(String hidingToggleCaption) { + this.hidingToggleCaption = hidingToggleCaption; + if (isHidable()) { + grid.columnHider.updateHidingToggleCaption(this); + } + } + + /** + * Gets the hiding toggle caption for this column. + * + * @since + * @see #setHidingToggleCaption(String) + * @return the hiding toggle's caption for this column + */ + public String getHidingToggleCaption() { + return hidingToggleCaption; + } + @Override public String toString() { String details = ""; diff --git a/server/src/com/vaadin/ui/Grid.java b/server/src/com/vaadin/ui/Grid.java index 2cb7ab352a..cd97e90e2e 100644 --- a/server/src/com/vaadin/ui/Grid.java +++ b/server/src/com/vaadin/ui/Grid.java @@ -2285,6 +2285,46 @@ public class Grid extends AbstractComponent implements SelectionNotifier, return this; } + /** + * Gets the caption of the hiding toggle for this column. + * + * @since + * @see #setHidingToggleCaption(String) + * @return the caption for the hiding toggle for this column + * @throws IllegalStateException + * if the column is no longer attached to any grid + */ + public String getHidingToggleCaption() throws IllegalStateException { + checkColumnIsAttached(); + return state.hidingToggleCaption; + } + + /** + * Sets the caption of the hiding toggle for this column. Shown in the + * toggle for this column in the grid's sidebar when the column is + * {@link #isHidable() hidable}. + *

+ * By default, before triggering this setter, a user friendly version of + * the column's {@link #getPropertyId() property id} is used. + *

+ * NOTE: setting this to null or empty string + * might cause the hiding toggle to not render correctly. + * + * @since + * @param hidingToggleCaption + * the text to show in the column hiding toggle + * @return the column itself + * @throws IllegalStateException + * if the column is no longer attached to any grid + */ + public Column setHidingToggleCaption(String hidingToggleCaption) + throws IllegalStateException { + checkColumnIsAttached(); + state.hidingToggleCaption = hidingToggleCaption; + grid.markAsDirty(); + return this; + } + /** * Returns the width (in pixels). By default a column is 100px wide. * @@ -3860,8 +3900,10 @@ public class Grid extends AbstractComponent implements SelectionNotifier, header.addColumn(datasourcePropertyId); footer.addColumn(datasourcePropertyId); - column.setHeaderCaption(SharedUtil.propertyIdToHumanFriendly(String - .valueOf(datasourcePropertyId))); + String humanFriendlyPropertyId = SharedUtil + .propertyIdToHumanFriendly(String.valueOf(datasourcePropertyId)); + column.setHeaderCaption(humanFriendlyPropertyId); + column.setHidingToggleCaption(humanFriendlyPropertyId); return column; } diff --git a/shared/src/com/vaadin/shared/ui/grid/GridColumnState.java b/shared/src/com/vaadin/shared/ui/grid/GridColumnState.java index b966c53352..5aa9ea9b65 100644 --- a/shared/src/com/vaadin/shared/ui/grid/GridColumnState.java +++ b/shared/src/com/vaadin/shared/ui/grid/GridColumnState.java @@ -82,4 +82,7 @@ public class GridColumnState implements Serializable { /** Can the column be hidden by the UI. */ public boolean hidable = false; + + /** The caption for the column hiding toggle. */ + public String hidingToggleCaption; } diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java index d3b1237cf9..6f4c7df38c 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java @@ -768,17 +768,23 @@ public class GridBasicFeatures extends AbstractComponentTest { new Command() { boolean wasHidable; + boolean wasHidden; + String wasColumnHidingToggleCaption; @Override public void execute(Grid grid, String value, Object data) { String columnProperty = getColumnProperty((Integer) data); - if (grid.getColumn(columnProperty) == null) { - grid.addColumn(columnProperty); - grid.getColumn(columnProperty).setHidable( - wasHidable); + Column column = grid.getColumn(columnProperty); + if (column == null) { + column = grid.addColumn(columnProperty); + column.setHidable(wasHidable); + column.setHidden(wasHidden); + column.setHidingToggleCaption(wasColumnHidingToggleCaption); } else { - wasHidable = grid.getColumn(columnProperty) - .isHidable(); + wasHidable = column.isHidable(); + wasHidden = column.isHidden(); + wasColumnHidingToggleCaption = column + .getHidingToggleCaption(); grid.removeColumn(columnProperty); } } @@ -840,6 +846,19 @@ public class GridBasicFeatures extends AbstractComponentTest { grid.getColumn(propertyId).setHidden(hidden); } }, getColumnProperty(c)); + createClickAction("Change hiding toggle caption", + getColumnProperty(c), new Command() { + int count = 0; + + @Override + public void execute(Grid grid, String value, Object data) { + final String columnProperty = getColumnProperty((Integer) data); + grid.getColumn(columnProperty) + .setHidingToggleCaption( + columnProperty + " caption " + + count++); + } + }, null, c); createCategory("Column " + c + " Width", getColumnProperty(c)); diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderTest.java index 68ba5f5c54..d779a5c81a 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderTest.java @@ -369,7 +369,7 @@ public class GridColumnReorderTest extends GridBasicClientFeaturesTest { assertColumnHeaderOrder(1, 3, 4, 5, 2); // when then - dragAndDropColumnHeader(0, 1, 3, CellSide.RIGHT); + dragAndDropColumnHeader(0, 1, 4, CellSide.LEFT); assertColumnHeaderOrder(1, 4, 3, 5, 2); dragAndDropColumnHeader(0, 2, 4, CellSide.LEFT); diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridColumnVisibilityTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridColumnVisibilityTest.java index 37eda1c28f..7942650576 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridColumnVisibilityTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridColumnVisibilityTest.java @@ -24,7 +24,6 @@ import static org.junit.Assert.assertTrue; import java.util.List; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.openqa.selenium.By; import org.openqa.selenium.WebElement; @@ -139,12 +138,15 @@ public class GridColumnVisibilityTest extends GridBasicFeaturesTest { } @Test - @Ignore - // known issue, column caption not passed to toggle when added again - public void testColumnHiding_whenHidableColumnAdded_toggleAdded() { + public void testColumnHiding_whenHidableColumnAdded_toggleWithCorrectCaptionAdded() { selectMenuPath("Component", "Size", "Width", "100%"); toggleColumnHidable(0); toggleColumnHidable(1); + toggleColumnHidingToggleCaptionChange(0); + getSidebarOpenButton().click(); + assertEquals("Column 0 caption 0", getColumnHidingToggle(0).getText()); + getSidebarOpenButton().click(); + addRemoveColumn(0); addRemoveColumn(4); addRemoveColumn(5); @@ -163,7 +165,43 @@ public class GridColumnVisibilityTest extends GridBasicFeaturesTest { assertColumnHeaderOrder(1, 2, 3, 11, 0); getSidebarOpenButton().click(); - assertNotNull(getColumnHidingToggle(0)); + assertEquals("Column 0 caption 0", getColumnHidingToggle(0).getText()); + } + + @Test + public void testColumnHidingToggleCaption_settingToggleCaption_updatesToggle() { + toggleColumnHidable(1); + getSidebarOpenButton().click(); + assertEquals("column 1", getGridElement().getHeaderCell(0, 1).getText() + .toLowerCase()); + assertEquals("Column 1", getColumnHidingToggle(1).getText()); + + toggleColumnHidingToggleCaptionChange(1); + assertEquals("column 1", getGridElement().getHeaderCell(0, 1).getText() + .toLowerCase()); + assertEquals("Column 1 caption 0", getColumnHidingToggle(1).getText()); + + toggleColumnHidingToggleCaptionChange(1); + assertEquals("Column 1 caption 1", getColumnHidingToggle(1).getText()); + } + + @Test + public void testColumnHidingToggleCaption_settingWidgetToHeader_toggleCaptionStays() { + toggleColumnHidable(1); + getSidebarOpenButton().click(); + assertEquals("column 1", getGridElement().getHeaderCell(0, 1).getText() + .toLowerCase()); + assertEquals("Column 1", getColumnHidingToggle(1).getText()); + + selectMenuPath("Component", "Columns", "Column 1", "Header Type", + "Widget Header"); + + assertEquals("Column 1", getColumnHidingToggle(1).getText()); + } + + private void toggleColumnHidingToggleCaptionChange(int index) { + selectMenuPath("Component", "Columns", "Column " + index, + "Change hiding toggle caption"); } @Test @@ -264,7 +302,7 @@ public class GridColumnVisibilityTest extends GridBasicFeaturesTest { List elements = sidebar.findElements(By .className("column-hiding-toggle")); for (WebElement e : elements) { - if (("Column " + columnIndex).equalsIgnoreCase(e.getText())) { + if ((e.getText().toLowerCase()).startsWith("column " + columnIndex)) { return e; } } -- cgit v1.2.3 From 849c92d431ca2d4d591b2ca61c541aa40f754e83 Mon Sep 17 00:00:00 2001 From: Pekka Hyvönen Date: Thu, 26 Mar 2015 21:23:16 +0200 Subject: Take hidden columns into account with spanned cells #17287 Change-Id: I595c6b7da061ebfa495c7f9f649c935a48190b66 --- client/src/com/vaadin/client/widgets/Grid.java | 45 +++- .../grid/basicfeatures/GridColumnHidingTest.java | 274 +++++++++++++++++++++ 2 files changed, 306 insertions(+), 13 deletions(-) (limited to 'client/src') diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index f9c6ed28fe..73dcccfd1e 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -453,6 +453,8 @@ public class Grid extends ResizeComposite implements } HashSet> columnGroup = new HashSet>(); + // NOTE: this doesn't care about hidden columns, those are + // filtered in calculateColspans() for (Column column : columns) { if (!cells.containsKey(column)) { throw new IllegalArgumentException( @@ -516,39 +518,46 @@ public class Grid extends ResizeComposite implements } void calculateColspans() { - // Reset all cells for (CELLTYPE cell : this.cells.values()) { cell.setColspan(1); } - - List> columnOrder = new ArrayList>( - section.grid.getColumns()); // Set colspan for grouped cells for (Set> group : cellGroups.keySet()) { - if (!checkCellGroupAndOrder(columnOrder, group)) { + if (!checkMergedCellIsContinuous(group)) { + // on error simply break the merged cell cellGroups.get(group).setColspan(1); } else { - int colSpan = group.size(); - cellGroups.get(group).setColspan(colSpan); + int colSpan = 0; + for (Column column : group) { + if (!column.isHidden()) { + colSpan++; + } + } + // colspan can't be 0 + cellGroups.get(group).setColspan(Math.max(1, colSpan)); } } } - private boolean checkCellGroupAndOrder( - List> columnOrder, Set> cellGroup) { - if (!columnOrder.containsAll(cellGroup)) { + private boolean checkMergedCellIsContinuous( + Set> mergedCell) { + // no matter if hidden or not, just check for continuous order + final List> columnOrder = new ArrayList>( + section.grid.getColumns()); + + if (!columnOrder.containsAll(mergedCell)) { return false; } for (int i = 0; i < columnOrder.size(); ++i) { - if (!cellGroup.contains(columnOrder.get(i))) { + if (!mergedCell.contains(columnOrder.get(i))) { continue; } - for (int j = 1; j < cellGroup.size(); ++j) { - if (!cellGroup.contains(columnOrder.get(i + j))) { + for (int j = 1; j < mergedCell.size(); ++j) { + if (!mergedCell.contains(columnOrder.get(i + j))) { return false; } } @@ -791,6 +800,14 @@ public class Grid extends ResizeComposite implements assert grid != null; return grid; } + + protected void updateColSpans() { + for (ROWTYPE row : rows) { + if (row.hasSpannedCells()) { + row.calculateColspans(); + } + } + } } /** @@ -4092,6 +4109,8 @@ public class Grid extends ResizeComposite implements } } grid.columnHider.updateToggleValue(this); + grid.header.updateColSpans(); + grid.footer.updateColSpans(); scheduleColumnWidthRecalculator(); this.grid.fireEvent(new ColumnVisibilityChangeEvent(this, hidden, userOriginated)); diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnHidingTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnHidingTest.java index ac04ed1a56..1213e02799 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnHidingTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnHidingTest.java @@ -31,12 +31,18 @@ import org.openqa.selenium.Keys; import org.openqa.selenium.WebElement; import org.openqa.selenium.interactions.Actions; +import com.vaadin.testbench.TestBenchElement; import com.vaadin.testbench.elements.GridElement.GridCellElement; import com.vaadin.testbench.parallel.TestCategory; @TestCategory("grid") public class GridColumnHidingTest extends GridBasicClientFeaturesTest { + private static final String CAPTION_0_1 = "Join column cells 0, 1"; + private static final String CAPTION_1_2 = "Join columns 1, 2"; + private static final String CAPTION_3_4_5 = "Join columns 3, 4, 5"; + private static final String CAPTION_ALL = "Join all columns"; + @Before public void before() { openTestURL(); @@ -618,11 +624,258 @@ public class GridColumnHidingTest extends GridBasicClientFeaturesTest { verifyColumnIsNotFrozen(4); } + @Test + public void testSpannedCells_hidingColumnInBeginning_rendersSpannedCellCorrectly() { + loadSpannedCellsFixture(); + verifySpannedCellsFixtureStart(); + + toggleHideColumnAPI(0); + + verifyNumberOfCellsInHeader(0, 7); + verifyNumberOfCellsInHeader(1, 5); + verifyNumberOfCellsInHeader(2, 6); + verifyNumberOfCellsInHeader(3, 1); + verifyHeaderCellContent(1, 0, CAPTION_0_1); + verifyHeaderCellContent(1, 2, CAPTION_3_4_5); + verifyHeaderCellContent(2, 0, CAPTION_1_2); + verifyHeaderCellContent(3, 0, CAPTION_ALL); + verifyHeaderCellColspan(1, 0, 1); + verifyHeaderCellColspan(1, 2, 3); + verifyHeaderCellColspan(2, 1, 2); + + toggleHideColumnAPI(0); + + verifySpannedCellsFixtureStart(); + + toggleHideColumnAPI(1); + + verifyNumberOfCellsInHeader(0, 7); + verifyNumberOfCellsInHeader(1, 5); + verifyNumberOfCellsInHeader(2, 7); + verifyNumberOfCellsInHeader(3, 1); + verifyHeaderCellContent(1, 0, CAPTION_0_1); + verifyHeaderCellContent(1, 2, CAPTION_3_4_5); + verifyHeaderCellContent(2, 1, CAPTION_1_2); + verifyHeaderCellContent(3, 0, CAPTION_ALL); + verifyHeaderCellColspan(1, 0, 1); + verifyHeaderCellColspan(1, 2, 3); + verifyHeaderCellColspan(2, 1, 1); + + toggleHideColumnAPI(3); + + verifyNumberOfCellsInHeader(0, 6); + verifyNumberOfCellsInHeader(1, 5); + verifyNumberOfCellsInHeader(2, 6); + verifyNumberOfCellsInHeader(3, 1); + verifyHeaderCellContent(1, 0, CAPTION_0_1); + verifyHeaderCellContent(1, 2, CAPTION_3_4_5); + verifyHeaderCellContent(2, 1, CAPTION_1_2); + verifyHeaderCellContent(3, 0, CAPTION_ALL); + verifyHeaderCellColspan(1, 0, 1); + verifyHeaderCellColspan(1, 2, 2); + verifyHeaderCellColspan(2, 1, 1); + + toggleHideColumnAPI(1); + + verifyNumberOfCellsInHeader(0, 7); + verifyNumberOfCellsInHeader(1, 5); + verifyNumberOfCellsInHeader(2, 6); + verifyNumberOfCellsInHeader(3, 1); + verifyHeaderCellContent(1, 0, CAPTION_0_1); + verifyHeaderCellContent(1, 3, CAPTION_3_4_5); + verifyHeaderCellContent(2, 1, CAPTION_1_2); + verifyHeaderCellContent(3, 0, CAPTION_ALL); + verifyHeaderCellColspan(1, 0, 2); + verifyHeaderCellColspan(1, 3, 2); + verifyHeaderCellColspan(2, 1, 2); + + toggleHideColumnAPI(3); + + verifySpannedCellsFixtureStart(); + } + + @Test + public void testSpannedCells_hidingColumnInMiddle_rendersSpannedCellCorrectly() { + loadSpannedCellsFixture(); + verifySpannedCellsFixtureStart(); + + toggleHideColumnAPI(4); + + verifyNumberOfCellsInHeader(0, 7); + verifyNumberOfCellsInHeader(1, 5); + verifyNumberOfCellsInHeader(2, 6); + verifyNumberOfCellsInHeader(3, 1); + verifyHeaderCellContent(1, 0, CAPTION_0_1); + verifyHeaderCellContent(1, 3, CAPTION_3_4_5); + verifyHeaderCellContent(2, 1, CAPTION_1_2); + verifyHeaderCellContent(3, 0, CAPTION_ALL); + verifyHeaderCellColspan(1, 0, 2); + verifyHeaderCellColspan(1, 3, 2); + verifyHeaderCellColspan(2, 1, 2); + + toggleHideColumnAPI(4); + + verifySpannedCellsFixtureStart(); + } + + @Test + public void testSpannedCells_hidingColumnInEnd_rendersSpannedCellCorrectly() { + loadSpannedCellsFixture(); + verifySpannedCellsFixtureStart(); + + toggleHideColumnAPI(1); + + verifyNumberOfCellsInHeader(0, 7); + verifyNumberOfCellsInHeader(1, 5); + verifyNumberOfCellsInHeader(2, 7); + verifyNumberOfCellsInHeader(3, 1); + verifyHeaderCellContent(1, 0, CAPTION_0_1); + verifyHeaderCellContent(1, 2, CAPTION_3_4_5); + verifyHeaderCellContent(2, 1, CAPTION_1_2); + verifyHeaderCellContent(3, 1, CAPTION_ALL); + verifyHeaderCellColspan(1, 0, 1); + verifyHeaderCellColspan(1, 2, 3); + verifyHeaderCellColspan(2, 1, 1); + + toggleHideColumnAPI(1); + + verifySpannedCellsFixtureStart(); + + toggleHideColumnAPI(2); + + verifyNumberOfCellsInHeader(0, 7); + verifyNumberOfCellsInHeader(1, 4); + verifyNumberOfCellsInHeader(2, 7); + verifyNumberOfCellsInHeader(3, 1); + verifyHeaderCellContent(1, 0, CAPTION_0_1); + verifyHeaderCellContent(1, 3, CAPTION_3_4_5); + verifyHeaderCellContent(2, 1, CAPTION_1_2); + verifyHeaderCellContent(3, 0, CAPTION_ALL); + verifyHeaderCellColspan(1, 0, 2); + verifyHeaderCellColspan(1, 3, 3); + verifyHeaderCellColspan(2, 1, 1); + + toggleHideColumnAPI(5); + + verifyNumberOfCellsInHeader(0, 6); + verifyNumberOfCellsInHeader(1, 4); + verifyNumberOfCellsInHeader(2, 6); + verifyNumberOfCellsInHeader(3, 1); + verifyHeaderCellContent(1, 0, CAPTION_0_1); + verifyHeaderCellContent(1, 3, CAPTION_3_4_5); + verifyHeaderCellContent(2, 1, CAPTION_1_2); + verifyHeaderCellContent(3, 0, CAPTION_ALL); + verifyHeaderCellColspan(1, 0, 2); + verifyHeaderCellColspan(1, 3, 2); + verifyHeaderCellColspan(2, 1, 1); + + toggleHideColumnAPI(5); + toggleHideColumnAPI(2); + + verifySpannedCellsFixtureStart(); + } + + @Test + public void testSpannedCells_spanningCellOverHiddenColumn_rendersSpannedCellCorrectly() { + selectMenuPath("Component", "State", "Width", "1000px"); + appendHeaderRow(); + toggleHideColumnAPI(4); + toggleHideColumnAPI(8); + toggleHideColumnAPI(9); + toggleHideColumnAPI(10); + toggleHideColumnAPI(11); + assertColumnHeaderOrder(0, 1, 2, 3, 5, 6, 7); + verifyNumberOfCellsInHeader(1, 7); + + mergeHeaderCellsTwoThreeFour(2); + + verifyNumberOfCellsInHeader(1, 6); + verifyHeaderCellContent(1, 3, CAPTION_3_4_5); + verifyHeaderCellColspan(1, 3, 2); + } + + @Test + public void testSpannedCells_spanningCellAllHiddenColumns_rendersSpannedCellCorrectly() { + selectMenuPath("Component", "State", "Width", "1000px"); + appendHeaderRow(); + toggleHideColumnAPI(3); + toggleHideColumnAPI(4); + toggleHideColumnAPI(5); + toggleHideColumnAPI(8); + toggleHideColumnAPI(9); + toggleHideColumnAPI(10); + toggleHideColumnAPI(11); + assertColumnHeaderOrder(0, 1, 2, 6, 7); + verifyNumberOfCellsInHeader(1, 5); + + mergeHeaderCellsTwoThreeFour(2); + + verifyNumberOfCellsInHeader(1, 5); + verifyHeaderCellColspan(1, 0, 1); + verifyHeaderCellColspan(1, 1, 1); + verifyHeaderCellColspan(1, 2, 1); + verifyHeaderCellColspan(1, 3, 1); + verifyHeaderCellColspan(1, 4, 1); + } + + private void loadSpannedCellsFixture() { + selectMenuPath("Component", "State", "Width", "1000px"); + appendHeaderRow(); + appendHeaderRow(); + appendHeaderRow(); + mergeHeaderCellsTwoThreeFour(2); + mergeHeaderCellsZeroOne(2); + mergeHeaderCellsOneTwo(3); + mergeHeaderCellsAll(4); + toggleHideColumnAPI(8); + toggleHideColumnAPI(9); + toggleHideColumnAPI(10); + toggleHideColumnAPI(11); + } + + private void verifySpannedCellsFixtureStart() { + assertColumnHeaderOrder(0, 1, 2, 3, 4, 5, 6, 7); + verifyNumberOfCellsInHeader(0, 8); + verifyNumberOfCellsInHeader(1, 5); + verifyNumberOfCellsInHeader(2, 7); + verifyNumberOfCellsInHeader(3, 1); + verifyHeaderCellContent(1, 0, CAPTION_0_1); + verifyHeaderCellContent(1, 3, CAPTION_3_4_5); + verifyHeaderCellContent(2, 1, CAPTION_1_2); + verifyHeaderCellContent(3, 0, CAPTION_ALL); + verifyHeaderCellColspan(1, 0, 2); + verifyHeaderCellColspan(1, 3, 3); + verifyHeaderCellColspan(2, 1, 2); + } + private void toggleFrozenColumns(int count) { selectMenuPath("Component", "State", "Frozen column count", count + " columns"); } + private void verifyHeaderCellColspan(int row, int column, int colspan) { + assertEquals(Integer.valueOf(colspan), Integer.valueOf(Integer + .parseInt(getGridElement().getHeaderCell(row, column) + .getAttribute("colspan")))); + } + + private void verifyNumberOfCellsInHeader(int row, int numberOfCells) { + int size = 0; + for (TestBenchElement cell : getGridElement().getHeaderCells(row)) { + if (cell.isDisplayed()) { + size++; + } + } + assertEquals(numberOfCells, size); + } + + private void verifyHeaderCellContent(int row, int column, String content) { + GridCellElement headerCell = getGridElement() + .getHeaderCell(row, column); + assertEquals(content.toLowerCase(), headerCell.getText().toLowerCase()); + assertTrue(headerCell.isDisplayed()); + } + private void verifyColumnIsFrozen(int index) { assertTrue(getGridElement().getHeaderCell(0, index).isFrozen()); } @@ -714,4 +967,25 @@ public class GridColumnHidingTest extends GridBasicClientFeaturesTest { selectMenuPath("Component", "Columns", "Column " + columnIndex, "Hidden"); } + + private void appendHeaderRow() { + selectMenuPath("Component", "Header", "Append row"); + } + + private void mergeHeaderCellsZeroOne(int row) { + selectMenuPath("Component", "Header", "Row " + row, CAPTION_0_1); + } + + private void mergeHeaderCellsOneTwo(int row) { + selectMenuPath("Component", "Header", "Row " + row, CAPTION_1_2); + } + + private void mergeHeaderCellsTwoThreeFour(int row) { + selectMenuPath("Component", "Header", "Row " + row, CAPTION_3_4_5); + } + + private void mergeHeaderCellsAll(int row) { + selectMenuPath("Component", "Header", "Row " + row, CAPTION_ALL); + } + } -- cgit v1.2.3 From 0ca0240a30a9701cdf9fb071594a9e24ee810ad9 Mon Sep 17 00:00:00 2001 From: Jouni Koivuviita Date: Fri, 27 Mar 2015 15:09:41 +0200 Subject: Adds theme to details in Grid (#16644) Change-Id: I84628ee5840b71f2ff889037a525d43f9e86af46 --- WebContent/VAADIN/themes/base/grid/grid.scss | 48 ++++- .../VAADIN/themes/valo/components/_grid.scss | 13 +- .../src/com/vaadin/client/widgets/Escalator.java | 1 - client/src/com/vaadin/client/widgets/Grid.java | 205 +++++++++++++++++---- .../grid/basicfeatures/GridBasicFeatures.java | 3 + .../client/GridDetailsClientTest.java | 18 +- .../basicfeatures/element/CustomGridElement.java | 10 +- 7 files changed, 250 insertions(+), 48 deletions(-) (limited to 'client/src') diff --git a/WebContent/VAADIN/themes/base/grid/grid.scss b/WebContent/VAADIN/themes/base/grid/grid.scss index ccb7043c50..7dc877dca5 100644 --- a/WebContent/VAADIN/themes/base/grid/grid.scss +++ b/WebContent/VAADIN/themes/base/grid/grid.scss @@ -24,6 +24,12 @@ $v-grid-cell-padding-horizontal: 5px !default; $v-grid-editor-background-color: $v-grid-row-background-color !default; +$v-grid-details-marker-width: 2px !default; +$v-grid-details-marker-color: $v-grid-row-selected-background-color !default; +$v-grid-details-border-top: $v-grid-cell-horizontal-border !default; +$v-grid-details-border-top-stripe: $v-grid-cell-horizontal-border !default; +$v-grid-details-border-bottom: 1px solid darken($v-grid-row-stripe-background-color, 10%) !default; +$v-grid-details-border-bottom-stripe: 1px solid darken($v-grid-row-background-color, 10%) !default; @import "../escalator/escalator"; @@ -392,9 +398,45 @@ $v-grid-editor-background-color: $v-grid-row-background-color !default; .#{$primaryStyleName}-editor-save { margin-right: 4px; } - - .#{$primaryStyleName}-spacer { - border: $v-grid-border; + + .#{$primaryStyleName}-spacer > td { + display: block; + padding: 0; + + background-color: $v-grid-row-background-color; + border-top: $v-grid-details-border-top; + border-bottom: $v-grid-details-border-bottom; + } + + .#{$primaryStyleName}-spacer.stripe > td { + background-color: $v-grid-row-stripe-background-color; + border-top: $v-grid-details-border-top-stripe; + border-bottom: $v-grid-details-border-bottom-stripe; + } + + .#{$primaryStyleName}-spacer .deco { + top: 0; // this will be overridden by code, but it's a good default. + left: 0; + width: $v-grid-details-marker-width; + background-color: $v-grid-details-marker-color; + position: absolute; + height: 100%; // this will be overridden by code, but it's a good default. + pointer-events: none; + + // IE 8-10 apply "pointer-events" only to SVG elements. + // Using an empty SVG instead of an empty text node makes IE + // obey the "pointer-events: none" and forwards click events + // to the underlying element. The data decodes to: + // + .ie8 &:before, + .ie9 &:before, + .ie10 &:before { + content: url(data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjwvc3ZnPg==); + } + } + + .#{$primaryStyleName}-spacer .content { + padding-left: $v-grid-details-marker-width; } // Renderers diff --git a/WebContent/VAADIN/themes/valo/components/_grid.scss b/WebContent/VAADIN/themes/valo/components/_grid.scss index 0d6d2ff0a6..0adfdd27ab 100644 --- a/WebContent/VAADIN/themes/valo/components/_grid.scss +++ b/WebContent/VAADIN/themes/valo/components/_grid.scss @@ -3,7 +3,8 @@ $v-grid-row-background-color: valo-table-background-color() !default; $v-grid-row-stripe-background-color: scale-color($v-grid-row-background-color, $lightness: if(color-luminance($v-grid-row-background-color) < 10, 4%, -4%)) !default; -$v-grid-border: flatten-list(valo-border($color: $v-grid-row-background-color, $strength: 0.8)) !default; +$v-grid-border-color-source: $v-grid-row-background-color !default; +$v-grid-border: flatten-list(valo-border($color: $v-grid-border-color-source, $strength: 0.8)) !default; $v-grid-cell-focused-border: max(2px, first-number($v-border)) solid $v-selection-color !default; $v-grid-row-height: $v-table-row-height !default; @@ -16,6 +17,12 @@ $v-grid-cell-padding-horizontal: $v-table-cell-padding-horizontal !default; $v-grid-animations-enabled: $v-animations-enabled !default; +$v-grid-details-marker-width: first-number($v-grid-border) * 2 !default; +$v-grid-details-marker-color: $v-selection-color !default; +$v-grid-details-border-top: valo-border($color: $v-grid-border-color-source, $strength: 0.3) !default; +$v-grid-details-border-top-stripe: valo-border($color: $v-grid-row-stripe-background-color, $strength: 0.3) !default; +$v-grid-details-border-bottom: $v-grid-cell-horizontal-border !default; +$v-grid-details-border-bottom-stripe: $v-grid-cell-horizontal-border !default; @import "../../base/grid/grid"; @@ -177,6 +184,10 @@ $v-grid-animations-enabled: $v-animations-enabled !default; outline: none; } + .#{$primary-stylename}-spacer { + margin-top: first-number($v-grid-border) * -1; + } + // Customize scrollbars .#{$primary-stylename}-scroller { &::-webkit-scrollbar { diff --git a/client/src/com/vaadin/client/widgets/Escalator.java b/client/src/com/vaadin/client/widgets/Escalator.java index 88ed9295e4..0ce5aff74e 100644 --- a/client/src/com/vaadin/client/widgets/Escalator.java +++ b/client/src/com/vaadin/client/widgets/Escalator.java @@ -4597,7 +4597,6 @@ public class Escalator extends Widget implements RequiresResize, getRootElement().getStyle().setWidth(getInnerWidth(), Unit.PX); setHeight(height); - spacerElement.getStyle().setWidth(100, Unit.PCT); spacerElement.setColSpan(getColumnConfiguration() .getColumnCount()); diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index 73dcccfd1e..ed7c0d1800 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -2843,15 +2843,21 @@ public class Grid extends ResizeComposite implements private class GridSpacerUpdater implements SpacerUpdater { + private static final String DECO_CLASSNAME = "deco"; + private static final String CONTENT_CLASSNAME = "content"; + private static final String STRIPE_CLASSNAME = "stripe"; + private final Map elementToWidgetMap = new HashMap(); @Override public void init(Spacer spacer) { + initStructure(spacer); + Element root = getDetailsRoot(spacer); - assert spacer.getElement().getFirstChild() == null : "The spacer's" + assert root.getFirstChild() == null : "The spacer's" + " element should be empty at this point. (row: " - + spacer.getRow() + ", child: " - + spacer.getElement().getFirstChild() + ")"; + + spacer.getRow() + ", child: " + root.getFirstChild() + + ")"; int rowIndex = spacer.getRow(); @@ -2865,43 +2871,45 @@ public class Grid extends ResizeComposite implements + rowIndex, e); } + final double spacerHeight; if (detailsWidget == null) { - spacer.getElement().removeAllChildren(); - escalator.getBody().setSpacer(rowIndex, - DETAILS_ROW_INITIAL_HEIGHT); - return; - } + root.removeAllChildren(); + spacerHeight = DETAILS_ROW_INITIAL_HEIGHT; + } else { + Element element = detailsWidget.getElement(); + root.appendChild(element); + setParent(detailsWidget, Grid.this); + Widget previousWidget = elementToWidgetMap.put(element, + detailsWidget); - Element element = detailsWidget.getElement(); - spacer.getElement().appendChild(element); - setParent(detailsWidget, Grid.this); - Widget previousWidget = elementToWidgetMap.put(element, - detailsWidget); + assert previousWidget == null : "Overwrote a pre-existing widget on row " + + rowIndex + " without proper removal first."; - assert previousWidget == null : "Overwrote a pre-existing widget on row " - + rowIndex + " without proper removal first."; + /* + * Once we have the content properly inside the DOM, we should + * re-measure it to make sure that it's the correct height. + */ + double measuredHeight = WidgetUtil + .getRequiredHeightBoundingClientRectDouble(root); + assert getElement().isOrHasChild(root) : "The spacer element wasn't in the DOM during measurement, but was assumed to be."; + spacerHeight = measuredHeight; + } - /* - * Once we have the content properly inside the DOM, we should - * re-measure it to make sure that it's the correct height. - */ - double measuredHeight = WidgetUtil - .getRequiredHeightBoundingClientRectDouble(spacer - .getElement()); - assert getElement().isOrHasChild(spacer.getElement()) : "The spacer element wasn't in the DOM during measurement, but was assumed to be."; - escalator.getBody().setSpacer(rowIndex, measuredHeight); + escalator.getBody().setSpacer(rowIndex, spacerHeight); + updateDecoratorGeometry(spacerHeight, spacer); } @Override public void destroy(Spacer spacer) { + Element root = getDetailsRoot(spacer); - assert getElement().isOrHasChild(spacer.getElement()) : "Trying " + assert getElement().isOrHasChild(root) : "Trying " + "to destroy a spacer that is not connected to this " + "Grid's DOM. (row: " + spacer.getRow() + ", element: " - + spacer.getElement() + ")"; + + root + ")"; - Widget detailsWidget = elementToWidgetMap.remove(spacer - .getElement().getFirstChildElement()); + Widget detailsWidget = elementToWidgetMap.remove(root + .getFirstChildElement()); if (detailsWidget != null) { /* @@ -2909,16 +2917,107 @@ public class Grid extends ResizeComposite implements * returned a null widget. */ - assert spacer.getElement().getFirstChild() != null : "The " + assert root.getFirstChild() != null : "The " + "details row to destroy did not contain a widget - " + "probably removed by something else without " + "permission? (row: " + spacer.getRow() - + ", element: " + spacer.getElement() + ")"; + + ", element: " + root + ")"; setParent(detailsWidget, null); - spacer.getElement().removeAllChildren(); + root.removeAllChildren(); + } + } + + /** + * Initializes the spacer element into a details structure, containing a + * decorator and a slot for the details widget. + */ + private void initStructure(Spacer spacer) { + Element spacerRoot = spacer.getElement(); + + if (spacerRoot.getChildCount() == 0) { + Element deco = DOM.createDiv(); + deco.setClassName(DECO_CLASSNAME); + + Element detailsContent = DOM.createDiv(); + detailsContent.setClassName(CONTENT_CLASSNAME); + + if (spacer.getRow() % 2 == 1) { + spacerRoot.getParentElement() + .addClassName(STRIPE_CLASSNAME); + } + + spacerRoot.appendChild(deco); + spacerRoot.appendChild(detailsContent); + } + + else { + if (spacer.getRow() % 2 == 1) { + spacerRoot.getParentElement() + .addClassName(STRIPE_CLASSNAME); + } else { + spacerRoot.getParentElement().removeClassName( + STRIPE_CLASSNAME); + } + + /* + * The only case when we get into this else branch is when the + * previous generated details element was a null Widget. In + * those situations, we don't call destroy on the content, but + * simply reuse it as-is. + */ + assert getDetailsRoot(spacer).getChildCount() == 0 : "This " + + "code should never be triggered unless the details " + + "root already was empty"; } } + + /** Gets the decorator element from the DOM structure. */ + private Element getDecorator(Spacer spacer) { + TableCellElement td = TableCellElement.as(spacer.getElement()); + Element decorator = td.getFirstChildElement(); + return decorator; + } + + /** Gets the element for the details widget from the DOM structure. */ + private Element getDetailsRoot(Spacer spacer) { + Element detailsRoot = getDecorator(spacer).getNextSiblingElement(); + return detailsRoot; + } + + /** Resizes and places the decorator. */ + private void updateDecoratorGeometry(double detailsHeight, Spacer spacer) { + Element decorator = getDecorator(spacer); + Style style = decorator.getStyle(); + double rowHeight = escalator.getBody().getDefaultRowHeight(); + double borderHeight = getBorderHeight(spacer); + + style.setTop(-(rowHeight - borderHeight), Unit.PX); + style.setHeight(detailsHeight + rowHeight, Unit.PX); + } + + private native double getBorderHeight(Spacer spacer) + /*-{ + var spacerCell = spacer.@com.vaadin.client.widget.escalator.Spacer::getElement()(); + if (typeof $wnd.getComputedStyle === 'function') { + var computedStyle = $wnd.getComputedStyle(spacerCell); + var borderTopWidth = computedStyle['borderTopWidth']; + var width = parseFloat(borderTopWidth); + return width; + } else { + var spacerRow = spacerCell.offsetParent; + var cloneCell = spacerCell.cloneNode(false); + spacerRow.appendChild(cloneCell); + cloneCell.style.height = "10px"; // IE8 wants the height to be set to something... + var heightWithBorder = cloneCell.offsetHeight; + cloneCell.style.borderTopWidth = "0"; + var heightWithoutBorder = cloneCell.offsetHeight; + spacerRow.removeChild(cloneCell); + + console.log(heightWithBorder+" - "+heightWithoutBorder); + return heightWithBorder - heightWithoutBorder; + } + }-*/; } /** @@ -4829,6 +4928,8 @@ public class Grid extends ResizeComposite implements setSelectionMode(SelectionMode.SINGLE); + escalator.getBody().setSpacerUpdater(gridSpacerUpdater); + escalator.addScrollHandler(new ScrollHandler() { @Override public void onScroll(ScrollEvent event) { @@ -6053,10 +6154,12 @@ public class Grid extends ResizeComposite implements private boolean isOrContainsInSpacer(Node node) { Node n = node; while (n != null && n != getElement()) { - if (Element.is(n) - && Element.as(n).getClassName() - .equals(getStylePrimaryName() + "-spacer")) { - return true; + boolean isElement = Element.is(n); + if (isElement) { + String className = Element.as(n).getClassName(); + if (className.contains(getStylePrimaryName() + "-spacer")) { + return true; + } } n = n.getParentNode(); } @@ -6318,10 +6421,19 @@ public class Grid extends ResizeComposite implements @SuppressWarnings("deprecation") public com.google.gwt.user.client.Element getSubPartElement(String subPart) { - Element subPartElement = escalator.getSubPartElement(subPart + /* + * gandles details[] (translated to spacer[] for Escalator), cell[], + * header[] and footer[] + */ + Element escalatorElement = escalator.getSubPartElement(subPart .replaceFirst("^details\\[", "spacer[")); - if (subPartElement != null) { - return DOM.asOld(subPartElement); + + if (escalatorElement != null) { + if (subPart.startsWith("details[")) { + return DOM.asOld(parseDetails(escalatorElement)); + } else { + return DOM.asOld(escalatorElement); + } } SubPartArguments args = Escalator.parseSubPartArguments(subPart); @@ -6334,6 +6446,23 @@ public class Grid extends ResizeComposite implements return null; } + @SuppressWarnings("static-method") + private Element parseDetails(Element spacer) { + assert spacer.getChildCount() == 2 : "Unexpected structure for details "; + + Element decorator = spacer.getFirstChildElement(); + assert decorator != null : "unexpected spacer DOM structure"; + assert decorator.getClassName() + .equals(GridSpacerUpdater.DECO_CLASSNAME) : "unexpected first details element"; + + Element spacerRoot = decorator.getNextSiblingElement(); + assert spacerRoot != null : "unexpected spacer DOM structure"; + assert spacerRoot.getClassName().equals( + GridSpacerUpdater.CONTENT_CLASSNAME) : "unexpected second details element"; + + return DOM.asOld(spacerRoot); + } + private Element getSubPartElementEditor(SubPartArguments args) { if (!args.getType().equalsIgnoreCase("editor") diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java index 6f4c7df38c..94620f34bd 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java @@ -1301,6 +1301,9 @@ public class GridBasicFeatures extends AbstractComponentTest { createBooleanAction("Open firstItemId", "Details", false, openOrCloseItemId, ds.firstItemId()); + createBooleanAction("Open 1", "Details", false, openOrCloseItemId, + ds.getIdByIndex(1)); + createBooleanAction("Open 995", "Details", false, openOrCloseItemId, ds.getIdByIndex(995)); } diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridDetailsClientTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridDetailsClientTest.java index 5ab0c238e5..619033226c 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridDetailsClientTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridDetailsClientTest.java @@ -23,6 +23,8 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.util.List; + import org.junit.Before; import org.junit.Test; import org.openqa.selenium.NoSuchElementException; @@ -33,12 +35,9 @@ import com.vaadin.shared.ui.grid.ScrollDestination; import com.vaadin.testbench.By; import com.vaadin.testbench.ElementQuery; import com.vaadin.testbench.TestBenchElement; -import com.vaadin.testbench.annotations.RunLocally; import com.vaadin.testbench.elements.NotificationElement; -import com.vaadin.testbench.parallel.Browser; import com.vaadin.tests.components.grid.basicfeatures.GridBasicClientFeaturesTest; -@RunLocally(Browser.PHANTOMJS) public class GridDetailsClientTest extends GridBasicClientFeaturesTest { private static final String[] SET_GENERATOR = new String[] { "Component", @@ -183,6 +182,19 @@ public class GridDetailsClientTest extends GridBasicClientFeaturesTest { getGridElement().getDetails(1); } + + @Test + public void rowElementClassNames() { + toggleDetailsFor(0); + toggleDetailsFor(1); + + List elements = getGridElement().findElements( + By.className("v-grid-spacer")); + assertEquals("v-grid-spacer", elements.get(0).getAttribute("class")); + assertEquals("v-grid-spacer stripe", + elements.get(1).getAttribute("class")); + } + @Test public void scrollDownToRowWithDetails() { toggleDetailsFor(100); diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/element/CustomGridElement.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/element/CustomGridElement.java index dc9b8722f2..e1934d4f2b 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/element/CustomGridElement.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/element/CustomGridElement.java @@ -15,6 +15,8 @@ */ package com.vaadin.tests.components.grid.basicfeatures.element; +import org.openqa.selenium.NoSuchElementException; + import com.vaadin.testbench.By; import com.vaadin.testbench.TestBenchElement; import com.vaadin.testbench.elements.GridElement; @@ -28,9 +30,13 @@ public class CustomGridElement extends GridElement { * @since * @param rowIndex * the index of the row for the details - * @return the element that contains the details of a row + * @return the element that contains the details of a row. null + * if no widget is defined for the detials row + * @throws NoSuchElementException + * if the given details row is currently not open */ - public TestBenchElement getDetails(int rowIndex) { + public TestBenchElement getDetails(int rowIndex) + throws NoSuchElementException { return getSubPart("#details[" + rowIndex + "]"); } -- cgit v1.2.3 From c5118632a6772ac0791a5ca1a0471f140027cf64 Mon Sep 17 00:00:00 2001 From: Henrik Paul Date: Thu, 2 Apr 2015 16:03:54 +0300 Subject: No unnecessary detail-request RPCs (#17367) Change-Id: Ia5f00765d623a5fadd254f2146fbd9e5c9e1b86a --- client/src/com/vaadin/client/connectors/GridConnector.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'client/src') diff --git a/client/src/com/vaadin/client/connectors/GridConnector.java b/client/src/com/vaadin/client/connectors/GridConnector.java index 5554664566..d31baaa665 100644 --- a/client/src/com/vaadin/client/connectors/GridConnector.java +++ b/client/src/com/vaadin/client/connectors/GridConnector.java @@ -178,7 +178,7 @@ public class GridConnector extends AbstractHasComponentsConnector implements /** * Sets a new renderer for this column object - * + * * @param rendererConnector * a renderer connector object */ @@ -736,14 +736,17 @@ public class GridConnector extends AbstractHasComponentsConnector implements private final DetailsListener detailsListener = new DetailsListener() { @Override public void reapplyDetailsVisibility(int rowIndex, JsonObject row) { - if (row.hasKey(GridState.JSONKEY_DETAILS_VISIBLE) - && row.getBoolean(GridState.JSONKEY_DETAILS_VISIBLE)) { + if (hasDetailsOpen(row)) { getWidget().setDetailsVisible(rowIndex, true); + detailsConnectorFetcher.schedule(); } else { getWidget().setDetailsVisible(rowIndex, false); } + } - detailsConnectorFetcher.schedule(); + private boolean hasDetailsOpen(JsonObject row) { + return row.hasKey(GridState.JSONKEY_DETAILS_VISIBLE) + && row.getBoolean(GridState.JSONKEY_DETAILS_VISIBLE); } @Override -- cgit v1.2.3 From a1aa700db37f727bb6a3690f1704d3ec512ee468 Mon Sep 17 00:00:00 2001 From: Henrik Paul Date: Tue, 31 Mar 2015 16:25:47 +0300 Subject: Escalator now reorders also spacers in DOM (#17334) Change-Id: I23832565bb1633dca4bac986b1a2e37f99163eb2 --- .../src/com/vaadin/client/widgets/Escalator.java | 53 ++++++++++++++++---- .../EscalatorBasicClientFeaturesTest.java | 1 + .../escalator/EscalatorSpacerTest.java | 56 ++++++++++++++++++++++ .../grid/EscalatorBasicClientFeaturesWidget.java | 20 +++++++- 4 files changed, 121 insertions(+), 9 deletions(-) (limited to 'client/src') diff --git a/client/src/com/vaadin/client/widgets/Escalator.java b/client/src/com/vaadin/client/widgets/Escalator.java index 0ce5aff74e..fe8ba4f67e 100644 --- a/client/src/com/vaadin/client/widgets/Escalator.java +++ b/client/src/com/vaadin/client/widgets/Escalator.java @@ -3692,11 +3692,12 @@ public class Escalator extends Widget implements RequiresResize, * its parents are) removed from the document. Therefore, we sort * everything around that row instead. */ - final TableRowElement focusedRow = getEscalatorRowWithFocus(); + final TableRowElement focusedRow = getRowWithFocus(); if (focusedRow != null) { assert focusedRow.getParentElement() == root : "Trying to sort around a row that doesn't exist in body"; - assert visualRowOrder.contains(focusedRow) : "Trying to sort around a row that doesn't exist in visualRowOrder."; + assert visualRowOrder.contains(focusedRow) + || body.spacerContainer.isSpacer(focusedRow) : "Trying to sort around a row that doesn't exist in visualRowOrder or is not a spacer."; } /* @@ -3717,6 +3718,21 @@ public class Escalator extends Widget implements RequiresResize, * the first child. */ + List orderedBodyRows = new ArrayList( + visualRowOrder); + for (int i = 0; i < visualRowOrder.size(); i++) { + SpacerContainer.SpacerImpl spacer = body.spacerContainer + .getSpacer(getTopRowLogicalIndex() + i); + + if (spacer != null) { + orderedBodyRows.add(i + 1, spacer.getRootElement()); + } + } + /* + * At this point, invisible spacers aren't reordered, so their + * position in the DOM is undefined. + */ + /* * If we have a focused row, start in the mode where we put * everything underneath that row. Otherwise, all rows are placed as @@ -3724,8 +3740,8 @@ public class Escalator extends Widget implements RequiresResize, */ boolean insertFirst = (focusedRow == null); - final ListIterator i = visualRowOrder - .listIterator(visualRowOrder.size()); + final ListIterator i = orderedBodyRows + .listIterator(orderedBodyRows.size()); while (i.hasPrevious()) { TableRowElement tr = i.previous(); @@ -3742,12 +3758,13 @@ public class Escalator extends Widget implements RequiresResize, } /** - * Get the escalator row that has focus. + * Get the {@literal } row that contains (or has) focus. * - * @return The escalator row that contains a focused DOM element, or - * null if focus is outside of a body row. + * @return The {@literal } row that contains a focused DOM + * element, or null if focus is outside of a body + * row. */ - private TableRowElement getEscalatorRowWithFocus() { + private TableRowElement getRowWithFocus() { TableRowElement rowContainingFocus = null; final Element focusedElement = WidgetUtil.getFocusedElement(); @@ -4781,6 +4798,24 @@ public class Escalator extends Widget implements RequiresResize, } } + /** Checks if a given element is a spacer element */ + public boolean isSpacer(TableRowElement focusedRow) { + + /* + * If this needs optimization, we could do a more heuristic check + * based on stylenames and stuff, instead of iterating through the + * map. + */ + + for (SpacerImpl spacer : rowIndexToSpacer.values()) { + if (spacer.getRootElement().equals(focusedRow)) { + return true; + } + } + + return false; + } + @SuppressWarnings("boxing") void scrollToSpacer(int spacerIndex, ScrollDestination destination, int padding) { @@ -5140,6 +5175,8 @@ public class Escalator extends Widget implements RequiresResize, spacer.setupDom(height); initSpacerContent(spacer); + + body.sortDomElements(); } private void updateExistingSpacer(int rowIndex, double newHeight) { 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 9037b29bb1..e1a3fc0f55 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/EscalatorBasicClientFeaturesTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/EscalatorBasicClientFeaturesTest.java @@ -77,6 +77,7 @@ public abstract class EscalatorBasicClientFeaturesTest extends MultiBrowserTest protected static final String COLSPAN_NONE = "Apply no colspan"; protected static final String SET_100PX = "Set 100px"; protected static final String SPACERS = "Spacers"; + protected static final String FOCUSABLE_UPDATER = "Focusable Updater"; protected static final String SCROLL_HERE_ANY_0PADDING = "Scroll here (ANY, 0)"; protected static final String SCROLL_HERE_SPACERBELOW_ANY_0PADDING = "Scroll here row+spacer below (ANY, 0)"; protected static final String REMOVE = "Remove"; 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 index ce8fcd233e..7905906536 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/escalator/EscalatorSpacerTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/escalator/EscalatorSpacerTest.java @@ -27,11 +27,13 @@ import java.util.regex.Pattern; import org.junit.Before; import org.junit.ComparisonFailure; import org.junit.Test; +import org.openqa.selenium.By; import org.openqa.selenium.WebElement; import com.vaadin.client.WidgetUtil; import com.vaadin.shared.ui.grid.Range; import com.vaadin.testbench.elements.NotificationElement; +import com.vaadin.testbench.parallel.BrowserUtil; import com.vaadin.tests.components.grid.basicfeatures.EscalatorBasicClientFeaturesTest; @SuppressWarnings("boxing") @@ -96,6 +98,7 @@ public class EscalatorSpacerTest extends EscalatorBasicClientFeaturesTest { @Before public void before() { + setDebug(true); openTestURL(); selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, "Set 20px default height"); populate(); @@ -372,6 +375,59 @@ public class EscalatorSpacerTest extends EscalatorBasicClientFeaturesTest { assertEquals(getScrollTop(), 950); } + @Test + public void domCanBeSortedWithFocusInSpacer() throws InterruptedException { + + // Firefox behaves badly with focus-related tests - skip it. + if (BrowserUtil.isFirefox(super.getDesiredCapabilities())) { + return; + } + + selectMenuPath(FEATURES, SPACERS, FOCUSABLE_UPDATER); + selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX); + + WebElement inputElement = getEscalator().findElement( + By.tagName("input")); + inputElement.click(); + scrollVerticallyTo(30); + + // Sleep needed because of all the JS we're doing, and to let + // the DOM reordering to take place. + Thread.sleep(500); + + assertFalse("Error message detected", $(NotificationElement.class) + .exists()); + } + + @Test + public void spacersAreInsertedInCorrectDomPosition() { + selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX); + + WebElement tbody = getEscalator().findElement(By.tagName("tbody")); + WebElement spacer = getChild(tbody, 2); + String cssClass = spacer.getAttribute("class"); + assertTrue("element index 2 was not a spacer (class=\"" + cssClass + + "\")", cssClass.contains("-spacer")); + } + + @Test + public void spacersAreInCorrectDomPositionAfterScroll() { + selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX); + + scrollVerticallyTo(30); // roughly one row's worth + + WebElement tbody = getEscalator().findElement(By.tagName("tbody")); + WebElement spacer = getChild(tbody, 1); + String cssClass = spacer.getAttribute("class"); + assertTrue("element index 1 was not a spacer (class=\"" + cssClass + + "\")", cssClass.contains("-spacer")); + } + + private WebElement getChild(WebElement parent, int childIndex) { + return (WebElement) executeScript("return arguments[0].children[" + + childIndex + "];", parent); + } + private static double[] getElementDimensions(WebElement element) { /* * we need to parse the style attribute, since using getCssValue gets a 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 ec9f214748..c735797731 100644 --- a/uitest/src/com/vaadin/tests/widgetset/client/grid/EscalatorBasicClientFeaturesWidget.java +++ b/uitest/src/com/vaadin/tests/widgetset/client/grid/EscalatorBasicClientFeaturesWidget.java @@ -6,6 +6,7 @@ import java.util.List; import com.google.gwt.core.client.Duration; import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.dom.client.TableCellElement; +import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.HTML; import com.vaadin.client.widget.escalator.EscalatorUpdater; @@ -658,7 +659,7 @@ public class EscalatorBasicClientFeaturesWidget extends public void execute() { BodyRowContainer body = escalator.getBody(); - if (body.getSpacerUpdater().equals(SpacerUpdater.NULL)) { + if (SpacerUpdater.NULL.equals(body.getSpacerUpdater())) { body.setSpacerUpdater(CUSTOM); } else { body.setSpacerUpdater(SpacerUpdater.NULL); @@ -666,6 +667,23 @@ public class EscalatorBasicClientFeaturesWidget extends } }, menupath); + addMenuCommand("Focusable Updater", new ScheduledCommand() { + @Override + public void execute() { + escalator.getBody().setSpacerUpdater(new SpacerUpdater() { + @Override + public void init(Spacer spacer) { + spacer.getElement().appendChild(DOM.createInputText()); + } + + @Override + public void destroy(Spacer spacer) { + spacer.getElement().removeAllChildren(); + } + }); + } + }, menupath); + createSpacersMenuForRow(-1, menupath); createSpacersMenuForRow(1, menupath); createSpacersMenuForRow(50, menupath); -- cgit v1.2.3 From 35d4a8fd0ec558b3a2a20bc6ffd0f1048ffc4c4d Mon Sep 17 00:00:00 2001 From: Henrik Paul Date: Thu, 2 Apr 2015 11:14:04 +0300 Subject: Escalator tries to hide out-of-sight spacers from the user (#17353) Change-Id: I807896e3cdd07f4a43a265ba720435f01778f7e1 --- .../src/com/vaadin/client/widgets/Escalator.java | 54 +++++++++++++++-- .../EscalatorBasicClientFeaturesTest.java | 1 + .../escalator/EscalatorSpacerTest.java | 69 ++++++++++++++++++++++ 3 files changed, 120 insertions(+), 4 deletions(-) (limited to 'client/src') diff --git a/client/src/com/vaadin/client/widgets/Escalator.java b/client/src/com/vaadin/client/widgets/Escalator.java index fe8ba4f67e..01567143dd 100644 --- a/client/src/com/vaadin/client/widgets/Escalator.java +++ b/client/src/com/vaadin/client/widgets/Escalator.java @@ -3720,19 +3720,32 @@ public class Escalator extends Widget implements RequiresResize, List orderedBodyRows = new ArrayList( visualRowOrder); - for (int i = 0; i < visualRowOrder.size(); i++) { - SpacerContainer.SpacerImpl spacer = body.spacerContainer - .getSpacer(getTopRowLogicalIndex() + i); + Map spacers = body.spacerContainer + .getSpacers(); + + /* + * Start at -1 to include a spacer that is rendered above the + * viewport, but its parent row is still not shown + */ + for (int i = -1; i < visualRowOrder.size(); i++) { + SpacerContainer.SpacerImpl spacer = spacers.remove(Integer + .valueOf(getTopRowLogicalIndex() + i)); if (spacer != null) { orderedBodyRows.add(i + 1, spacer.getRootElement()); + spacer.show(); } } /* * At this point, invisible spacers aren't reordered, so their - * position in the DOM is undefined. + * position in the DOM will remain undefined. */ + // If a spacer was not reordered, it means that it's out of view. + for (SpacerContainer.SpacerImpl unmovedSpacer : spacers.values()) { + unmovedSpacer.hide(); + } + /* * If we have a focused row, start in the mode where we put * everything underneath that row. Otherwise, all rows are placed as @@ -4755,6 +4768,33 @@ public class Escalator extends Widget implements RequiresResize, root.setPropertyInt(SPACER_LOGICAL_ROW_PROPERTY, rowIndex); rowIndexToSpacer.put(this.rowIndex, this); } + + /** + * Updates the spacer's visibility parameters, based on whether it + * is being currently visible or not. + */ + public void updateVisibility() { + if (isInViewport()) { + show(); + } else { + hide(); + } + } + + private boolean isInViewport() { + int top = (int) Math.ceil(getTop()); + int height = (int) Math.floor(getHeight()); + Range location = Range.withLength(top, height); + return getViewportPixels().intersects(location); + } + + public void show() { + getRootElement().getStyle().clearDisplay(); + } + + public void hide() { + getRootElement().getStyle().setDisplay(Display.NONE); + } } private final TreeMap rowIndexToSpacer = new TreeMap(); @@ -4887,6 +4927,10 @@ public class Escalator extends Widget implements RequiresResize, } } + public Map getSpacers() { + return new HashMap(rowIndexToSpacer); + } + /** * Calculates the sum of all spacers. * @@ -5239,6 +5283,8 @@ public class Escalator extends Widget implements RequiresResize, spacerUpdater.init(spacer); assert getElement().isOrHasChild(spacer.getRootElement()) : "Spacer's root element somehow got detached from Escalator during attaching"; assert getElement().isOrHasChild(spacer.getElement()) : "Spacer element somehow got detached from Escalator during attaching"; + + spacer.updateVisibility(); } public String getSubPartName(Element subElement) { 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 e1a3fc0f55..862e959ebc 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/EscalatorBasicClientFeaturesTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/EscalatorBasicClientFeaturesTest.java @@ -83,6 +83,7 @@ public abstract class EscalatorBasicClientFeaturesTest extends MultiBrowserTest protected static final String REMOVE = "Remove"; protected static final String ROW_MINUS1 = "Row -1"; + protected static final String ROW_0 = "Row 0"; protected static final String ROW_1 = "Row 1"; protected static final String ROW_25 = "Row 25"; protected static final String ROW_50 = "Row 50"; 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 index 7905906536..71cc19ecdd 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/escalator/EscalatorSpacerTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/escalator/EscalatorSpacerTest.java @@ -17,6 +17,7 @@ package com.vaadin.tests.components.grid.basicfeatures.escalator; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -28,6 +29,7 @@ import org.junit.Before; import org.junit.ComparisonFailure; import org.junit.Test; import org.openqa.selenium.By; +import org.openqa.selenium.Keys; import org.openqa.selenium.WebElement; import com.vaadin.client.WidgetUtil; @@ -423,6 +425,73 @@ public class EscalatorSpacerTest extends EscalatorBasicClientFeaturesTest { + "\")", cssClass.contains("-spacer")); } + @Test + public void spacerScrolledIntoViewGetsFocus() { + selectMenuPath(FEATURES, SPACERS, FOCUSABLE_UPDATER); + selectMenuPath(FEATURES, SPACERS, ROW_50, SET_100PX); + selectMenuPath(FEATURES, SPACERS, ROW_50, SCROLL_HERE_ANY_0PADDING); + + tryToTabIntoFocusUpdaterElement(); + assertEquals("input", getFocusedElement().getTagName()); + } + + @Test + public void spacerScrolledOutOfViewDoesNotGetFocus() { + selectMenuPath(FEATURES, SPACERS, FOCUSABLE_UPDATER); + selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX); + selectMenuPath(FEATURES, SPACERS, ROW_50, SCROLL_HERE_ANY_0PADDING); + + tryToTabIntoFocusUpdaterElement(); + assertNotEquals("input", getFocusedElement().getTagName()); + } + + @Test + public void spacerOpenedInViewGetsFocus() { + selectMenuPath(FEATURES, SPACERS, FOCUSABLE_UPDATER); + selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX); + tryToTabIntoFocusUpdaterElement(); + assertEquals("input", getFocusedElement().getTagName()); + } + + @Test + public void spacerOpenedOutOfViewDoesNotGetFocus() { + selectMenuPath(FEATURES, SPACERS, FOCUSABLE_UPDATER); + selectMenuPath(FEATURES, SPACERS, ROW_50, SET_100PX); + + tryToTabIntoFocusUpdaterElement(); + assertNotEquals("input", getFocusedElement().getTagName()); + } + + @Test + public void spacerOpenedInViewAndScrolledOutAndBackAgainGetsFocus() { + selectMenuPath(FEATURES, SPACERS, FOCUSABLE_UPDATER); + selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX); + selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, SCROLL_TO, ROW_50); + selectMenuPath(FEATURES, SPACERS, ROW_1, SCROLL_HERE_ANY_0PADDING); + + tryToTabIntoFocusUpdaterElement(); + assertEquals("input", getFocusedElement().getTagName()); + } + + @Test + public void spacerOpenedOutOfViewAndScrolledInAndBackAgainDoesNotGetFocus() { + selectMenuPath(FEATURES, SPACERS, FOCUSABLE_UPDATER); + selectMenuPath(FEATURES, SPACERS, ROW_50, SET_100PX); + selectMenuPath(FEATURES, SPACERS, ROW_50, SCROLL_HERE_ANY_0PADDING); + selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, SCROLL_TO, ROW_0); + + tryToTabIntoFocusUpdaterElement(); + assertNotEquals("input", getFocusedElement().getTagName()); + } + + private void tryToTabIntoFocusUpdaterElement() { + getEscalator().sendKeys( // + Keys.TAB, // v-ui v-scrollable + Keys.TAB, // menubar + Keys.TAB // + ); + } + private WebElement getChild(WebElement parent, int childIndex) { return (WebElement) executeScript("return arguments[0].children[" + childIndex + "];", parent); -- cgit v1.2.3 From 59d5a799d63cfafbf02e0d680f99758da1a3338a Mon Sep 17 00:00:00 2001 From: Pekka Hyvönen Date: Wed, 8 Apr 2015 11:46:28 +0300 Subject: Use MenuBar in Grid's sidebar for column hiding #17332 Change-Id: I3b15430c6f8dab6dadcd84ddf280153e91192c5f --- client/src/com/vaadin/client/widgets/Grid.java | 199 ++++++++++++++------- .../grid/basicfeatures/GridBasicFeatures.java | 12 +- 2 files changed, 146 insertions(+), 65 deletions(-) (limited to 'client/src') diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index ed7c0d1800..9efea31d02 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -49,6 +49,8 @@ import com.google.gwt.dom.client.Touch; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.dom.client.KeyCodes; +import com.google.gwt.event.dom.client.KeyDownEvent; +import com.google.gwt.event.dom.client.KeyDownHandler; import com.google.gwt.event.dom.client.KeyEvent; import com.google.gwt.event.dom.client.MouseEvent; import com.google.gwt.event.logical.shared.ValueChangeEvent; @@ -66,8 +68,9 @@ import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.HasEnabled; import com.google.gwt.user.client.ui.HasWidgets; +import com.google.gwt.user.client.ui.MenuBar; +import com.google.gwt.user.client.ui.MenuItem; import com.google.gwt.user.client.ui.ResizeComposite; -import com.google.gwt.user.client.ui.ToggleButton; import com.google.gwt.user.client.ui.Widget; import com.vaadin.client.BrowserInfo; import com.vaadin.client.DeferredWorker; @@ -3049,6 +3052,8 @@ public class Grid extends ResizeComposite implements private final FlowPanel content; + private final MenuBar menuBar; + private final Button openCloseButton; private final Grid grid; @@ -3076,6 +3081,63 @@ public class Grid extends ResizeComposite implements return removed; } }; + + menuBar = new MenuBar(true) { + + @Override + public MenuItem addItem(MenuItem item) { + if (getParent() == null) { + content.insert(this, 0); + updateVisibility(); + } + return super.addItem(item); + } + + @Override + public void removeItem(MenuItem item) { + super.removeItem(item); + if (getItems().isEmpty()) { + menuBar.removeFromParent(); + } + } + + @Override + public void onBrowserEvent(Event event) { + // selecting a item with enter will lose the focus and + // selected item, which means that further keyboard + // selection won't work unless we do this: + if (event.getTypeInt() == Event.ONKEYDOWN + && event.getKeyCode() == KeyCodes.KEY_ENTER) { + final MenuItem item = getSelectedItem(); + super.onBrowserEvent(event); + Scheduler.get().scheduleDeferred( + new ScheduledCommand() { + + @Override + public void execute() { + selectItem(item); + focus(); + } + }); + + } else { + super.onBrowserEvent(event); + } + } + + }; + KeyDownHandler keyDownHandler = new KeyDownHandler() { + + @Override + public void onKeyDown(KeyDownEvent event) { + if (event.getNativeKeyCode() == KeyCodes.KEY_ESCAPE) { + close(); + } + } + }; + openCloseButton.addDomHandler(keyDownHandler, + KeyDownEvent.getType()); + menuBar.addDomHandler(keyDownHandler, KeyDownEvent.getType()); } /** @@ -3085,6 +3147,7 @@ public class Grid extends ResizeComposite implements public void open() { if (!isOpen() && isInDOM()) { addStyleName("opened"); + removeStyleName("closed"); rootContainer.add(content); } } @@ -3095,6 +3158,7 @@ public class Grid extends ResizeComposite implements public void close() { if (isOpen()) { removeStyleName("opened"); + addStyleName("closed"); content.removeFromParent(); } } @@ -3153,6 +3217,13 @@ public class Grid extends ResizeComposite implements super.setStylePrimaryName(styleName); content.setStylePrimaryName(styleName + "-content"); openCloseButton.setStylePrimaryName(styleName + "-button"); + if (isOpen()) { + addStyleName("open"); + removeStyleName("closed"); + } else { + removeStyleName("open"); + addStyleName("closed"); + } } private void updateVisibility() { @@ -3171,98 +3242,97 @@ public class Grid extends ResizeComposite implements private boolean isInDOM() { return getParent() != null; } - } /** * UI and functionality related to hiding columns with toggles in the * sidebar. */ - private final class ColumnHider extends FlowPanel { - - ColumnHider() { - setStyleName("column-hiding-panel"); - } + private final class ColumnHider { /** Map from columns to their hiding toggles, component might change */ - private HashMap, ToggleButton> columnToHidingToggleMap = new HashMap, ToggleButton>(); + private HashMap, MenuItem> columnToHidingToggleMap = new HashMap, MenuItem>(); + + /** + * When column is being hidden with a toggle, do not refresh toggles for + * no reason. Also helps for keeping the keyboard navigation working. + */ + private boolean hidingColumn; private void updateColumnHidable(final Column column) { if (column.isHidable()) { - ToggleButton cb = columnToHidingToggleMap.get(column); - if (cb == null) { - cb = createToggle(column); + MenuItem toggle = columnToHidingToggleMap.get(column); + if (toggle == null) { + toggle = createToggle(column); } - updateToggleValue(cb, column.isHidden()); - } else if (columnToHidingToggleMap.containsValue(column)) { - ((Widget) columnToHidingToggleMap.remove(column)) - .removeFromParent(); + toggle.setStyleName("hidden", column.isHidden()); + } else if (columnToHidingToggleMap.containsKey(column)) { + sidebar.menuBar.removeItem((columnToHidingToggleMap + .remove(column))); } updateTogglesOrder(); - updatePanelVisibility(); } - private ToggleButton createToggle(final Column column) { - ToggleButton toggle = new ToggleButton(); - toggle.addStyleName("column-hiding-toggle"); - toggle.addValueChangeHandler(new ValueChangeHandler() { + private MenuItem createToggle(final Column column) { + MenuItem toggle = new MenuItem(createHTML(column), true, + new ScheduledCommand() { - @Override - public void onValueChange(ValueChangeEvent event) { - column.setHidden(!event.getValue(), true); - } - }); - updateHidingToggleCaption(column, toggle); + @Override + public void execute() { + hidingColumn = true; + column.setHidden(!column.isHidden(), true); + hidingColumn = false; + } + }); + toggle.addStyleName("column-hiding-toggle"); columnToHidingToggleMap.put(column, toggle); return toggle; } - private void updateTogglesOrder() { - clear(); - for (Column c : getColumns()) { - if (c.isHidable()) { - add(columnToHidingToggleMap.get(c)); - } + private String createHTML(Column column) { + final StringBuffer buf = new StringBuffer(); + buf.append("

"); + String caption = column.getHidingToggleCaption(); + if (caption == null) { + caption = column.headerCaption; + } + buf.append(caption); + buf.append("
"); - private void updatePanelVisibility() { - final boolean columnHidable = getWidgetCount() > 0; - final boolean columnTogglesPanelIsVisible = getParent() != null; + return buf.toString(); + } - if (columnHidable && !columnTogglesPanelIsVisible) { - sidebar.insert(this, 0); - } else if (!columnHidable && columnTogglesPanelIsVisible) { - sidebar.remove(this); + private void updateTogglesOrder() { + if (!hidingColumn) { + for (Column column : getColumns()) { + if (column.isHidable()) { + final MenuItem menuItem = columnToHidingToggleMap + .get(column); + sidebar.menuBar.removeItem(menuItem); + sidebar.menuBar.addItem(menuItem); + } + } } } - private void updateToggleValue(Column column) { + private void updateHidingToggle(Column column) { if (column.isHidable()) { - updateToggleValue(columnToHidingToggleMap.get(column), - column.isHidden()); + MenuItem toggle = columnToHidingToggleMap.get(column); + toggle.setHTML(createHTML(column)); + toggle.setStyleName("hidden", column.isHidden()); } // else we can just ignore } - private void updateToggleValue(ToggleButton hasValue, boolean hidden) { - hasValue.setValue(!hidden, false); - hasValue.setStyleName("hidden", hidden); - } - - private void updateHidingToggleCaption(Column column) { - updateHidingToggleCaption(column, - columnToHidingToggleMap.get(column)); + private void removeColumnHidingToggle(Column column) { + sidebar.menuBar.removeItem(columnToHidingToggleMap.get(column)); } - private void updateHidingToggleCaption(Column column, - ToggleButton toggle) { - String caption = column.getHidingToggleCaption(); - if (caption == null) { - caption = column.headerCaption; - // the caption might still be null, but that is the users fault - } - toggle.setText(caption); - } } /** @@ -4007,7 +4077,7 @@ public class Grid extends ResizeComposite implements if (row != null) { row.getCell(this).setText(headerCaption); if (isHidable()) { - grid.columnHider.updateHidingToggleCaption(this); + grid.columnHider.updateHidingToggle(this); } } } @@ -4207,7 +4277,7 @@ public class Grid extends ResizeComposite implements .setFrozenColumnCount(++escalatorFrozenColumns); } } - grid.columnHider.updateToggleValue(this); + grid.columnHider.updateHidingToggle(this); grid.header.updateColSpans(); grid.footer.updateColSpans(); scheduleColumnWidthRecalculator(); @@ -4276,7 +4346,7 @@ public class Grid extends ResizeComposite implements public void setHidingToggleCaption(String hidingToggleCaption) { this.hidingToggleCaption = hidingToggleCaption; if (isHidable()) { - grid.columnHider.updateHidingToggleCaption(this); + grid.columnHider.updateHidingToggle(this); } } @@ -5019,6 +5089,7 @@ public class Grid extends ResizeComposite implements escalator.setStylePrimaryName(style); editor.setStylePrimaryName(style); sidebar.setStylePrimaryName(style + "-sidebar"); + sidebar.addStyleName("v-contextmenu"); String rowStyle = getStylePrimaryName() + "-row"; rowHasDataStyleName = rowStyle + "-has-data"; @@ -5261,7 +5332,7 @@ public class Grid extends ResizeComposite implements columns.remove(columnIndex); if (column.isHidable()) { - columnHider.updateColumnHidable(column); + columnHider.removeColumnHidingToggle(column); } } diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java index 94620f34bd..2abd603ae4 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java @@ -759,7 +759,6 @@ public class GridBasicFeatures extends AbstractComponentTest { @SuppressWarnings("boxing") protected void createColumnActions() { createCategory("Columns", null); - for (int c = 0; c < COLUMNS; c++) { final int index = c; createCategory(getColumnProperty(c), "Columns"); @@ -974,6 +973,17 @@ public class GridBasicFeatures extends AbstractComponentTest { }, c); } + createBooleanAction("All columns hidable", "Columns", false, + new Command() { + + @Override + public void execute(Grid c, Boolean value, Object data) { + for (Column col : grid.getColumns()) { + col.setHidable(value); + } + + } + }); } private static String getColumnProperty(int c) { -- cgit v1.2.3 From 8a6c260a6a59a412227366d02e5cbce4108b2fd5 Mon Sep 17 00:00:00 2001 From: Pekka Hyvönen Date: Mon, 13 Apr 2015 08:36:27 +0300 Subject: Move details row decorators out of spacers td #17423 Change-Id: Ie6c0166ad307b9172acccaa781d6fea316ee6390 --- WebContent/VAADIN/themes/base/grid/grid.scss | 19 ++- .../com/vaadin/client/widget/escalator/Spacer.java | 5 + .../src/com/vaadin/client/widgets/Escalator.java | 125 ++++++++++++++++- client/src/com/vaadin/client/widgets/Grid.java | 149 +++------------------ 4 files changed, 158 insertions(+), 140 deletions(-) (limited to 'client/src') diff --git a/WebContent/VAADIN/themes/base/grid/grid.scss b/WebContent/VAADIN/themes/base/grid/grid.scss index 360f020822..0e1dee2b99 100644 --- a/WebContent/VAADIN/themes/base/grid/grid.scss +++ b/WebContent/VAADIN/themes/base/grid/grid.scss @@ -1,4 +1,5 @@ -$v-grid-border: 1px solid #ddd !default; +$v-grid-border-size: 1px !default; +$v-grid-border: $v-grid-border-size solid #ddd !default; $v-grid-cell-vertical-border: $v-grid-border !default; $v-grid-cell-horizontal-border: $v-grid-cell-vertical-border !default; $v-grid-cell-focused-border: 1px solid !default; @@ -420,6 +421,10 @@ $v-grid-details-border-bottom-stripe: 1px solid darken($v-grid-row-background-co margin-right: 4px; } + .#{$primaryStyleName}-spacer { + left: $v-grid-details-marker-width - $v-grid-border-size; + } + .#{$primaryStyleName}-spacer > td { display: block; padding: 0; @@ -434,8 +439,14 @@ $v-grid-details-border-bottom-stripe: 1px solid darken($v-grid-row-background-co border-top: $v-grid-details-border-top-stripe; border-bottom: $v-grid-details-border-bottom-stripe; } + + .#{$primaryStyleName}-spacer-deco-container { + position: relative; + top: $v-grid-border-size; + z-index: 5; + } - .#{$primaryStyleName}-spacer .deco { + .#{$primaryStyleName}-spacer-deco { top: 0; // this will be overridden by code, but it's a good default. left: 0; width: $v-grid-details-marker-width; @@ -456,10 +467,6 @@ $v-grid-details-border-bottom-stripe: 1px solid darken($v-grid-row-background-co } } - .#{$primaryStyleName}-spacer .content { - padding-left: $v-grid-details-marker-width; - } - // Renderers .#{$primaryStyleName}-cell > .v-progressbar { diff --git a/client/src/com/vaadin/client/widget/escalator/Spacer.java b/client/src/com/vaadin/client/widget/escalator/Spacer.java index 6ccef88f0d..789a64a21e 100644 --- a/client/src/com/vaadin/client/widget/escalator/Spacer.java +++ b/client/src/com/vaadin/client/widget/escalator/Spacer.java @@ -33,6 +33,11 @@ public interface Spacer { */ Element getElement(); + /** + * Gets the decorative element for this spacer. + */ + Element getDecoElement(); + /** * Gets the row index. * diff --git a/client/src/com/vaadin/client/widgets/Escalator.java b/client/src/com/vaadin/client/widgets/Escalator.java index 01567143dd..eae5789b8a 100644 --- a/client/src/com/vaadin/client/widgets/Escalator.java +++ b/client/src/com/vaadin/client/widgets/Escalator.java @@ -962,6 +962,7 @@ public class Escalator extends Widget implements RequiresResize, lastScrollTop = scrollTop; body.updateEscalatorRowsOnScroll(); + body.spacerContainer.updateSpacerDecosVisibility(); /* * TODO [[optimize]]: Might avoid a reflow by first calculating new * scrolltop and scrolleft, then doing the escalator magic based on @@ -2195,6 +2196,7 @@ public class Escalator extends Widget implements RequiresResize, - footer.getHeightOfSection()); body.verifyEscalatorCount(); + body.spacerContainer.updateSpacerDecosVisibility(); } Profiler.leave("Escalator.AbstractStaticRowContainer.recalculateSectionHeight"); @@ -2258,10 +2260,13 @@ public class Escalator extends Widget implements RequiresResize, @Override protected void sectionHeightCalculated() { - bodyElem.getStyle().setMarginTop(getHeightOfSection(), Unit.PX); + double heightOfSection = getHeightOfSection(); + bodyElem.getStyle().setMarginTop(heightOfSection, Unit.PX); + spacerDecoContainer.getStyle().setMarginTop(heightOfSection, + Unit.PX); verticalScrollbar.getElement().getStyle() - .setTop(getHeightOfSection(), Unit.PX); - headerDeco.getStyle().setHeight(getHeightOfSection(), Unit.PX); + .setTop(heightOfSection, Unit.PX); + headerDeco.getStyle().setHeight(heightOfSection, Unit.PX); } @Override @@ -3473,6 +3478,7 @@ public class Escalator extends Widget implements RequiresResize, tBodyScrollLeft = scrollLeft; tBodyScrollTop = scrollTop; position.set(bodyElem, -tBodyScrollLeft, -tBodyScrollTop); + position.set(spacerDecoContainer, 0, -tBodyScrollTop); } /** @@ -4602,9 +4608,11 @@ public class Escalator extends Widget implements RequiresResize, private final class SpacerImpl implements Spacer { private TableCellElement spacerElement; private TableRowElement root; + private DivElement deco; private int rowIndex; private double height = -1; private boolean domHasBeenSetup = false; + private double decoHeight; public SpacerImpl(int rowIndex) { this.rowIndex = rowIndex; @@ -4613,6 +4621,7 @@ public class Escalator extends Widget implements RequiresResize, spacerElement = TableCellElement.as(DOM.createTD()); root.appendChild(spacerElement); root.setPropertyInt(SPACER_LOGICAL_ROW_PROPERTY, rowIndex); + deco = DivElement.as(DOM.createDiv()); } public void setPositionDiff(double x, double y) { @@ -4637,12 +4646,19 @@ public class Escalator extends Widget implements RequiresResize, return root; } + @Override + public Element getDecoElement() { + return deco; + } + public void setPosition(double x, double y) { positions.set(getRootElement(), x, y); + positions.set(getDecoElement(), 0, y); } public void setStylePrimaryName(String style) { UIObject.setStylePrimaryName(root, style + "-spacer"); + UIObject.setStylePrimaryName(deco, style + "-spacer-deco"); } public void setHeight(double height) { @@ -4731,8 +4747,42 @@ public class Escalator extends Widget implements RequiresResize, verticalScrollbar.setScrollSize(verticalScrollbar .getScrollSize() + heightDiff); } + + updateDecoratorGeometry(height); + } + + /** Resizes and places the decorator. */ + private void updateDecoratorGeometry(double detailsHeight) { + Style style = deco.getStyle(); + decoHeight = detailsHeight + getBody().getDefaultRowHeight(); + + style.setTop( + -(getBody().getDefaultRowHeight() - getBorderTopHeight(getElement())), + Unit.PX); + style.setHeight(decoHeight, Unit.PX); } + private native double getBorderTopHeight(Element spacerCell) + /*-{ + if (typeof $wnd.getComputedStyle === 'function') { + var computedStyle = $wnd.getComputedStyle(spacerCell); + var borderTopWidth = computedStyle['borderTopWidth']; + var width = parseFloat(borderTopWidth); + return width; + } else { + var spacerRow = spacerCell.offsetParent; + var cloneCell = spacerCell.cloneNode(false); + spacerRow.appendChild(cloneCell); + cloneCell.style.height = "10px"; // IE8 wants the height to be set to something... + var heightWithBorder = cloneCell.offsetHeight; + cloneCell.style.borderTopWidth = "0"; + var heightWithoutBorder = cloneCell.offsetHeight; + spacerRow.removeChild(cloneCell); + + return heightWithBorder - heightWithoutBorder; + } + }-*/; + @Override public Element getElement() { return spacerElement; @@ -4795,6 +4845,35 @@ public class Escalator extends Widget implements RequiresResize, public void hide() { getRootElement().getStyle().setDisplay(Display.NONE); } + + /** + * Crop the decorator element so that it doesn't overlap the header + * and footer sections. + * + * @param bodyTop + * the top cordinate of the escalator body + * @param bodyBottom + * the bottom cordinate of the escalator body + * @param decoWidth + * width of the deco + */ + private void updateDecoClip(final double bodyTop, + final double bodyBottom, final double decoWidth) { + final int top = deco.getAbsoluteTop(); + final int bottom = deco.getAbsoluteBottom(); + if (top < bodyTop || bottom > bodyBottom) { + final double topClip = Math.max(0.0D, bodyTop - top); + final double bottomClip = decoHeight + - Math.max(0.0D, bottom - bodyBottom); + final String clip = new StringBuilder("rect(") + .append(topClip).append("px,").append(decoWidth) + .append("px,").append(bottomClip).append("px,0") + .toString(); + deco.getStyle().setProperty("clip", clip); + } else { + deco.getStyle().clearProperty("clip"); + } + } } private final TreeMap rowIndexToSpacer = new TreeMap(); @@ -4818,6 +4897,9 @@ public class Escalator extends Widget implements RequiresResize, }; private HandlerRegistration spacerScrollerRegistration; + /** Width of the spacers' decos. Calculated once then cached. */ + private double spacerDecoWidth = 0.0D; + public void setSpacer(int rowIndex, double height) throws IllegalArgumentException { @@ -4883,9 +4965,10 @@ public class Escalator extends Widget implements RequiresResize, } public void reapplySpacerWidths() { + // FIXME #16266 , spacers get couple pixels too much because borders + final double width = getInnerWidth() - spacerDecoWidth; for (SpacerImpl spacer : rowIndexToSpacer.values()) { - spacer.getRootElement().getStyle() - .setWidth(getInnerWidth(), Unit.PX); + spacer.getRootElement().getStyle().setWidth(width, Unit.PX); } } @@ -4916,6 +4999,7 @@ public class Escalator extends Widget implements RequiresResize, destroySpacerContent(spacer); spacer.setHeight(0); // resets row offsets spacer.getRootElement().removeFromParent(); + spacer.getDecoElement().removeFromParent(); } removedSpacers.clear(); @@ -5218,6 +5302,15 @@ public class Escalator extends Widget implements RequiresResize, body.getElement().appendChild(spacerRoot); spacer.setupDom(height); + spacerDecoContainer.appendChild(spacer.getDecoElement()); + if (spacerDecoContainer.getParentElement() == null) { + getElement().appendChild(spacerDecoContainer); + // calculate the spacer deco width, it won't change + spacerDecoWidth = WidgetUtil + .getRequiredWidthBoundingClientRectDouble(spacer + .getDecoElement()); + } + initSpacerContent(spacer); body.sortDomElements(); @@ -5340,6 +5433,22 @@ public class Escalator extends Widget implements RequiresResize, spacer.setRowIndex(spacer.getRow() + numberOfRows); } } + + private void updateSpacerDecosVisibility() { + final Range visibleRowRange = getVisibleRowRange(); + Collection visibleSpacers = rowIndexToSpacer.subMap( + visibleRowRange.getStart() - 1, + visibleRowRange.getEnd() + 1).values(); + if (!visibleSpacers.isEmpty()) { + final double top = tableWrapper.getAbsoluteTop() + + header.getHeightOfSection(); + final double bottom = tableWrapper.getAbsoluteBottom() + - footer.getHeightOfSection(); + for (SpacerImpl spacer : visibleSpacers) { + spacer.updateDecoClip(top, bottom, spacerDecoWidth); + } + } + } } private class ElementPositionBookkeeper { @@ -5495,6 +5604,8 @@ public class Escalator extends Widget implements RequiresResize, .createDiv()); private final DivElement headerDeco = DivElement.as(DOM.createDiv()); private final DivElement footerDeco = DivElement.as(DOM.createDiv()); + private final DivElement spacerDecoContainer = DivElement.as(DOM + .createDiv()); private PositionFunction position; @@ -5570,6 +5681,8 @@ public class Escalator extends Widget implements RequiresResize, setStylePrimaryName("v-escalator"); + spacerDecoContainer.setAttribute("aria-hidden", "true"); + // init default dimensions setHeight(null); setWidth(null); @@ -6294,6 +6407,8 @@ public class Escalator extends Widget implements RequiresResize, UIObject.setStylePrimaryName(footerDeco, style + "-footer-deco"); UIObject.setStylePrimaryName(horizontalScrollbarDeco, style + "-horizontal-scrollbar-deco"); + UIObject.setStylePrimaryName(spacerDecoContainer, style + + "-spacer-deco-container"); header.setStylePrimaryName(style); body.setStylePrimaryName(style); diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index 77bddb281f..f45d8ef3b4 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -2864,21 +2864,13 @@ public class Grid extends ResizeComposite implements private class GridSpacerUpdater implements SpacerUpdater { - private static final String DECO_CLASSNAME = "deco"; - private static final String CONTENT_CLASSNAME = "content"; private static final String STRIPE_CLASSNAME = "stripe"; private final Map elementToWidgetMap = new HashMap(); @Override public void init(Spacer spacer) { - initStructure(spacer); - Element root = getDetailsRoot(spacer); - - assert root.getFirstChild() == null : "The spacer's" - + " element should be empty at this point. (row: " - + spacer.getRow() + ", child: " + root.getFirstChild() - + ")"; + initTheming(spacer); int rowIndex = spacer.getRow(); @@ -2893,12 +2885,13 @@ public class Grid extends ResizeComposite implements } final double spacerHeight; + Element spacerElement = spacer.getElement(); if (detailsWidget == null) { - root.removeAllChildren(); + spacerElement.removeAllChildren(); spacerHeight = DETAILS_ROW_INITIAL_HEIGHT; } else { Element element = detailsWidget.getElement(); - root.appendChild(element); + spacerElement.appendChild(element); setParent(detailsWidget, Grid.this); Widget previousWidget = elementToWidgetMap.put(element, detailsWidget); @@ -2911,25 +2904,24 @@ public class Grid extends ResizeComposite implements * re-measure it to make sure that it's the correct height. */ double measuredHeight = WidgetUtil - .getRequiredHeightBoundingClientRectDouble(root); - assert getElement().isOrHasChild(root) : "The spacer element wasn't in the DOM during measurement, but was assumed to be."; + .getRequiredHeightBoundingClientRectDouble(spacerElement); + assert getElement().isOrHasChild(spacerElement) : "The spacer element wasn't in the DOM during measurement, but was assumed to be."; spacerHeight = measuredHeight; } escalator.getBody().setSpacer(rowIndex, spacerHeight); - updateDecoratorGeometry(spacerHeight, spacer); } @Override public void destroy(Spacer spacer) { - Element root = getDetailsRoot(spacer); + Element spacerElement = spacer.getElement(); - assert getElement().isOrHasChild(root) : "Trying " + assert getElement().isOrHasChild(spacerElement) : "Trying " + "to destroy a spacer that is not connected to this " + "Grid's DOM. (row: " + spacer.getRow() + ", element: " - + root + ")"; + + spacerElement + ")"; - Widget detailsWidget = elementToWidgetMap.remove(root + Widget detailsWidget = elementToWidgetMap.remove(spacerElement .getFirstChildElement()); if (detailsWidget != null) { @@ -2938,107 +2930,27 @@ public class Grid extends ResizeComposite implements * returned a null widget. */ - assert root.getFirstChild() != null : "The " + assert spacerElement.getFirstChild() != null : "The " + "details row to destroy did not contain a widget - " + "probably removed by something else without " + "permission? (row: " + spacer.getRow() - + ", element: " + root + ")"; + + ", element: " + spacerElement + ")"; setParent(detailsWidget, null); - root.removeAllChildren(); + spacerElement.removeAllChildren(); } } - /** - * Initializes the spacer element into a details structure, containing a - * decorator and a slot for the details widget. - */ - private void initStructure(Spacer spacer) { + private void initTheming(Spacer spacer) { Element spacerRoot = spacer.getElement(); - if (spacerRoot.getChildCount() == 0) { - Element deco = DOM.createDiv(); - deco.setClassName(DECO_CLASSNAME); - - Element detailsContent = DOM.createDiv(); - detailsContent.setClassName(CONTENT_CLASSNAME); - - if (spacer.getRow() % 2 == 1) { - spacerRoot.getParentElement() - .addClassName(STRIPE_CLASSNAME); - } - - spacerRoot.appendChild(deco); - spacerRoot.appendChild(detailsContent); - } - - else { - if (spacer.getRow() % 2 == 1) { - spacerRoot.getParentElement() - .addClassName(STRIPE_CLASSNAME); - } else { - spacerRoot.getParentElement().removeClassName( - STRIPE_CLASSNAME); - } - - /* - * The only case when we get into this else branch is when the - * previous generated details element was a null Widget. In - * those situations, we don't call destroy on the content, but - * simply reuse it as-is. - */ - assert getDetailsRoot(spacer).getChildCount() == 0 : "This " - + "code should never be triggered unless the details " - + "root already was empty"; + if (spacer.getRow() % 2 == 1) { + spacerRoot.getParentElement().addClassName(STRIPE_CLASSNAME); + } else { + spacerRoot.getParentElement().removeClassName(STRIPE_CLASSNAME); } } - /** Gets the decorator element from the DOM structure. */ - private Element getDecorator(Spacer spacer) { - TableCellElement td = TableCellElement.as(spacer.getElement()); - Element decorator = td.getFirstChildElement(); - return decorator; - } - - /** Gets the element for the details widget from the DOM structure. */ - private Element getDetailsRoot(Spacer spacer) { - Element detailsRoot = getDecorator(spacer).getNextSiblingElement(); - return detailsRoot; - } - - /** Resizes and places the decorator. */ - private void updateDecoratorGeometry(double detailsHeight, Spacer spacer) { - Element decorator = getDecorator(spacer); - Style style = decorator.getStyle(); - double rowHeight = escalator.getBody().getDefaultRowHeight(); - double borderHeight = getBorderHeight(spacer); - - style.setTop(-(rowHeight - borderHeight), Unit.PX); - style.setHeight(detailsHeight + rowHeight, Unit.PX); - } - - private native double getBorderHeight(Spacer spacer) - /*-{ - var spacerCell = spacer.@com.vaadin.client.widget.escalator.Spacer::getElement()(); - if (typeof $wnd.getComputedStyle === 'function') { - var computedStyle = $wnd.getComputedStyle(spacerCell); - var borderTopWidth = computedStyle['borderTopWidth']; - var width = parseFloat(borderTopWidth); - return width; - } else { - var spacerRow = spacerCell.offsetParent; - var cloneCell = spacerCell.cloneNode(false); - spacerRow.appendChild(cloneCell); - cloneCell.style.height = "10px"; // IE8 wants the height to be set to something... - var heightWithBorder = cloneCell.offsetHeight; - cloneCell.style.borderTopWidth = "0"; - var heightWithoutBorder = cloneCell.offsetHeight; - spacerRow.removeChild(cloneCell); - - console.log(heightWithBorder+" - "+heightWithoutBorder); - return heightWithBorder - heightWithoutBorder; - } - }-*/; } /** @@ -6539,18 +6451,14 @@ public class Grid extends ResizeComposite implements public com.google.gwt.user.client.Element getSubPartElement(String subPart) { /* - * gandles details[] (translated to spacer[] for Escalator), cell[], + * handles details[] (translated to spacer[] for Escalator), cell[], * header[] and footer[] */ Element escalatorElement = escalator.getSubPartElement(subPart .replaceFirst("^details\\[", "spacer[")); if (escalatorElement != null) { - if (subPart.startsWith("details[")) { - return DOM.asOld(parseDetails(escalatorElement)); - } else { - return DOM.asOld(escalatorElement); - } + return DOM.asOld(escalatorElement); } SubPartArguments args = Escalator.parseSubPartArguments(subPart); @@ -6563,23 +6471,6 @@ public class Grid extends ResizeComposite implements return null; } - @SuppressWarnings("static-method") - private Element parseDetails(Element spacer) { - assert spacer.getChildCount() == 2 : "Unexpected structure for details "; - - Element decorator = spacer.getFirstChildElement(); - assert decorator != null : "unexpected spacer DOM structure"; - assert decorator.getClassName() - .equals(GridSpacerUpdater.DECO_CLASSNAME) : "unexpected first details element"; - - Element spacerRoot = decorator.getNextSiblingElement(); - assert spacerRoot != null : "unexpected spacer DOM structure"; - assert spacerRoot.getClassName().equals( - GridSpacerUpdater.CONTENT_CLASSNAME) : "unexpected second details element"; - - return DOM.asOld(spacerRoot); - } - private Element getSubPartElementEditor(SubPartArguments args) { if (!args.getType().equalsIgnoreCase("editor") -- cgit v1.2.3 From dd550858b9b4300acac8e35159f4c93e7f58d8ed Mon Sep 17 00:00:00 2001 From: Pekka Hyvönen Date: Tue, 14 Apr 2015 15:39:18 +0300 Subject: Calculate Grid sidebar button height when closed #17412 + contains Valo theming fixes for all browsers. + includes screenshot test for sidebar in Valo Change-Id: Ic6401057efff7e4d4ab65c46885dda2d995bce5d --- WebContent/VAADIN/themes/base/grid/grid.scss | 24 ++++-- WebContent/VAADIN/themes/reindeer/grid/grid.scss | 8 +- .../VAADIN/themes/valo/components/_grid.scss | 11 +-- client/src/com/vaadin/client/widgets/Grid.java | 22 ++++++ .../grid/basicfeatures/GridBasicFeaturesTest.java | 27 +++++++ .../grid/basicfeatures/GridColumnHidingTest.java | 9 ++- .../server/GridColumnVisibilityTest.java | 31 -------- .../basicfeatures/server/GridSidebarThemeTest.java | 86 ++++++++++++++++++++++ .../basicfeatures/server/LoadingIndicatorTest.java | 7 -- .../src/com/vaadin/tests/tb3/AbstractTB3Test.java | 20 +++++ 10 files changed, 183 insertions(+), 62 deletions(-) create mode 100644 uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridSidebarThemeTest.java (limited to 'client/src') diff --git a/WebContent/VAADIN/themes/base/grid/grid.scss b/WebContent/VAADIN/themes/base/grid/grid.scss index 0e1dee2b99..d6509c1216 100644 --- a/WebContent/VAADIN/themes/base/grid/grid.scss +++ b/WebContent/VAADIN/themes/base/grid/grid.scss @@ -85,9 +85,9 @@ $v-grid-details-border-bottom-stripe: 1px solid darken($v-grid-row-background-co } } } - + // Sidebar - + .#{$primaryStyleName}-sidebar.v-contextmenu { @include box-shadow(none); position: absolute; @@ -103,32 +103,44 @@ $v-grid-details-border-bottom-stripe: 1px solid darken($v-grid-row-background-co background: transparent; border: none; cursor: pointer; - height: $v-grid-header-row-height; outline: none; padding: 0 4px; text-align: right; + &::-moz-focus-inner { + border: 0; + } + &:after { content: "\f0c9"; + display: block; font-family: FontAwesome, sans-serif; font-size: $v-grid-header-font-size; - line-height: $v-grid-header-row-height; } } &.closed { border-radius: 0; } - + &.opened { .#{$primaryStyleName}-sidebar-button { width: 100%; - + &:after { content: "\00d7"; font-size: 16px; + line-height: 1; } } + } + + .v-ie &.opened .#{$primaryStyleName}-sidebar-button { + vertical-align: middle; + } + + .v-ie8 &.opened .#{$primaryStyleName}-sidebar-button:after { + display: inline; } .#{$primaryStyleName}-sidebar-content { diff --git a/WebContent/VAADIN/themes/reindeer/grid/grid.scss b/WebContent/VAADIN/themes/reindeer/grid/grid.scss index f9b966096a..7ae0f402aa 100644 --- a/WebContent/VAADIN/themes/reindeer/grid/grid.scss +++ b/WebContent/VAADIN/themes/reindeer/grid/grid.scss @@ -34,15 +34,9 @@ border-color: #b1cde4; } } - + // Sidebar .#{$primaryStyleName}-sidebar.v-contextmenu { - &.closed { - .#{$primaryStyleName}-sidebar-button:after { - line-height: 20px; - } - } - .#{$primaryStyleName}-sidebar-content { background-color: #f8f8f9; } diff --git a/WebContent/VAADIN/themes/valo/components/_grid.scss b/WebContent/VAADIN/themes/valo/components/_grid.scss index c1862c8209..c07d330410 100644 --- a/WebContent/VAADIN/themes/valo/components/_grid.scss +++ b/WebContent/VAADIN/themes/valo/components/_grid.scss @@ -193,24 +193,19 @@ $v-grid-details-border-bottom-stripe: $v-grid-cell-horizontal-border !default; &.opened { .#{$primary-stylename}-sidebar-button:after { font-size: 20px; - line-height: 20px; } - + .#{$primary-stylename}-sidebar-content { margin: 0 0 2px; padding: 4px 4px 2px; } } - + &.closed { @include valo-gradient($v-grid-header-background-color); - - .#{$primary-stylename}-sidebar-button { - line-height: 36px; - } } } - + // Customize scrollbars .#{$primary-stylename}-scroller { &::-webkit-scrollbar { diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index f45d8ef3b4..220e83257b 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -3080,6 +3080,7 @@ public class Grid extends ResizeComposite implements removeStyleName("closed"); rootContainer.add(content); } + openCloseButton.setHeight(""); } /** @@ -3090,6 +3091,8 @@ public class Grid extends ResizeComposite implements removeStyleName("opened"); addStyleName("closed"); content.removeFromParent(); + // adjust open button to header height when closed + setHeightToHeaderCellHeight(); } } @@ -3156,6 +3159,23 @@ public class Grid extends ResizeComposite implements } } + private void setHeightToHeaderCellHeight() { + try { + double height = WidgetUtil + .getRequiredHeightBoundingClientRectDouble(grid.escalator + .getHeader().getRowElement(0) + .getFirstChildElement()) + - (WidgetUtil.measureVerticalBorder(getElement()) / 2); + openCloseButton.setHeight(height + "px"); + } catch (NullPointerException npe) { + getLogger() + .warning( + "Got null header first row or first row cell when calculating sidebar button height"); + openCloseButton.setHeight(grid.escalator.getHeader() + .getDefaultRowHeight() + "px"); + } + } + private void updateVisibility() { final boolean hasWidgets = content.getWidgetCount() > 0; final boolean isVisible = isInDOM(); @@ -3166,6 +3186,8 @@ public class Grid extends ResizeComposite implements close(); grid.getElement().appendChild(getElement()); Grid.setParent(this, grid); + // border calculation won't work until attached + setHeightToHeaderCellHeight(); } } 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 ca8ea9fd75..469bf37c2f 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeaturesTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeaturesTest.java @@ -233,4 +233,31 @@ public abstract class GridBasicFeaturesTest extends MultiBrowserTest { assertTrue(getGridElement().getCell(row, column).getAttribute("class") .contains("focused")); } + + protected WebElement getSidebar() { + List elements = findElements(By.className("v-grid-sidebar")); + return elements.isEmpty() ? null : elements.get(0); + } + + protected WebElement getSidebarOpenButton() { + List elements = findElements(By + .className("v-grid-sidebar-button")); + return elements.isEmpty() ? null : elements.get(0); + } + + /** + * Returns the toggle inside the sidebar for hiding the column at the given + * index, or null if not found. + */ + protected WebElement getColumnHidingToggle(int columnIndex) { + WebElement sidebar = getSidebar(); + List elements = sidebar.findElements(By + .className("column-hiding-toggle")); + for (WebElement e : elements) { + if ((e.getText().toLowerCase()).startsWith("column " + columnIndex)) { + return e; + } + } + return null; + } } diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnHidingTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnHidingTest.java index 1213e02799..b446bdef48 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnHidingTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnHidingTest.java @@ -921,12 +921,14 @@ public class GridColumnHidingTest extends GridBasicClientFeaturesTest { assertNotNull(sidebar); } - private WebElement getSidebar() { + @Override + protected WebElement getSidebar() { List elements = findElements(By.className("v-grid-sidebar")); return elements.isEmpty() ? null : elements.get(0); } - private WebElement getSidebarOpenButton() { + @Override + protected WebElement getSidebarOpenButton() { List elements = findElements(By .className("v-grid-sidebar-button")); return elements.isEmpty() ? null : elements.get(0); @@ -936,7 +938,8 @@ public class GridColumnHidingTest extends GridBasicClientFeaturesTest { * Returns the toggle inside the sidebar for hiding the column at the given * index, or null if not found. */ - private WebElement getColumnHidingToggle(int columnIndex) { + @Override + protected WebElement getColumnHidingToggle(int columnIndex) { WebElement sidebar = getSidebar(); List elements = sidebar.findElements(By .className("column-hiding-toggle")); diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridColumnVisibilityTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridColumnVisibilityTest.java index 7942650576..d01e689b72 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridColumnVisibilityTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridColumnVisibilityTest.java @@ -21,12 +21,8 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import java.util.List; - import org.junit.Before; import org.junit.Test; -import org.openqa.selenium.By; -import org.openqa.selenium.WebElement; import com.vaadin.testbench.parallel.TestCategory; import com.vaadin.tests.components.grid.basicfeatures.GridBasicFeaturesTest; @@ -281,31 +277,4 @@ public class GridColumnVisibilityTest extends GridBasicFeaturesTest { selectMenuPath("Component", "Columns", "Column " + index, "Add / Remove"); } - - private WebElement getSidebar() { - List elements = findElements(By.className("v-grid-sidebar")); - return elements.isEmpty() ? null : elements.get(0); - } - - private WebElement getSidebarOpenButton() { - List elements = findElements(By - .className("v-grid-sidebar-button")); - return elements.isEmpty() ? null : elements.get(0); - } - - /** - * Returns the toggle inside the sidebar for hiding the column at the given - * index, or null if not found. - */ - private WebElement getColumnHidingToggle(int columnIndex) { - WebElement sidebar = getSidebar(); - List elements = sidebar.findElements(By - .className("column-hiding-toggle")); - for (WebElement e : elements) { - if ((e.getText().toLowerCase()).startsWith("column " + columnIndex)) { - return e; - } - } - return null; - } } diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridSidebarThemeTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridSidebarThemeTest.java new file mode 100644 index 0000000000..79327993b0 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridSidebarThemeTest.java @@ -0,0 +1,86 @@ +/* + * 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.server; + +import java.io.IOException; +import java.util.List; + +import org.junit.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.interactions.Actions; +import org.openqa.selenium.remote.DesiredCapabilities; +import org.openqa.selenium.support.ui.ExpectedConditions; + +import com.vaadin.testbench.parallel.Browser; +import com.vaadin.tests.components.grid.basicfeatures.GridBasicFeaturesTest; + +public class GridSidebarThemeTest extends GridBasicFeaturesTest { + + @Test + public void testValo() throws Exception { + runTestSequence("valo"); + } + + private void runTestSequence(String theme) throws IOException { + openTestURL("theme=" + theme); + if (getDesiredCapabilities().getBrowserName().equals( + Browser.CHROME.getDesiredCapabilities().getBrowserName())) { + waitUntil(ExpectedConditions.elementToBeClickable(By.id("menu")), 2); + getDriver().findElement(By.id("menu")).click(); + selectMenu("Columns"); + selectMenu("All columns hidable"); + waitUntilLoadingIndicatorNotVisible(); + } else { + selectMenuPath("Component", "Columns", "All columns hidable"); + } + + compareScreen(theme + "|SidebarClosed"); + getSidebarOpenButton().click(); + + compareScreen(theme + "|SidebarOpen"); + + new Actions(getDriver()).moveToElement(getColumnHidingToggle(2), 5, 5) + .perform(); + + compareScreen(theme + "|OnMouseOverNotHiddenToggle"); + + getColumnHidingToggle(2).click(); + getColumnHidingToggle(3).click(); + getColumnHidingToggle(6).click(); + + new Actions(getDriver()).moveToElement(getSidebarOpenButton()) + .perform(); + ; + + compareScreen(theme + "|TogglesTriggered"); + + new Actions(getDriver()).moveToElement(getColumnHidingToggle(2)) + .perform(); + ; + + compareScreen(theme + "|OnMouseOverHiddenToggle"); + + getSidebarOpenButton().click(); + + compareScreen(theme + "|SidebarClosed2"); + } + + @Override + public List getBrowsersToTest() { + // phantom JS looks wrong from the beginning, so not tested + return getBrowsersExcludingPhantomJS(); + } +} diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/LoadingIndicatorTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/LoadingIndicatorTest.java index f251313100..f4771b9067 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/LoadingIndicatorTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/LoadingIndicatorTest.java @@ -19,7 +19,6 @@ import org.junit.Assert; import org.junit.Test; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; -import org.openqa.selenium.WebElement; import org.openqa.selenium.support.ui.ExpectedCondition; import org.openqa.selenium.support.ui.ExpectedConditions; @@ -83,10 +82,4 @@ public class LoadingIndicatorTest extends GridBasicFeaturesTest { }); } - private boolean isLoadingIndicatorVisible() { - WebElement loadingIndicator = findElement(By - .className("v-loading-indicator")); - - return loadingIndicator.isDisplayed(); - } } diff --git a/uitest/src/com/vaadin/tests/tb3/AbstractTB3Test.java b/uitest/src/com/vaadin/tests/tb3/AbstractTB3Test.java index 2d032ecd9e..48f99e5057 100644 --- a/uitest/src/com/vaadin/tests/tb3/AbstractTB3Test.java +++ b/uitest/src/com/vaadin/tests/tb3/AbstractTB3Test.java @@ -938,4 +938,24 @@ public abstract class AbstractTB3Test extends ParallelTest { protected void click(CheckBoxElement checkbox) { checkbox.findElement(By.xpath("input")).click(); } + + protected boolean isLoadingIndicatorVisible() { + WebElement loadingIndicator = findElement(By + .className("v-loading-indicator")); + + return loadingIndicator.isDisplayed(); + } + + protected void waitUntilLoadingIndicatorNotVisible() { + waitUntil(new ExpectedCondition() { + + @Override + public Boolean apply(WebDriver input) { + WebElement loadingIndicator = input.findElement(By + .className("v-loading-indicator")); + + return !loadingIndicator.isDisplayed(); + } + }); + } } -- cgit v1.2.3 From a69660c9d8cb35fc73af09b42df82c7a4a153caa Mon Sep 17 00:00:00 2001 From: Pekka Hyvönen Date: Tue, 21 Apr 2015 15:42:54 +0300 Subject: Fixed regression and fragile test for Grid. - Fixed regression caused by #17423 in IE8 - Fixed fragile tests for focusing spacer content in Grid - Changed TestCategory for EscalatorBasicClientFeaturesTest from 'escalator' to 'grid'. Change-Id: I644f85a68fee643e468342093ad537dcd7bf8626 --- client/src/com/vaadin/client/widgets/Escalator.java | 2 +- .../grid/basicfeatures/EscalatorBasicClientFeaturesTest.java | 2 +- .../grid/basicfeatures/escalator/EscalatorSpacerTest.java | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) (limited to 'client/src') diff --git a/client/src/com/vaadin/client/widgets/Escalator.java b/client/src/com/vaadin/client/widgets/Escalator.java index eae5789b8a..cfbbb7dace 100644 --- a/client/src/com/vaadin/client/widgets/Escalator.java +++ b/client/src/com/vaadin/client/widgets/Escalator.java @@ -4871,7 +4871,7 @@ public class Escalator extends Widget implements RequiresResize, .toString(); deco.getStyle().setProperty("clip", clip); } else { - deco.getStyle().clearProperty("clip"); + deco.getStyle().setProperty("clip", "auto"); } } } 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 862e959ebc..c1b8028cbf 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/EscalatorBasicClientFeaturesTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/EscalatorBasicClientFeaturesTest.java @@ -31,7 +31,7 @@ import com.vaadin.testbench.TestBenchElement; import com.vaadin.testbench.parallel.TestCategory; import com.vaadin.tests.tb3.MultiBrowserTest; -@TestCategory("escalator") +@TestCategory("grid") public abstract class EscalatorBasicClientFeaturesTest extends MultiBrowserTest { private static final String LOGICAL_ROW_ATTRIBUTE_NAME = "vLogicalRow"; 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 index 71cc19ecdd..e044c192f7 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/escalator/EscalatorSpacerTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/escalator/EscalatorSpacerTest.java @@ -34,6 +34,7 @@ import org.openqa.selenium.WebElement; import com.vaadin.client.WidgetUtil; import com.vaadin.shared.ui.grid.Range; +import com.vaadin.testbench.TestBenchElement; import com.vaadin.testbench.elements.NotificationElement; import com.vaadin.testbench.parallel.BrowserUtil; import com.vaadin.tests.components.grid.basicfeatures.EscalatorBasicClientFeaturesTest; @@ -450,7 +451,8 @@ public class EscalatorSpacerTest extends EscalatorBasicClientFeaturesTest { selectMenuPath(FEATURES, SPACERS, FOCUSABLE_UPDATER); selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX); tryToTabIntoFocusUpdaterElement(); - assertEquals("input", getFocusedElement().getTagName()); + WebElement focusedElement = getFocusedElement(); + assertEquals("input", focusedElement.getTagName()); } @Test @@ -485,11 +487,9 @@ public class EscalatorSpacerTest extends EscalatorBasicClientFeaturesTest { } private void tryToTabIntoFocusUpdaterElement() { - getEscalator().sendKeys( // - Keys.TAB, // v-ui v-scrollable - Keys.TAB, // menubar - Keys.TAB // - ); + ((TestBenchElement) findElement(By.className("gwt-MenuBar"))).focus(); + WebElement focusedElement = getFocusedElement(); + focusedElement.sendKeys(Keys.TAB); } private WebElement getChild(WebElement parent, int childIndex) { -- cgit v1.2.3 From ec4e470e401a1a8f55b427b04dee08c39ed63d8b Mon Sep 17 00:00:00 2001 From: Pekka Hyvönen Date: Wed, 22 Apr 2015 17:17:18 +0300 Subject: Fixed theming issues with Grid's Sidebar (#17412) - if columns hidable when grid rendered, sidebar button had invalid height - removed border radius from grid sidebar - inherit font color for grid sidebar button Change-Id: Ib35d917b35e701bd736a28f19b34dbece7fcbe71 --- WebContent/VAADIN/themes/base/grid/grid.scss | 5 +++-- client/src/com/vaadin/client/widgets/Grid.java | 14 +++++++++++++ .../grid/basicfeatures/GridBasicFeatures.java | 20 +++++++++++++----- .../grid/basicfeatures/GridSidebarFeatures.java | 24 ++++++++++++++++++++++ .../basicfeatures/server/GridSidebarThemeTest.java | 24 ++++++++++------------ 5 files changed, 67 insertions(+), 20 deletions(-) create mode 100644 uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridSidebarFeatures.java (limited to 'client/src') diff --git a/WebContent/VAADIN/themes/base/grid/grid.scss b/WebContent/VAADIN/themes/base/grid/grid.scss index 838c0de502..ee503822b9 100644 --- a/WebContent/VAADIN/themes/base/grid/grid.scss +++ b/WebContent/VAADIN/themes/base/grid/grid.scss @@ -90,6 +90,7 @@ $v-grid-details-border-bottom-stripe: 1px solid darken($v-grid-row-background-co .#{$primaryStyleName}-sidebar.v-contextmenu { @include box-shadow(none); + border-radius: 0; position: absolute; top: 0; right: 0; @@ -102,6 +103,7 @@ $v-grid-details-border-bottom-stripe: 1px solid darken($v-grid-row-background-co .#{$primaryStyleName}-sidebar-button { background: transparent; border: none; + color: inherit; cursor: pointer; outline: none; padding: 0 4px; @@ -133,7 +135,7 @@ $v-grid-details-border-bottom-stripe: 1px solid darken($v-grid-row-background-co line-height: 1; } } - } + } .v-ie &.opened .#{$primaryStyleName}-sidebar-button { vertical-align: middle; @@ -144,7 +146,6 @@ $v-grid-details-border-bottom-stripe: 1px solid darken($v-grid-row-background-co } .#{$primaryStyleName}-sidebar-content { - background: #fff; border-top: $v-grid-border; padding: 4px 0; diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index 0934d61eeb..65d1173ee4 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -3191,6 +3191,20 @@ public class Grid extends ResizeComposite implements private boolean isInDOM() { return getParent() != null; } + + @Override + protected void onAttach() { + super.onAttach(); + // make sure the button will get correct height if the button should + // be visible when the grid is rendered the first time. + Scheduler.get().scheduleDeferred(new ScheduledCommand() { + + @Override + public void execute() { + setHeightToHeaderCellHeight(); + } + }); + } } /** diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java index 2abd603ae4..8c60deaf3c 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java @@ -291,7 +291,9 @@ public class GridBasicFeatures extends AbstractComponentTest { // Set varying column widths for (int col = 0; col < COLUMNS; col++) { - grid.getColumn(getColumnProperty(col)).setWidth(100 + col * 50); + Column column = grid.getColumn(getColumnProperty(col)); + column.setWidth(100 + col * 50); + column.setHidable(isColumnHidableByDefault(col)); } grid.addSortListener(new SortListener() { @@ -334,6 +336,14 @@ public class GridBasicFeatures extends AbstractComponentTest { return grid; } + protected boolean isColumnHidableByDefault(int col) { + return false; + } + + protected boolean isColumnHiddenByDefault(int col) { + return false; + } + private void addInternalActions() { createClickAction("Update column order without updating client", "Internals", new Command() { @@ -828,8 +838,8 @@ public class GridBasicFeatures extends AbstractComponentTest { } }, c); - createBooleanAction("Hidable", getColumnProperty(c), false, - new Command() { + createBooleanAction("Hidable", getColumnProperty(c), + isColumnHidableByDefault(c), new Command() { @Override public void execute(Grid c, Boolean hidable, Object propertyId) { @@ -837,8 +847,8 @@ public class GridBasicFeatures extends AbstractComponentTest { } }, getColumnProperty(c)); - createBooleanAction("Hidden", getColumnProperty(c), false, - new Command() { + createBooleanAction("Hidden", getColumnProperty(c), + isColumnHiddenByDefault(c), new Command() { @Override public void execute(Grid c, Boolean hidden, Object propertyId) { diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridSidebarFeatures.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridSidebarFeatures.java new file mode 100644 index 0000000000..9494988cf4 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridSidebarFeatures.java @@ -0,0 +1,24 @@ +/* + * 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; + +public class GridSidebarFeatures extends GridBasicFeatures { + + @Override + protected boolean isColumnHidableByDefault(int col) { + return true; + } +} diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridSidebarThemeTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridSidebarThemeTest.java index 79327993b0..5262156b41 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridSidebarThemeTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridSidebarThemeTest.java @@ -19,13 +19,11 @@ import java.io.IOException; import java.util.List; import org.junit.Test; -import org.openqa.selenium.By; import org.openqa.selenium.interactions.Actions; import org.openqa.selenium.remote.DesiredCapabilities; -import org.openqa.selenium.support.ui.ExpectedConditions; -import com.vaadin.testbench.parallel.Browser; import com.vaadin.tests.components.grid.basicfeatures.GridBasicFeaturesTest; +import com.vaadin.tests.components.grid.basicfeatures.GridSidebarFeatures; public class GridSidebarThemeTest extends GridBasicFeaturesTest { @@ -34,18 +32,18 @@ public class GridSidebarThemeTest extends GridBasicFeaturesTest { runTestSequence("valo"); } + @Test + public void testValoDark() throws Exception { + runTestSequence("tests-valo-dark"); + } + + @Override + protected Class getUIClass() { + return GridSidebarFeatures.class; + } + private void runTestSequence(String theme) throws IOException { openTestURL("theme=" + theme); - if (getDesiredCapabilities().getBrowserName().equals( - Browser.CHROME.getDesiredCapabilities().getBrowserName())) { - waitUntil(ExpectedConditions.elementToBeClickable(By.id("menu")), 2); - getDriver().findElement(By.id("menu")).click(); - selectMenu("Columns"); - selectMenu("All columns hidable"); - waitUntilLoadingIndicatorNotVisible(); - } else { - selectMenuPath("Component", "Columns", "All columns hidable"); - } compareScreen(theme + "|SidebarClosed"); getSidebarOpenButton().click(); -- cgit v1.2.3 From 0435a27fc9ed0e04f33d7b046e66f571ab4b09e0 Mon Sep 17 00:00:00 2001 From: Leif Åstrand Date: Tue, 21 Apr 2015 17:06:50 +0300 Subject: Add support for custom Grid sidebar items (#17569) The current implementation does not in all cases enforce that visibility toggles are always before any custom items. The JavaDoc warns about this and the order is also restored whenever a visibility toggle is added or removed. Change-Id: I7160a04d6c96b2d6d821b13e420172e6115bc072 --- client/src/com/vaadin/client/widgets/Grid.java | 54 +++++++++++- .../client/GridSidebarContentTest.java | 99 ++++++++++++++++++++++ .../client/grid/GridBasicClientFeaturesWidget.java | 87 +++++++++++++++++++ 3 files changed, 237 insertions(+), 3 deletions(-) (limited to 'client/src') diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index 65d1173ee4..005532a849 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -3012,12 +3012,13 @@ public class Grid extends ResizeComposite implements menuBar = new MenuBar(true) { @Override - public MenuItem addItem(MenuItem item) { + public MenuItem insertItem(MenuItem item, int beforeIndex) + throws IndexOutOfBoundsException { if (getParent() == null) { content.insert(this, 0); updateVisibility(); } - return super.addItem(item); + return super.insertItem(item, beforeIndex); } @Override @@ -3273,12 +3274,13 @@ public class Grid extends ResizeComposite implements private void updateTogglesOrder() { if (!hidingColumn) { + int lastIndex = 0; for (Column column : getColumns()) { if (column.isHidable()) { final MenuItem menuItem = columnToHidingToggleMap .get(column); sidebar.menuBar.removeItem(menuItem); - sidebar.menuBar.addItem(menuItem); + sidebar.menuBar.insertItem(menuItem, lastIndex++); } } } @@ -7710,4 +7712,50 @@ public class Grid extends ResizeComposite implements private Sidebar getSidebar() { return sidebar; } + + /** + * Gets the customizable menu bar that is by default used for toggling + * column hidability. The application developer is allowed to add their + * custom items to the end of the menu, but should try to avoid modifying + * the items in the beginning of the menu that control the column hiding if + * any columns are marked as hidable. A toggle for opening the menu will be + * displayed whenever the menu contains at least one item. + * + * @since 7.5.0 + * @return the menu bar + */ + public MenuBar getSidebarMenu() { + return sidebar.menuBar; + } + + /** + * Tests whether the sidebar menu is currently open. + * + * @since 7.5.0 + * @see #getSidebarMenu() + * @return true if the sidebar is open; false if + * it is closed + */ + public boolean isSidebarOpen() { + return sidebar.isOpen(); + } + + /** + * Sets whether the sidebar menu is open. + * + * + * @since 7.5.0 + * @see #getSidebarMenu() + * @see #isSidebarOpen() + * @param sidebarOpen + * true to open the sidebar; false to + * close it + */ + public void setSidebarOpen(boolean sidebarOpen) { + if (sidebarOpen) { + sidebar.open(); + } else { + sidebar.close(); + } + } } diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridSidebarContentTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridSidebarContentTest.java index fb647c7a41..c5092a9c22 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridSidebarContentTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridSidebarContentTest.java @@ -15,6 +15,8 @@ */ package com.vaadin.tests.components.grid.basicfeatures.client; +import java.util.List; + import org.junit.Assert; import org.junit.Test; import org.openqa.selenium.By; @@ -49,6 +51,103 @@ public class GridSidebarContentTest extends GridBasicClientFeaturesTest { } + @Test + public void testAddingCustomSidebarItem() { + openTestURL(); + CustomGridElement gridElement = getGridElement(); + + selectMenuPath("Component", "Sidebar", "Add item to end"); + + gridElement.findElement(By.className("v-grid-sidebar-button")).click(); + + WebElement sidebarItem = gridElement.findElement(By + .cssSelector(".v-grid-sidebar-content .gwt-MenuItem")); + + sidebarItem.click(); + + Assert.assertEquals("Sidebar should be closed after clicking item 0", + 0, countBySelector(".v-grid-sidebar-content")); + } + + @Test + public void testProgrammaticSidebarToggle() { + openTestURL(); + + selectMenuPath("Component", "Columns", "Column 0", "Hidable"); + + selectMenuPath("Component", "Sidebar", "Toggle sidebar visibility"); + + Assert.assertEquals("Sidebar be open", 1, + countBySelector(".v-grid-sidebar-content")); + + selectMenuPath("Component", "Sidebar", "Toggle sidebar visibility"); + + Assert.assertEquals("Sidebar be closed", 0, + countBySelector(".v-grid-sidebar-content")); + } + + @Test + public void testBasicSidebarOrder() { + openTestURL(); + CustomGridElement gridElement = getGridElement(); + + // First add custom content + selectMenuPath("Component", "Sidebar", "Add separator to end"); + selectMenuPath("Component", "Sidebar", "Add item to end"); + + // Then make one column togglable + selectMenuPath("Component", "Columns", "Column 0", "Hidable"); + + selectMenuPath("Component", "Sidebar", "Toggle sidebar visibility"); + + assertSidebarMenuItems("Header (0,0)", null, "Custom menu item 0"); + } + + @Test + public void testSidebarOrderAbuse() { + openTestURL(); + CustomGridElement gridElement = getGridElement(); + + selectMenuPath("Component", "Columns", "Column 0", "Hidable"); + selectMenuPath("Component", "Columns", "Column 1", "Hidable"); + + // Inserts a menu item between the two visibility toggles + selectMenuPath("Component", "Sidebar", "Add item before index 1"); + + selectMenuPath("Component", "Sidebar", "Toggle sidebar visibility"); + + // Total order enforcement not implemented at this point. Test can be + // updated when it is. + assertSidebarMenuItems("Header (0,0)", "Custom menu item 0", + "Header (0,1)"); + + selectMenuPath("Component", "Columns", "Column 2", "Hidable"); + + // Adding a new togglable column should have restored the expected order + assertSidebarMenuItems("Header (0,0)", "Header (0,1)", "Header (0,2)", + "Custom menu item 0"); + } + + private void assertSidebarMenuItems(String... items) { + List menuItems = getGridElement().findElements( + By.cssSelector(".v-grid-sidebar-content td")); + + Assert.assertEquals("Expected " + items.length + " menu items", + items.length, menuItems.size()); + + for (int i = 0; i < items.length; i++) { + String expectedItem = items[i]; + if (expectedItem == null) { + Assert.assertEquals("Item " + i + " should be a separator", + "gwt-MenuItemSeparator", + menuItems.get(i).getAttribute("class")); + } else { + Assert.assertEquals("Unexpected content for item " + i, + expectedItem, menuItems.get(i).getText()); + } + } + } + private int countBySelector(String cssSelector) { return getGridElement().findElements(By.cssSelector(cssSelector)) .size(); 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 196428822c..81f000c44e 100644 --- a/uitest/src/com/vaadin/tests/widgetset/client/grid/GridBasicClientFeaturesWidget.java +++ b/uitest/src/com/vaadin/tests/widgetset/client/grid/GridBasicClientFeaturesWidget.java @@ -36,6 +36,8 @@ import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.MenuItem; +import com.google.gwt.user.client.ui.MenuItemSeparator; import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.Widget; import com.vaadin.client.data.DataSource; @@ -409,6 +411,7 @@ public class GridBasicClientFeaturesWidget extends createInternalsMenu(); createDataSourceMenu(); createDetailsMenu(); + createSidebarMenu(); grid.getElement().getStyle().setZIndex(0); @@ -1478,4 +1481,88 @@ public class GridBasicClientFeaturesWidget extends } } + + private static Logger getLogger() { + return Logger.getLogger(GridBasicClientFeaturesWidget.class.getName()); + } + + private void createSidebarMenu() { + String[] menupath = new String[] { "Component", "Sidebar" }; + + final List customMenuItems = new ArrayList(); + final List separators = new ArrayList(); + + addMenuCommand("Add item to end", new ScheduledCommand() { + @Override + public void execute() { + MenuItem item = createSidebarMenuItem(customMenuItems.size()); + customMenuItems.add(item); + grid.getSidebarMenu().addItem(item); + } + }, menupath); + + addMenuCommand("Add item before index 1", new ScheduledCommand() { + @Override + public void execute() { + MenuItem item = createSidebarMenuItem(customMenuItems.size()); + customMenuItems.add(item); + grid.getSidebarMenu().insertItem(item, 1); + } + }, menupath); + + addMenuCommand("Remove last added item", new ScheduledCommand() { + @Override + public void execute() { + grid.getSidebarMenu().removeItem( + customMenuItems.remove(customMenuItems.size() - 1)); + } + }, menupath); + + addMenuCommand("Add separator to end", new ScheduledCommand() { + @Override + public void execute() { + MenuItemSeparator separator = new MenuItemSeparator(); + separators.add(separator); + grid.getSidebarMenu().addSeparator(separator); + } + }, menupath); + + addMenuCommand("Add separator before index 1", new ScheduledCommand() { + @Override + public void execute() { + MenuItemSeparator separator = new MenuItemSeparator(); + separators.add(separator); + grid.getSidebarMenu().insertSeparator(separator, 1); + } + }, menupath); + + addMenuCommand("Remove last added separator", new ScheduledCommand() { + @Override + public void execute() { + grid.getSidebarMenu().removeSeparator( + separators.remove(separators.size() - 1)); + } + }, menupath); + + addMenuCommand("Toggle sidebar visibility", new ScheduledCommand() { + @Override + public void execute() { + grid.setSidebarOpen(!grid.isSidebarOpen()); + } + }, menupath); + } + + private MenuItem createSidebarMenuItem(final int index) { + final MenuItem menuItem = new MenuItem("Custom menu item " + index, + new ScheduledCommand() { + @Override + public void execute() { + if (index % 2 == 0) { + grid.setSidebarOpen(false); + } + getLogger().info("Menu item " + index + " selected"); + } + }); + return menuItem; + } } -- cgit v1.2.3 From 98f22b3a664034b655c08f7c20dbe4219052865b Mon Sep 17 00:00:00 2001 From: Pekka Hyvönen Date: Thu, 23 Apr 2015 14:29:25 +0300 Subject: Fixed Grid details row height regression and refactored tests (#17423) Fixed regression caused by initial #17423 change Refactored tests for Grid's details row and added @TestCategory("grid"). Change-Id: I0b68eb7d6650d16700104f76b00972483d615855 --- client/src/com/vaadin/client/widgets/Grid.java | 2 +- .../tests/components/grid/GridDetailsLocation.java | 121 +++++++++ .../components/grid/GridDetailsLocationTest.java | 300 +++++++++++++++++++++ .../grid/GridScrollToRowWithDetails.java | 133 --------- .../grid/GridScrollToRowWithDetailsTest.java | 224 --------------- 5 files changed, 422 insertions(+), 358 deletions(-) create mode 100644 uitest/src/com/vaadin/tests/components/grid/GridDetailsLocation.java create mode 100644 uitest/src/com/vaadin/tests/components/grid/GridDetailsLocationTest.java delete mode 100644 uitest/src/com/vaadin/tests/components/grid/GridScrollToRowWithDetails.java delete mode 100644 uitest/src/com/vaadin/tests/components/grid/GridScrollToRowWithDetailsTest.java (limited to 'client/src') diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index 005532a849..08a86fe6a7 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -2901,7 +2901,7 @@ public class Grid extends ResizeComposite implements * re-measure it to make sure that it's the correct height. */ double measuredHeight = WidgetUtil - .getRequiredHeightBoundingClientRectDouble(spacerElement); + .getRequiredHeightBoundingClientRectDouble(element); assert getElement().isOrHasChild(spacerElement) : "The spacer element wasn't in the DOM during measurement, but was assumed to be."; spacerHeight = measuredHeight; } diff --git a/uitest/src/com/vaadin/tests/components/grid/GridDetailsLocation.java b/uitest/src/com/vaadin/tests/components/grid/GridDetailsLocation.java new file mode 100644 index 0000000000..2880df44b0 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/grid/GridDetailsLocation.java @@ -0,0 +1,121 @@ +/* + * 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; + +import com.vaadin.annotations.Theme; +import com.vaadin.data.Property.ValueChangeEvent; +import com.vaadin.data.Property.ValueChangeListener; +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.util.Person; +import com.vaadin.tests.util.PersonContainer; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.CheckBox; +import com.vaadin.ui.Component; +import com.vaadin.ui.Grid; +import com.vaadin.ui.Grid.DetailsGenerator; +import com.vaadin.ui.Grid.RowReference; +import com.vaadin.ui.Grid.SelectionMode; +import com.vaadin.ui.Label; +import com.vaadin.ui.Layout; +import com.vaadin.ui.TextField; +import com.vaadin.ui.UI; +import com.vaadin.ui.VerticalLayout; +import com.vaadin.ui.themes.ValoTheme; + +@Theme(ValoTheme.THEME_NAME) +public class GridDetailsLocation extends UI { + + private final DetailsGenerator detailsGenerator = new DetailsGenerator() { + @Override + public Component getDetails(RowReference rowReference) { + Person person = (Person) rowReference.getItemId(); + Label label = new Label(person.getFirstName() + " " + + person.getLastName()); + // currently the decorator row doesn't change its height when the + // content height is different. + label.setHeight("30px"); + return label; + } + }; + + private TextField numberTextField; + private Grid grid; + + @Override + protected void init(VaadinRequest request) { + + Layout layout = new VerticalLayout(); + + grid = new Grid(PersonContainer.createWithTestData(1000)); + grid.setSelectionMode(SelectionMode.NONE); + layout.addComponent(grid); + + final CheckBox checkbox = new CheckBox("Details generator"); + checkbox.addValueChangeListener(new ValueChangeListener() { + @Override + @SuppressWarnings("boxing") + public void valueChange(ValueChangeEvent event) { + if (checkbox.getValue()) { + grid.setDetailsGenerator(detailsGenerator); + } else { + grid.setDetailsGenerator(DetailsGenerator.NULL); + } + } + }); + layout.addComponent(checkbox); + + numberTextField = new TextField("Row"); + numberTextField.setImmediate(true); + layout.addComponent(numberTextField); + + layout.addComponent(new Button("Toggle and scroll", + new Button.ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + toggle(); + scrollTo(); + } + })); + layout.addComponent(new Button("Scroll and toggle", + new Button.ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + scrollTo(); + toggle(); + } + })); + + setContent(layout); + } + + private void toggle() { + Object itemId = getItemId(); + boolean isVisible = grid.isDetailsVisible(itemId); + grid.setDetailsVisible(itemId, !isVisible); + } + + private void scrollTo() { + grid.scrollTo(getItemId()); + } + + private Object getItemId() { + int row = Integer.parseInt(numberTextField.getValue()); + Object itemId = grid.getContainerDataSource().getIdByIndex(row); + return itemId; + } + +} diff --git a/uitest/src/com/vaadin/tests/components/grid/GridDetailsLocationTest.java b/uitest/src/com/vaadin/tests/components/grid/GridDetailsLocationTest.java new file mode 100644 index 0000000000..06e79ac509 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/grid/GridDetailsLocationTest.java @@ -0,0 +1,300 @@ +/* + * 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; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.JavascriptExecutor; +import org.openqa.selenium.Keys; +import org.openqa.selenium.StaleElementReferenceException; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.ui.ExpectedCondition; + +import com.vaadin.testbench.TestBenchElement; +import com.vaadin.testbench.elements.ButtonElement; +import com.vaadin.testbench.elements.CheckBoxElement; +import com.vaadin.testbench.elements.GridElement.GridRowElement; +import com.vaadin.testbench.elements.TextFieldElement; +import com.vaadin.testbench.parallel.TestCategory; +import com.vaadin.tests.components.grid.basicfeatures.element.CustomGridElement; +import com.vaadin.tests.tb3.MultiBrowserTest; + +@TestCategory("grid") +public class GridDetailsLocationTest extends MultiBrowserTest { + + private static final int decoratorDefaultHeight = 50; + private static final int decoratorDefinedHeight = 30; + + private static class Param { + private final int rowIndex; + private final boolean useGenerator; + private final boolean scrollFirstToBottom; + + public Param(int rowIndex, boolean useGenerator, + boolean scrollFirstToBottom) { + this.rowIndex = rowIndex; + this.useGenerator = useGenerator; + this.scrollFirstToBottom = scrollFirstToBottom; + } + + public int getRowIndex() { + return rowIndex; + } + + public boolean useGenerator() { + return useGenerator; + } + + public boolean scrollFirstToBottom() { + return scrollFirstToBottom; + } + + @Override + public String toString() { + return "Param [rowIndex=" + getRowIndex() + ", useGenerator=" + + useGenerator() + ", scrollFirstToBottom=" + + scrollFirstToBottom() + "]"; + } + + } + + public static Collection parameters() { + List data = new ArrayList(); + + int[][] params = new int[][] {// @formatter:off + // row, top+noGen, top+gen + { 0, decoratorDefaultHeight, decoratorDefinedHeight }, + { 500, decoratorDefaultHeight, decoratorDefinedHeight }, + { 999, decoratorDefaultHeight, decoratorDefinedHeight}, + }; + // @formatter:on + + for (int i[] : params) { + int rowIndex = i[0]; + + data.add(new Param(rowIndex, false, false)); + data.add(new Param(rowIndex, true, false)); + data.add(new Param(rowIndex, false, true)); + data.add(new Param(rowIndex, true, true)); + } + + return data; + } + + @Before + public void setUp() { + setDebug(true); + } + + @Test + public void toggleAndScroll() throws Throwable { + for (Param param : parameters()) { + try { + openTestURL(); + useGenerator(param.useGenerator()); + scrollToBottom(param.scrollFirstToBottom()); + + // the tested method + toggleAndScroll(param.getRowIndex()); + + verifyLocation(param); + } catch (Throwable t) { + throw new Throwable("" + param, t); + } + } + } + + @Test + public void scrollAndToggle() throws Throwable { + for (Param param : parameters()) { + try { + openTestURL(); + useGenerator(param.useGenerator()); + scrollToBottom(param.scrollFirstToBottom()); + + // the tested method + scrollAndToggle(param.getRowIndex()); + + verifyLocation(param); + + } catch (Throwable t) { + throw new Throwable("" + param, t); + } + } + } + + @Test + public void testDecoratorHeightWithNoGenerator() { + openTestURL(); + toggleAndScroll(5); + + verifyDetailsRowHeight(5, decoratorDefaultHeight); + } + + @Test + public void testDecoratorHeightWithGenerator() { + openTestURL(); + useGenerator(true); + toggleAndScroll(5); + + verifyDetailsRowHeight(5, decoratorDefinedHeight); + } + + private void verifyDetailsRowHeight(int rowIndex, int expectedHeight) { + waitForDetailsVisible(); + WebElement details = getDetailsElement(); + Assert.assertEquals("Wrong details row height", expectedHeight, details + .getSize().getHeight()); + } + + private void verifyLocation(Param param) { + Assert.assertFalse("Notification was present", + isElementPresent(By.className("v-Notification"))); + + TestBenchElement headerRow = getGrid().getHeaderRow(0); + final int topBoundary = headerRow.getLocation().getX() + + headerRow.getSize().height; + final int bottomBoundary = getGrid().getLocation().getX() + + getGrid().getSize().getHeight() + - getHorizontalScrollbar().getSize().height; + + GridRowElement row = getGrid().getRow(param.getRowIndex()); + final int rowTop = row.getLocation().getX(); + + waitForDetailsVisible(); + WebElement details = getDetailsElement(); + final int detailsBottom = details.getLocation().getX() + + details.getSize().getHeight(); + + assertGreaterOrEqual("Row top should be inside grid, gridTop:" + + topBoundary + " rowTop" + rowTop, topBoundary, rowTop); + assertLessThanOrEqual( + "Decorator bottom should be inside grid, gridBottom:" + + bottomBoundary + " decoratorBotton:" + detailsBottom, + detailsBottom, bottomBoundary); + + Assert.assertFalse("Notification was present", + isElementPresent(By.className("v-Notification"))); + } + + private final By locator = By.className("v-grid-spacer"); + + private WebElement getDetailsElement() { + return findElement(locator); + } + + private void waitForDetailsVisible() { + waitUntil(new ExpectedCondition() { + + @Override + public WebElement apply(WebDriver driver) { + try { + WebElement detailsElement = getDetailsElement(); + return detailsElement.isDisplayed() + && detailsElement.getSize().getHeight() > 3 ? detailsElement + : null; + } catch (StaleElementReferenceException e) { + return null; + } + } + + @Override + public String toString() { + return "visibility of element located by " + locator; + } + + }, 5); + waitForElementVisible(By.className("v-grid-spacer")); + } + + private void scrollToBottom(boolean scrollFirstToBottom) { + if (scrollFirstToBottom) { + executeScript("arguments[0].scrollTop = 9999999", + getVerticalScrollbar()); + } + } + + private void useGenerator(boolean use) { + CheckBoxElement checkBox = $(CheckBoxElement.class).first(); + boolean isChecked = isCheckedValo(checkBox); + if (use != isChecked) { + clickValo(checkBox); + } + } + + @SuppressWarnings("boxing") + private boolean isCheckedValo(CheckBoxElement checkBoxElement) { + WebElement checkbox = checkBoxElement.findElement(By.tagName("input")); + Object value = executeScript("return arguments[0].checked;", checkbox); + return (Boolean) value; + } + + private void clickValo(CheckBoxElement checkBoxElement) { + checkBoxElement.findElement(By.tagName("label")).click(); + } + + private Object executeScript(String string, Object... param) { + return ((JavascriptExecutor) getDriver()).executeScript(string, param); + } + + private void scrollAndToggle(int row) { + setRow(row); + getScrollAndToggle().click(); + } + + private void toggleAndScroll(int row) { + setRow(row); + getToggleAndScroll().click(); + } + + private ButtonElement getScrollAndToggle() { + return $(ButtonElement.class).caption("Scroll and toggle").first(); + } + + private ButtonElement getToggleAndScroll() { + return $(ButtonElement.class).caption("Toggle and scroll").first(); + } + + private void setRow(int row) { + $(TextFieldElement.class).first().clear(); + $(TextFieldElement.class).first().sendKeys(String.valueOf(row), + Keys.ENTER, Keys.TAB); + } + + private CustomGridElement getGrid() { + return $(CustomGridElement.class).first(); + } + + private WebElement getVerticalScrollbar() { + WebElement scrollBar = getGrid().findElement( + By.className("v-grid-scroller-vertical")); + return scrollBar; + } + + private WebElement getHorizontalScrollbar() { + WebElement scrollBar = getGrid().findElement( + By.className("v-grid-scroller-horizontal")); + return scrollBar; + } + +} diff --git a/uitest/src/com/vaadin/tests/components/grid/GridScrollToRowWithDetails.java b/uitest/src/com/vaadin/tests/components/grid/GridScrollToRowWithDetails.java deleted file mode 100644 index 5659f01bdd..0000000000 --- a/uitest/src/com/vaadin/tests/components/grid/GridScrollToRowWithDetails.java +++ /dev/null @@ -1,133 +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; - -import com.vaadin.annotations.Theme; -import com.vaadin.data.Property.ValueChangeEvent; -import com.vaadin.data.Property.ValueChangeListener; -import com.vaadin.server.VaadinRequest; -import com.vaadin.tests.util.Person; -import com.vaadin.tests.util.PersonContainer; -import com.vaadin.ui.Button; -import com.vaadin.ui.Button.ClickEvent; -import com.vaadin.ui.CheckBox; -import com.vaadin.ui.Component; -import com.vaadin.ui.Grid; -import com.vaadin.ui.Grid.DetailsGenerator; -import com.vaadin.ui.Grid.RowReference; -import com.vaadin.ui.Grid.SelectionMode; -import com.vaadin.ui.Label; -import com.vaadin.ui.Layout; -import com.vaadin.ui.TextField; -import com.vaadin.ui.UI; -import com.vaadin.ui.VerticalLayout; -import com.vaadin.ui.themes.ValoTheme; - -@Theme(ValoTheme.THEME_NAME) -public class GridScrollToRowWithDetails extends UI { - - private final DetailsGenerator detailsGenerator = new DetailsGenerator() { - @Override - public Component getDetails(RowReference rowReference) { - Person person = (Person) rowReference.getItemId(); - Label label = new Label(person.getFirstName() + " " - + person.getLastName()); - label.setHeight("30px"); - return label; - } - }; - - private TextField numberTextField; - private Grid grid; - - @Override - protected void init(VaadinRequest request) { - - Layout layout = new VerticalLayout(); - - grid = new Grid(PersonContainer.createWithTestData(1000)); - grid.setSelectionMode(SelectionMode.NONE); - layout.addComponent(grid); - - final CheckBox checkbox = new CheckBox("Details generator"); - checkbox.addValueChangeListener(new ValueChangeListener() { - @Override - @SuppressWarnings("boxing") - public void valueChange(ValueChangeEvent event) { - if (checkbox.getValue()) { - grid.setDetailsGenerator(detailsGenerator); - } else { - grid.setDetailsGenerator(DetailsGenerator.NULL); - } - } - }); - layout.addComponent(checkbox); - - numberTextField = new TextField("Row"); - numberTextField.setImmediate(false); - layout.addComponent(numberTextField); - - layout.addComponent(new Button("Toggle", new Button.ClickListener() { - @Override - public void buttonClick(ClickEvent event) { - toggle(); - } - })); - - layout.addComponent(new Button("Scroll to", new Button.ClickListener() { - @Override - public void buttonClick(ClickEvent event) { - scrollTo(); - } - })); - - layout.addComponent(new Button("Toggle and scroll", - new Button.ClickListener() { - @Override - public void buttonClick(ClickEvent event) { - toggle(); - scrollTo(); - } - })); - layout.addComponent(new Button("Scroll and toggle", - new Button.ClickListener() { - @Override - public void buttonClick(ClickEvent event) { - scrollTo(); - toggle(); - } - })); - - setContent(layout); - } - - private void toggle() { - Object itemId = getItemId(); - boolean isVisible = grid.isDetailsVisible(itemId); - grid.setDetailsVisible(itemId, !isVisible); - } - - private void scrollTo() { - grid.scrollTo(getItemId()); - } - - private Object getItemId() { - int row = Integer.parseInt(numberTextField.getValue()); - Object itemId = grid.getContainerDataSource().getIdByIndex(row); - return itemId; - } - -} diff --git a/uitest/src/com/vaadin/tests/components/grid/GridScrollToRowWithDetailsTest.java b/uitest/src/com/vaadin/tests/components/grid/GridScrollToRowWithDetailsTest.java deleted file mode 100644 index b6ecd3f6e2..0000000000 --- a/uitest/src/com/vaadin/tests/components/grid/GridScrollToRowWithDetailsTest.java +++ /dev/null @@ -1,224 +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; - -import static org.junit.Assert.assertTrue; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import org.junit.Before; -import org.junit.Test; -import org.openqa.selenium.By; -import org.openqa.selenium.JavascriptExecutor; -import org.openqa.selenium.WebElement; - -import com.vaadin.shared.ui.grid.Range; -import com.vaadin.testbench.elements.ButtonElement; -import com.vaadin.testbench.elements.CheckBoxElement; -import com.vaadin.testbench.elements.TextFieldElement; -import com.vaadin.tests.components.grid.basicfeatures.element.CustomGridElement; -import com.vaadin.tests.tb3.MultiBrowserTest; - -public class GridScrollToRowWithDetailsTest extends MultiBrowserTest { - - private static class Param { - private final int rowIndex; - private final boolean useGenerator; - private final boolean scrollFirstToBottom; - private final int scrollTarget; - - public Param(int rowIndex, boolean useGenerator, - boolean scrollFirstToBottom, int scrollTarget) { - this.rowIndex = rowIndex; - this.useGenerator = useGenerator; - this.scrollFirstToBottom = scrollFirstToBottom; - this.scrollTarget = Math.max(0, scrollTarget); - } - - public int getRowIndex() { - return rowIndex; - } - - public boolean useGenerator() { - return useGenerator; - } - - public boolean scrollFirstToBottom() { - return scrollFirstToBottom; - } - - public int getScrollTarget() { - return scrollTarget; - } - - @Override - public String toString() { - return "Param [rowIndex=" + getRowIndex() + ", useGenerator=" - + useGenerator() + ", scrollFirstToBottom=" - + scrollFirstToBottom() + ", scrollTarget=" - + getScrollTarget() + "]"; - } - } - - public static Collection parameters() { - List data = new ArrayList(); - - int[][] params = new int[][] {// @formatter:off - // row, top+noGen, top+gen, bot+noGen, bot+gen - { 0, 0, 0, 0, 0 }, - { 500, 18741, 18723, 19000, 19000 }, - { 999, 37703, 37685, 37703, 37685 }, - }; - // @formatter:on - - for (int i[] : params) { - int rowIndex = i[0]; - int targetTopScrollWithoutGenerator = i[1]; - int targetTopScrollWithGenerator = i[2]; - int targetBottomScrollWithoutGenerator = i[3]; - int targetBottomScrollWithGenerator = i[4]; - - data.add(new Param(rowIndex, false, false, - targetTopScrollWithoutGenerator)); - data.add(new Param(rowIndex, true, false, - targetTopScrollWithGenerator)); - data.add(new Param(rowIndex, false, true, - targetBottomScrollWithoutGenerator)); - data.add(new Param(rowIndex, true, true, - targetBottomScrollWithGenerator)); - } - - return data; - } - - @Before - public void setUp() { - setDebug(true); - } - - @Test - public void toggleAndScroll() throws Throwable { - for (Param param : parameters()) { - try { - openTestURL(); - useGenerator(param.useGenerator()); - scrollToBottom(param.scrollFirstToBottom()); - - // the tested method - toggleAndScroll(param.getRowIndex()); - - Range allowedRange = Range.withLength( - param.getScrollTarget() - 5, 10); - assertTrue( - allowedRange + " does not contain " + getScrollTop(), - allowedRange.contains(getScrollTop())); - } catch (Throwable t) { - throw new Throwable("" + param, t); - } - } - } - - @Test - public void scrollAndToggle() throws Throwable { - for (Param param : parameters()) { - try { - openTestURL(); - useGenerator(param.useGenerator()); - scrollToBottom(param.scrollFirstToBottom()); - - // the tested method - scrollAndToggle(param.getRowIndex()); - - Range allowedRange = Range.withLength( - param.getScrollTarget() - 5, 10); - assertTrue( - allowedRange + " does not contain " + getScrollTop(), - allowedRange.contains(getScrollTop())); - } catch (Throwable t) { - throw new Throwable("" + param, t); - } - } - } - - private void scrollToBottom(boolean scrollFirstToBottom) { - if (scrollFirstToBottom) { - executeScript("arguments[0].scrollTop = 9999999", - getVerticalScrollbar()); - } - } - - private void useGenerator(boolean use) { - CheckBoxElement checkBox = $(CheckBoxElement.class).first(); - boolean isChecked = isCheckedValo(checkBox); - if (use != isChecked) { - clickValo(checkBox); - } - } - - @SuppressWarnings("boxing") - private boolean isCheckedValo(CheckBoxElement checkBoxElement) { - WebElement checkbox = checkBoxElement.findElement(By.tagName("input")); - Object value = executeScript("return arguments[0].checked;", checkbox); - return (Boolean) value; - } - - private void clickValo(CheckBoxElement checkBoxElement) { - checkBoxElement.findElement(By.tagName("label")).click(); - } - - private Object executeScript(String string, Object... param) { - return ((JavascriptExecutor) getDriver()).executeScript(string, param); - } - - private void scrollAndToggle(int row) { - setRow(row); - getScrollAndToggle().click(); - } - - private void toggleAndScroll(int row) { - setRow(row); - getToggleAndScroll().click(); - } - - private ButtonElement getScrollAndToggle() { - return $(ButtonElement.class).caption("Scroll and toggle").first(); - } - - private ButtonElement getToggleAndScroll() { - return $(ButtonElement.class).caption("Toggle and scroll").first(); - } - - private void setRow(int row) { - $(TextFieldElement.class).first().setValue(String.valueOf(row)); - } - - private CustomGridElement getGrid() { - return $(CustomGridElement.class).first(); - } - - private int getScrollTop() { - return ((Long) executeScript("return arguments[0].scrollTop;", - getVerticalScrollbar())).intValue(); - } - - private WebElement getVerticalScrollbar() { - WebElement scrollBar = getGrid().findElement( - By.className("v-grid-scroller-vertical")); - return scrollBar; - } -} -- cgit v1.2.3 From bbf30fff168fd4a9552d23c8341e27aa1821884b Mon Sep 17 00:00:00 2001 From: Pekka Hyvönen Date: Wed, 29 Apr 2015 16:44:19 +0300 Subject: Details row decorator and border positioning and sizes (#17423) IE8 still isn't pixel perfect, but you can't have 'em all. Change-Id: I1780441f130032503d783657103066f502dce570 --- .../VAADIN/themes/base/escalator/escalator.scss | 12 +-- WebContent/VAADIN/themes/base/grid/grid.scss | 3 +- client/src/com/vaadin/client/WidgetUtil.java | 120 +++++++++++++++++++++ .../src/com/vaadin/client/widgets/Escalator.java | 60 +++++------ client/src/com/vaadin/client/widgets/Grid.java | 10 +- .../components/grid/GridDetailsLocationTest.java | 101 +++++++++++++---- .../client/GridDetailsClientTest.java | 1 - .../escalator/EscalatorSpacerTest.java | 2 +- 8 files changed, 246 insertions(+), 63 deletions(-) (limited to 'client/src') diff --git a/WebContent/VAADIN/themes/base/escalator/escalator.scss b/WebContent/VAADIN/themes/base/escalator/escalator.scss index bae95b299c..73d45854b9 100644 --- a/WebContent/VAADIN/themes/base/escalator/escalator.scss +++ b/WebContent/VAADIN/themes/base/escalator/escalator.scss @@ -136,20 +136,14 @@ .#{$primaryStyleName}-spacer { position: absolute; display: block; - + background-color: $background-color; - + > td { width: 100%; height: 100%; + @include box-sizing(border-box); } - .v-ie8 &, .v-ie9 & { - // The inline style of margin-top from the to offset the - // header's dimension is, for some strange reason, inherited into each - // contained . We need to cancel it: - - margin-top: 0; - } } } diff --git a/WebContent/VAADIN/themes/base/grid/grid.scss b/WebContent/VAADIN/themes/base/grid/grid.scss index ee503822b9..531abb1ff1 100644 --- a/WebContent/VAADIN/themes/base/grid/grid.scss +++ b/WebContent/VAADIN/themes/base/grid/grid.scss @@ -456,8 +456,9 @@ $v-grid-details-border-bottom-stripe: 1px solid darken($v-grid-row-background-co } .#{$primaryStyleName}-spacer-deco-container { + border-top: $v-grid-border-size solid transparent; // same size as table wrapper border position: relative; - top: $v-grid-border-size; + top: 0; // escalator will override top for scrolling and margin-top for header offset. z-index: 5; } diff --git a/client/src/com/vaadin/client/WidgetUtil.java b/client/src/com/vaadin/client/WidgetUtil.java index 5f88f6da46..a9cd23c841 100644 --- a/client/src/com/vaadin/client/WidgetUtil.java +++ b/client/src/com/vaadin/client/WidgetUtil.java @@ -1459,4 +1459,124 @@ public class WidgetUtil { return Logger.getLogger(WidgetUtil.class.getName()); } + /** + * Returns the thickness of the given element's top border. + *

+ * The value is determined using computed style when available and + * calculated otherwise. + * + * @since + * @param element + * the element to measure + * @return the top border thickness + */ + public static double getBorderTopThickness(Element element) { + return getBorderThickness(element, new String[] { "borderTopWidth" }); + } + + /** + * Returns the thickness of the given element's bottom border. + *

+ * The value is determined using computed style when available and + * calculated otherwise. + * + * @since + * @param element + * the element to measure + * @return the bottom border thickness + */ + public static double getBorderBottomThickness(Element element) { + return getBorderThickness(element, new String[] { "borderBottomWidth" }); + } + + /** + * Returns the combined thickness of the given element's top and bottom + * borders. + *

+ * The value is determined using computed style when available and + * calculated otherwise. + * + * @since + * @param element + * the element to measure + * @return the top and bottom border thickness + */ + public static double getBorderTopAndBottomThickness(Element element) { + return getBorderThickness(element, new String[] { "borderTopWidth", + "borderBottomWidth" }); + } + + /** + * Returns the thickness of the given element's left border. + *

+ * The value is determined using computed style when available and + * calculated otherwise. + * + * @since + * @param element + * the element to measure + * @return the left border thickness + */ + public static double getBorderLeftThickness(Element element) { + return getBorderThickness(element, new String[] { "borderLeftWidth" }); + } + + /** + * Returns the thickness of the given element's right border. + *

+ * The value is determined using computed style when available and + * calculated otherwise. + * + * @since + * @param element + * the element to measure + * @return the right border thickness + */ + public static double getBorderRightThickness(Element element) { + return getBorderThickness(element, new String[] { "borderRightWidth" }); + } + + /** + * Returns the thickness of the given element's left and right borders. + *

+ * The value is determined using computed style when available and + * calculated otherwise. + * + * @since + * @param element + * the element to measure + * @return the top border thickness + */ + public static double getBorderLeftAndRightThickness(Element element) { + return getBorderThickness(element, new String[] { "borderLeftWidth", + "borderRightWidth" }); + } + + private static native double getBorderThickness( + com.google.gwt.dom.client.Element element, String[] borderNames) + /*-{ + if (typeof $wnd.getComputedStyle === 'function') { + var computedStyle = $wnd.getComputedStyle(element); + var width = 0; + for (i=0; i< borderNames.length; i++) { + var borderWidth = computedStyle[borderNames[i]]; + width += parseFloat(borderWidth); + } + return width; + } else { + var parentElement = element.offsetParent; + var cloneElement = element.cloneNode(false); + cloneElement.style.boxSizing ="content-box"; + parentElement.appendChild(cloneElement); + cloneElement.style.height = "10px"; // IE8 wants the height to be set to something... + var heightWithBorder = cloneElement.offsetHeight; + for (i=0; i< borderNames.length; i++) { + cloneElement.style[borderNames[i]] = "0"; + } + var heightWithoutBorder = cloneElement.offsetHeight; + parentElement.removeChild(cloneElement); + + return heightWithBorder - heightWithoutBorder; + } + }-*/; } diff --git a/client/src/com/vaadin/client/widgets/Escalator.java b/client/src/com/vaadin/client/widgets/Escalator.java index 17236c5e30..0cd59ce7ed 100644 --- a/client/src/com/vaadin/client/widgets/Escalator.java +++ b/client/src/com/vaadin/client/widgets/Escalator.java @@ -4580,6 +4580,7 @@ public class Escalator extends Widget implements RequiresResize, private double height = -1; private boolean domHasBeenSetup = false; private double decoHeight; + private double defaultCellBorderBottomSize = -1; public SpacerImpl(int rowIndex) { this.rowIndex = rowIndex; @@ -4620,7 +4621,12 @@ public class Escalator extends Widget implements RequiresResize, public void setPosition(double x, double y) { positions.set(getRootElement(), x, y); - positions.set(getDecoElement(), 0, y); + positions + .set(getDecoElement(), 0, y - getSpacerDecoTopOffset()); + } + + private double getSpacerDecoTopOffset() { + return getBody().getDefaultRowHeight(); } public void setStylePrimaryName(String style) { @@ -4637,7 +4643,18 @@ public class Escalator extends Widget implements RequiresResize, final double oldHeight = this.height; this.height = height; - root.getStyle().setHeight(height, Unit.PX); + + // since the spacer might be rendered on top of the previous + // rows border (done with css), need to increase height the + // amount of the border thickness + if (defaultCellBorderBottomSize < 0) { + defaultCellBorderBottomSize = WidgetUtil + .getBorderBottomThickness(body.getRowElement( + getVisibleRowRange().getStart()) + .getFirstChildElement()); + } + root.getStyle().setHeight(height + defaultCellBorderBottomSize, + Unit.PX); // move the visible spacers getRow row onwards. shiftSpacerPositionsAfterRow(getRow(), heightDiff); @@ -4722,34 +4739,9 @@ public class Escalator extends Widget implements RequiresResize, private void updateDecoratorGeometry(double detailsHeight) { Style style = deco.getStyle(); decoHeight = detailsHeight + getBody().getDefaultRowHeight(); - - style.setTop( - -(getBody().getDefaultRowHeight() - getBorderTopHeight(getElement())), - Unit.PX); style.setHeight(decoHeight, Unit.PX); } - private native double getBorderTopHeight(Element spacerCell) - /*-{ - if (typeof $wnd.getComputedStyle === 'function') { - var computedStyle = $wnd.getComputedStyle(spacerCell); - var borderTopWidth = computedStyle['borderTopWidth']; - var width = parseFloat(borderTopWidth); - return width; - } else { - var spacerRow = spacerCell.offsetParent; - var cloneCell = spacerCell.cloneNode(false); - spacerRow.appendChild(cloneCell); - cloneCell.style.height = "10px"; // IE8 wants the height to be set to something... - var heightWithBorder = cloneCell.offsetHeight; - cloneCell.style.borderTopWidth = "0"; - var heightWithoutBorder = cloneCell.offsetHeight; - spacerRow.removeChild(cloneCell); - - return heightWithBorder - heightWithoutBorder; - } - }-*/; - @Override public Element getElement() { return spacerElement; @@ -4807,10 +4799,12 @@ public class Escalator extends Widget implements RequiresResize, public void show() { getRootElement().getStyle().clearDisplay(); + getDecoElement().getStyle().clearDisplay(); } public void hide() { getRootElement().getStyle().setDisplay(Display.NONE); + getDecoElement().getStyle().setDisplay(Display.NONE); } /** @@ -4832,9 +4826,10 @@ public class Escalator extends Widget implements RequiresResize, final double topClip = Math.max(0.0D, bodyTop - top); final double bottomClip = decoHeight - Math.max(0.0D, bottom - bodyBottom); + // TODO [optimize] not sure how GWT compiles this final String clip = new StringBuilder("rect(") .append(topClip).append("px,").append(decoWidth) - .append("px,").append(bottomClip).append("px,0") + .append("px,").append(bottomClip).append("px,0)") .toString(); deco.getStyle().setProperty("clip", clip); } else { @@ -5258,16 +5253,21 @@ public class Escalator extends Widget implements RequiresResize, spacerScrollerRegistration = addScrollHandler(spacerScroller); } - SpacerImpl spacer = new SpacerImpl(rowIndex); + final SpacerImpl spacer = new SpacerImpl(rowIndex); rowIndexToSpacer.put(rowIndex, spacer); - spacer.setPosition(getScrollLeft(), calculateSpacerTop(rowIndex)); + // set the position before adding it to DOM + positions.set(spacer.getRootElement(), getScrollLeft(), + calculateSpacerTop(rowIndex)); TableRowElement spacerRoot = spacer.getRootElement(); spacerRoot.getStyle().setWidth( columnConfiguration.calculateRowWidth(), Unit.PX); body.getElement().appendChild(spacerRoot); spacer.setupDom(height); + // set the deco position, requires that spacer is in the DOM + positions.set(spacer.getDecoElement(), 0, + spacer.getTop() - spacer.getSpacerDecoTopOffset()); spacerDecoContainer.appendChild(spacer.getDecoElement()); if (spacerDecoContainer.getParentElement() == null) { diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index 08a86fe6a7..4951997995 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -2899,9 +2899,17 @@ public class Grid extends ResizeComposite implements /* * Once we have the content properly inside the DOM, we should * re-measure it to make sure that it's the correct height. + * + * This is rather tricky, since the row (tr) will get the + * height, but the spacer cell (td) has the borders, which + * should go on top of the previous row and next row. */ - double measuredHeight = WidgetUtil + double requiredHeightBoundingClientRectDouble = WidgetUtil .getRequiredHeightBoundingClientRectDouble(element); + double borderTopAndBottomHeight = WidgetUtil + .getBorderTopAndBottomThickness(spacerElement); + double measuredHeight = requiredHeightBoundingClientRectDouble + + borderTopAndBottomHeight; assert getElement().isOrHasChild(spacerElement) : "The spacer element wasn't in the DOM during measurement, but was assumed to be."; spacerHeight = measuredHeight; } diff --git a/uitest/src/com/vaadin/tests/components/grid/GridDetailsLocationTest.java b/uitest/src/com/vaadin/tests/components/grid/GridDetailsLocationTest.java index 06e79ac509..c14503fb8d 100644 --- a/uitest/src/com/vaadin/tests/components/grid/GridDetailsLocationTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/GridDetailsLocationTest.java @@ -28,6 +28,7 @@ import org.openqa.selenium.Keys; import org.openqa.selenium.StaleElementReferenceException; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; +import org.openqa.selenium.remote.DesiredCapabilities; import org.openqa.selenium.support.ui.ExpectedCondition; import com.vaadin.testbench.TestBenchElement; @@ -35,6 +36,7 @@ import com.vaadin.testbench.elements.ButtonElement; import com.vaadin.testbench.elements.CheckBoxElement; import com.vaadin.testbench.elements.GridElement.GridRowElement; import com.vaadin.testbench.elements.TextFieldElement; +import com.vaadin.testbench.parallel.Browser; import com.vaadin.testbench.parallel.TestCategory; import com.vaadin.tests.components.grid.basicfeatures.element.CustomGridElement; import com.vaadin.tests.tb3.MultiBrowserTest; @@ -42,8 +44,9 @@ import com.vaadin.tests.tb3.MultiBrowserTest; @TestCategory("grid") public class GridDetailsLocationTest extends MultiBrowserTest { - private static final int decoratorDefaultHeight = 50; - private static final int decoratorDefinedHeight = 30; + private static final int detailsDefaultHeight = 51; + private static final int detailsDefinedHeight = 33; + private static final int detailsDefinedHeightIE8 = 31; private static class Param { private final int rowIndex; @@ -81,16 +84,9 @@ public class GridDetailsLocationTest extends MultiBrowserTest { public static Collection parameters() { List data = new ArrayList(); - int[][] params = new int[][] {// @formatter:off - // row, top+noGen, top+gen - { 0, decoratorDefaultHeight, decoratorDefinedHeight }, - { 500, decoratorDefaultHeight, decoratorDefinedHeight }, - { 999, decoratorDefaultHeight, decoratorDefinedHeight}, - }; - // @formatter:on + int[] params = new int[] { 0, 500, 999 }; - for (int i[] : params) { - int rowIndex = i[0]; + for (int rowIndex : params) { data.add(new Param(rowIndex, false, false)); data.add(new Param(rowIndex, true, false)); @@ -144,29 +140,72 @@ public class GridDetailsLocationTest extends MultiBrowserTest { } @Test - public void testDecoratorHeightWithNoGenerator() { + public void testDetailsHeightWithNoGenerator() { openTestURL(); toggleAndScroll(5); - verifyDetailsRowHeight(5, decoratorDefaultHeight); + verifyDetailsRowHeight(5, detailsDefaultHeight, 0); + verifyDetailsDecoratorLocation(5, 0, 0); + + toggleAndScroll(0); + + verifyDetailsRowHeight(0, detailsDefaultHeight, 0); + verifyDetailsDecoratorLocation(0, 0, 1); + + verifyDetailsRowHeight(5, detailsDefaultHeight, 1); + verifyDetailsDecoratorLocation(5, 1, 0); } @Test - public void testDecoratorHeightWithGenerator() { + public void testDetailsHeightWithGenerator() { openTestURL(); useGenerator(true); toggleAndScroll(5); - verifyDetailsRowHeight(5, decoratorDefinedHeight); + verifyDetailsRowHeight(5, getDefinedHeight(), 0); + verifyDetailsDecoratorLocation(5, 0, 0); + + toggleAndScroll(0); + + verifyDetailsRowHeight(0, getDefinedHeight(), 0); + // decorator elements are in DOM in the order they have been added + verifyDetailsDecoratorLocation(0, 0, 1); + + verifyDetailsRowHeight(5, getDefinedHeight(), 1); + verifyDetailsDecoratorLocation(5, 1, 0); + } + + private int getDefinedHeight() { + boolean ie8 = isIE8(); + return ie8 ? detailsDefinedHeightIE8 : detailsDefinedHeight; } - private void verifyDetailsRowHeight(int rowIndex, int expectedHeight) { + private void verifyDetailsRowHeight(int rowIndex, int expectedHeight, + int visibleIndexOfSpacer) { waitForDetailsVisible(); - WebElement details = getDetailsElement(); + WebElement details = getDetailsElement(visibleIndexOfSpacer); Assert.assertEquals("Wrong details row height", expectedHeight, details .getSize().getHeight()); } + private void verifyDetailsDecoratorLocation(int row, + int visibleIndexOfSpacer, int visibleIndexOfDeco) { + WebElement detailsElement = getDetailsElement(visibleIndexOfSpacer); + WebElement detailsDecoElement = getDetailsDecoElement(visibleIndexOfDeco); + GridRowElement rowElement = getGrid().getRow(row); + + Assert.assertEquals( + "Details deco top position does not match row top pos", + rowElement.getLocation().getY(), detailsDecoElement + .getLocation().getY()); + Assert.assertEquals( + "Details deco bottom position does not match details bottom pos", + detailsElement.getLocation().getY() + + detailsElement.getSize().getHeight(), + detailsDecoElement.getLocation().getY() + + detailsDecoElement.getSize().getHeight()); + } + private void verifyLocation(Param param) { Assert.assertFalse("Notification was present", isElementPresent(By.className("v-Notification"))); @@ -193,6 +232,11 @@ public class GridDetailsLocationTest extends MultiBrowserTest { + bottomBoundary + " decoratorBotton:" + detailsBottom, detailsBottom, bottomBoundary); + verifyDetailsRowHeight(param.getRowIndex(), + param.useGenerator() ? getDefinedHeight() + : detailsDefaultHeight, 0); + verifyDetailsDecoratorLocation(param.getRowIndex(), 0, 0); + Assert.assertFalse("Notification was present", isElementPresent(By.className("v-Notification"))); } @@ -200,7 +244,15 @@ public class GridDetailsLocationTest extends MultiBrowserTest { private final By locator = By.className("v-grid-spacer"); private WebElement getDetailsElement() { - return findElement(locator); + return getDetailsElement(0); + } + + private WebElement getDetailsElement(int index) { + return findElements(locator).get(index); + } + + private WebElement getDetailsDecoElement(int index) { + return findElements(By.className("v-grid-spacer-deco")).get(index); } private void waitForDetailsVisible() { @@ -242,6 +294,16 @@ public class GridDetailsLocationTest extends MultiBrowserTest { } } + private boolean isIE8() { + DesiredCapabilities desiredCapabilities = getDesiredCapabilities(); + DesiredCapabilities ie8Capabilities = Browser.IE8 + .getDesiredCapabilities(); + return desiredCapabilities.getBrowserName().equals( + ie8Capabilities.getBrowserName()) + && desiredCapabilities.getVersion().equals( + ie8Capabilities.getVersion()); + } + @SuppressWarnings("boxing") private boolean isCheckedValo(CheckBoxElement checkBoxElement) { WebElement checkbox = checkBoxElement.findElement(By.tagName("input")); @@ -296,5 +358,4 @@ public class GridDetailsLocationTest extends MultiBrowserTest { By.className("v-grid-scroller-horizontal")); return scrollBar; } - -} +} \ No newline at end of file diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridDetailsClientTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridDetailsClientTest.java index 619033226c..88158c7f6f 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridDetailsClientTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridDetailsClientTest.java @@ -182,7 +182,6 @@ public class GridDetailsClientTest extends GridBasicClientFeaturesTest { getGridElement().getDetails(1); } - @Test public void rowElementClassNames() { toggleDetailsFor(0); 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 index e044c192f7..66c131255f 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/escalator/EscalatorSpacerTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/escalator/EscalatorSpacerTest.java @@ -417,7 +417,7 @@ public class EscalatorSpacerTest extends EscalatorBasicClientFeaturesTest { public void spacersAreInCorrectDomPositionAfterScroll() { selectMenuPath(FEATURES, SPACERS, ROW_1, SET_100PX); - scrollVerticallyTo(30); // roughly one row's worth + scrollVerticallyTo(32); // roughly one row's worth WebElement tbody = getEscalator().findElement(By.tagName("tbody")); WebElement spacer = getChild(tbody, 1); -- cgit v1.2.3