diff options
author | Aleksi Hietanen <aleksi@vaadin.com> | 2016-10-03 10:30:05 +0300 |
---|---|---|
committer | Vaadin Code Review <review@vaadin.com> | 2016-10-06 08:43:28 +0000 |
commit | 6abb9c5c64c60db47e8f2dde87afaf0e76b2228c (patch) | |
tree | d7831d622a019523d22e63da8ada12075b79d590 | |
parent | 45082d1abcfec7f281c986c08fe0c1f065af29f2 (diff) | |
download | vaadin-framework-6abb9c5c64c60db47e8f2dde87afaf0e76b2228c.tar.gz vaadin-framework-6abb9c5c64c60db47e8f2dde87afaf0e76b2228c.zip |
Reintroduce reordering of grid columns
Change-Id: If8a23427ef5500a0177081c4be8065d2d5a0ca4c
7 files changed, 685 insertions, 34 deletions
diff --git a/client/src/main/java/com/vaadin/client/connectors/grid/GridConnector.java b/client/src/main/java/com/vaadin/client/connectors/grid/GridConnector.java index 6596137d9d..02909e1fc3 100644 --- a/client/src/main/java/com/vaadin/client/connectors/grid/GridConnector.java +++ b/client/src/main/java/com/vaadin/client/connectors/grid/GridConnector.java @@ -20,7 +20,9 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; +import java.util.stream.Collectors; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.EventTarget; @@ -132,7 +134,7 @@ public class GridConnector * the id of the column to get * @return the column with the given id */ - public Column<?, ?> getColumn(String columnId) { + public Column<?, JsonObject> getColumn(String columnId) { return idToColumn.get(columnId); } @@ -180,6 +182,16 @@ public class GridConnector getColumnId(event.getColumn()), event.isHidden()); } }); + getWidget().addColumnReorderHandler(event -> { + if (event.isUserOriginated()) { + List<String> newColumnOrder = mapColumnsToIds( + event.getNewColumnOrder()); + List<String> oldColumnOrder = mapColumnsToIds( + event.getOldColumnOrder()); + getRpcProxy(GridServerRpc.class) + .columnsReordered(newColumnOrder, oldColumnOrder); + } + }); getWidget().addColumnResizeHandler(event -> { Column<?, JsonObject> column = event.getColumn(); getRpcProxy(GridServerRpc.class).columnResized(getColumnId(column), @@ -219,6 +231,13 @@ public class GridConnector layout(); } + @SuppressWarnings("unchecked") + @OnStateChange("columnOrder") + void updateColumnOrder() { + getWidget().setColumnOrder(getState().columnOrder.stream() + .map(this::getColumn).toArray(size -> new Column[size])); + } + /** * Updates the grid header section on state change. */ @@ -446,4 +465,9 @@ public class GridConnector } return false; } + + private List<String> mapColumnsToIds(List<Column<?, JsonObject>> columns) { + return columns.stream().map(this::getColumnId).filter(Objects::nonNull) + .collect(Collectors.toList()); + } } diff --git a/client/src/main/java/com/vaadin/client/widget/grid/events/ColumnReorderEvent.java b/client/src/main/java/com/vaadin/client/widget/grid/events/ColumnReorderEvent.java index 72c0eb5878..0063c20b28 100644 --- a/client/src/main/java/com/vaadin/client/widget/grid/events/ColumnReorderEvent.java +++ b/client/src/main/java/com/vaadin/client/widget/grid/events/ColumnReorderEvent.java @@ -15,7 +15,10 @@ */ package com.vaadin.client.widget.grid.events; +import java.util.List; + import com.google.gwt.event.shared.GwtEvent; +import com.vaadin.client.widgets.Grid.Column; /** * An event for notifying that the columns in the Grid have been reordered. @@ -37,6 +40,48 @@ public class ColumnReorderEvent<T> extends GwtEvent<ColumnReorderHandler<T>> { return TYPE; } + private final List<Column<?, T>> oldColumnOrder; + + private final List<Column<?, T>> newColumnOrder; + + private final boolean userOriginated; + + public ColumnReorderEvent(List<Column<?, T>> oldColumnOrder, + List<Column<?, T>> newColumnOrder, boolean userOriginated) { + this.oldColumnOrder = oldColumnOrder; + this.newColumnOrder = newColumnOrder; + this.userOriginated = userOriginated; + } + + /** + * Gets the ordering of columns prior to this event. + * + * @return the list of columns in the grid's order prior to this event + */ + public List<Column<?, T>> getOldColumnOrder() { + return oldColumnOrder; + } + + /** + * Gets the new ordering of columns. + * + * @return the list of columns in the grid's current order + */ + public List<Column<?, T>> getNewColumnOrder() { + return newColumnOrder; + } + + /** + * Check whether this event originated from the user reordering columns or + * via API call. + * + * @return {@code true} if columns were reordered by the user, {@code false} + * if not + */ + public boolean isUserOriginated() { + return userOriginated; + } + @SuppressWarnings({ "rawtypes", "unchecked" }) @Override public Type<ColumnReorderHandler<T>> getAssociatedType() { diff --git a/client/src/main/java/com/vaadin/client/widgets/Grid.java b/client/src/main/java/com/vaadin/client/widgets/Grid.java index eac958fdce..7d6c1369b8 100644 --- a/client/src/main/java/com/vaadin/client/widgets/Grid.java +++ b/client/src/main/java/com/vaadin/client/widgets/Grid.java @@ -4372,7 +4372,7 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>, Column<?, T>[] array = reordered .toArray(new Column[reordered.size()]); - setColumnOrder(array); + setColumnOrder(true, array); transferCellFocusOnDrop(); } // else no reordering } @@ -8171,6 +8171,12 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>, * array of columns in wanted order */ public void setColumnOrder(Column<?, T>... orderedColumns) { + setColumnOrder(false, orderedColumns); + } + + private void setColumnOrder(boolean isUserOriginated, + Column<?, T>... orderedColumns) { + List<Column<?, T>> oldOrder = new ArrayList<>(columns); ColumnConfiguration conf = getEscalator().getColumnConfiguration(); // Trigger ComplexRenderer.destroy for old content @@ -8221,7 +8227,8 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>, columnHider.updateTogglesOrder(); - fireEvent(new ColumnReorderEvent<T>()); + fireEvent(new ColumnReorderEvent<T>(oldOrder, newOrder, + isUserOriginated)); } /** diff --git a/server/src/main/java/com/vaadin/ui/Grid.java b/server/src/main/java/com/vaadin/ui/Grid.java index e2df8b437b..891c08aa47 100644 --- a/server/src/main/java/com/vaadin/ui/Grid.java +++ b/server/src/main/java/com/vaadin/ui/Grid.java @@ -17,6 +17,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; @@ -32,11 +33,14 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.function.Function; +import java.util.stream.Collectors; import java.util.stream.Stream; import com.vaadin.event.ConnectorEvent; import com.vaadin.event.ContextClickEvent; import com.vaadin.event.EventListener; +import com.vaadin.server.EncodeResult; +import com.vaadin.server.JsonCodec; import com.vaadin.server.KeyMapper; import com.vaadin.server.data.SortOrder; import com.vaadin.shared.MouseEventDetails; @@ -73,6 +77,11 @@ import elemental.json.JsonValue; public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents { @Deprecated + private static final Method COLUMN_REORDER_METHOD = ReflectTools.findMethod( + ColumnReorderListener.class, "columnReorder", + ColumnReorderEvent.class); + + @Deprecated private static final Method COLUMN_RESIZE_METHOD = ReflectTools.findMethod( ColumnResizeListener.class, "columnResize", ColumnResizeEvent.class); @@ -88,10 +97,24 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents { ColumnVisibilityChangeEvent.class); /** + * An event listener for column reorder events in the Grid. + */ + @FunctionalInterface + public interface ColumnReorderListener extends Serializable { + + /** + * Called when the columns of the grid have been reordered. + * + * @param event + * An event providing more information + */ + void columnReorder(ColumnReorderEvent event); + } + + /** * An event listener for column resize events in the Grid. - * - * @since 7.6 */ + @FunctionalInterface public interface ColumnResizeListener extends Serializable { /** @@ -104,10 +127,39 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents { } /** + * An event that is fired when the columns are reordered. + */ + public static class ColumnReorderEvent extends Component.Event { + + private final boolean userOriginated; + + /** + * + * @param source + * the grid where the event originated from + * @param userOriginated + * <code>true</code> if event is a result of user + * interaction, <code>false</code> if from API call + */ + public ColumnReorderEvent(Grid source, boolean userOriginated) { + super(source); + this.userOriginated = userOriginated; + } + + /** + * Returns <code>true</code> if the column reorder was done by the user, + * <code>false</code> if not and it was triggered by server side code. + * + * @return <code>true</code> if event is a result of user interaction + */ + public boolean isUserOriginated() { + return userOriginated; + } + } + + /** * An event that is fired when a column is resized, either programmatically * or by the user. - * - * @since 7.6 */ public static class ColumnResizeEvent extends Component.Event { @@ -324,6 +376,7 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents { * * @since 7.5.0 */ + @FunctionalInterface public interface ColumnVisibilityChangeListener extends Serializable { /** @@ -522,7 +575,39 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents { @Override public void columnsReordered(List<String> newColumnOrder, List<String> oldColumnOrder) { - // TODO Auto-generated method stub + 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()); + fireColumnReorderEvent(true); + } else { + // make sure the client is reverted to the order that the + // server thinks it is + diffState.remove(diffStateKey); + markAsDirty(); + } } @Override @@ -1433,7 +1518,7 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents { /** * Returns the cell on this row corresponding to the given column id. - * + * * @param columnId * the id of the column whose header cell to get, not null * @return the header cell @@ -1444,7 +1529,7 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents { /** * Returns the cell on this row corresponding to the given column. - * + * * @param column * the column whose header cell to get, not null * @return the header cell @@ -1463,14 +1548,14 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents { /** * Returns the textual caption of this cell. - * + * * @return the header caption */ public String getText(); /** * Sets the textual caption of this cell. - * + * * @param text * the header caption to set, not null */ @@ -1555,19 +1640,7 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents { AbstractRenderer<? super T, V> renderer) { final Column<T, V> column = new Column<>(caption, valueProvider, renderer); - final String columnId = columnKeys.key(column); - - column.extend(this); - column.setId(columnId); - columnSet.add(column); - addDataGenerator(column); - - getHeader().addColumn(columnId); - - if (getDefaultHeaderRow() != null) { - getDefaultHeaderRow().getCell(columnId).setText(caption); - } - + addColumn(column); return column; } @@ -1587,6 +1660,27 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents { return addColumn(caption, valueProvider, new TextRenderer()); } + private void addColumn(Column<T, ?> column) { + if (getColumns().contains(column)) { + return; + } + + final String columnId = columnKeys.key(column); + + column.extend(this); + column.setId(columnId); + columnSet.add(column); + addDataGenerator(column); + + getState().columnOrder.add(columnId); + getHeader().addColumn(columnId); + + if (getDefaultHeaderRow() != null) { + getDefaultHeaderRow().getCell(columnId) + .setText(column.getCaption()); + } + } + /** * Removes the given column from this {@link Grid}. * @@ -1645,8 +1739,9 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents { * * @return unmodifiable collection of columns */ - public Collection<Column<T, ?>> getColumns() { - return Collections.unmodifiableSet(columnSet); + public List<Column<T, ?>> getColumns() { + return Collections.unmodifiableList(getState(false).columnOrder.stream() + .map(this::getColumn).collect(Collectors.toList())); } /** @@ -1894,10 +1989,10 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents { * the index at which to insert the row, where the topmost row * has index zero * @return the inserted header row - * + * * @throws IndexOutOfBoundsException * if {@code rowIndex < 0 || rowIndex > getHeaderRowCount()} - * + * * @see #appendHeaderRow() * @see #prependHeaderRow() * @see #removeHeaderRow(HeaderRow) @@ -1911,7 +2006,7 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents { * Adds a new row at the bottom of the header section. * * @return the appended header row - * + * * @see #prependHeaderRow() * @see #addHeaderRowAt(int) * @see #removeHeaderRow(HeaderRow) @@ -1925,7 +2020,7 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents { * Adds a new row at the top of the header section. * * @return the prepended header row - * + * * @see #appendHeaderRow() * @see #addHeaderRowAt(int) * @see #removeHeaderRow(HeaderRow) @@ -1944,7 +2039,7 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents { * * @throws IllegalArgumentException * if the header does not contain the row - * + * * @see #removeHeaderRow(int) * @see #addHeaderRowAt(int) * @see #appendHeaderRow() @@ -1963,7 +2058,7 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents { * * @throws IndexOutOfBoundsException * if {@code index < 0 || index >= getHeaderRowCount()} - * + * * @see #removeHeaderRow(HeaderRow) * @see #addHeaderRowAt(int) * @see #appendHeaderRow() @@ -1977,7 +2072,7 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents { * Returns the current default row of the header. * * @return the default row or null if no default row set - * + * * @see #setDefaultHeaderRow(HeaderRow) */ public HeaderRow getDefaultHeaderRow() { @@ -2013,6 +2108,20 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents { } /** + * Registers a new column reorder listener. + * + * @param listener + * the listener to register, not null + * @return a registration for the listener + */ + public Registration addColumnReorderListener( + ColumnReorderListener listener) { + addListener(ColumnReorderEvent.class, listener, COLUMN_REORDER_METHOD); + return () -> removeListener(ColumnReorderEvent.class, listener, + COLUMN_REORDER_METHOD); + } + + /** * Registers a new column resize listener. * * @param listener @@ -2055,6 +2164,82 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents { COLUMN_VISIBILITY_METHOD); } + /** + * Returns whether column reordering is allowed. Default value is + * <code>false</code>. + * + * @return true if reordering is allowed + */ + public boolean isColumnReorderingAllowed() { + return getState(false).columnReorderingAllowed; + } + + /** + * Sets whether or not column reordering is allowed. Default value is + * <code>false</code>. + * + * @param columnReorderingAllowed + * specifies whether column reordering is allowed + */ + public void setColumnReorderingAllowed(boolean columnReorderingAllowed) { + if (isColumnReorderingAllowed() != columnReorderingAllowed) { + getState().columnReorderingAllowed = columnReorderingAllowed; + } + } + + /** + * Sets the columns and their order for the grid. Columns currently in this + * grid that are not present in columns are removed. Similarly, any new + * column in columns will be added to this grid. + * + * @param columns + * the columns to set + */ + public void setColumns(Column<T, ?>... columns) { + List<Column<T, ?>> currentColumns = getColumns(); + Set<Column<T, ?>> removeColumns = new HashSet<>(currentColumns); + Set<Column<T, ?>> addColumns = Arrays.stream(columns) + .collect(Collectors.toSet()); + + removeColumns.removeAll(addColumns); + removeColumns.stream().forEach(this::removeColumn); + + addColumns.removeAll(currentColumns); + addColumns.stream().forEach(this::addColumn); + + setColumnOrder(columns); + } + + /** + * 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 + * grid. + * + * @param columns + * the columns in the order they should be + */ + public void setColumnOrder(Column<T, ?>... columns) { + List<String> columnOrder = new ArrayList<>(); + for (Column<T, ?> column : columns) { + if (columnSet.contains(column)) { + columnOrder.add(column.getId()); + } else { + throw new IllegalArgumentException( + "setColumnOrder should not be called " + + "with columns that are not in the grid."); + } + } + + List<String> stateColumnOrder = getState().columnOrder; + if (stateColumnOrder.size() != columnOrder.size()) { + stateColumnOrder.removeAll(columnOrder); + columnOrder.addAll(stateColumnOrder); + } + + getState().columnOrder = columnOrder; + fireColumnReorderEvent(false); + } + @Override protected GridState getState() { return getState(true); @@ -2079,6 +2264,10 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents { } } + private void fireColumnReorderEvent(boolean userOriginated) { + fireEvent(new ColumnReorderEvent(this, userOriginated)); + } + private void fireColumnResizeEvent(Column<?, ?> column, boolean userOriginated) { fireEvent(new ColumnResizeEvent(this, column, userOriginated)); diff --git a/uitest/src/main/java/com/vaadin/tests/components/grid/basics/GridBasics.java b/uitest/src/main/java/com/vaadin/tests/components/grid/basics/GridBasics.java index f75dbd27cc..28c8a987b7 100644 --- a/uitest/src/main/java/com/vaadin/tests/components/grid/basics/GridBasics.java +++ b/uitest/src/main/java/com/vaadin/tests/components/grid/basics/GridBasics.java @@ -1,6 +1,8 @@ package com.vaadin.tests.components.grid.basics; import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; @@ -50,6 +52,35 @@ public class GridBasics extends AbstractTestUIWithLog { "Column 2", "Row Number", "Date", "HTML String", "Big Random", "Small Random" }; + private final Command toggleReorderListenerCommand = new Command() { + private Registration registration = null; + + @Override + public void menuSelected(MenuItem selectedItem) { + removeRegistration(); + if (selectedItem.isChecked()) { + registration = grid.addColumnReorderListener(event -> { + List<String> columnCaptions = new ArrayList<String>(); + for (Column<DataObject, ?> column : grid.getColumns()) { + columnCaptions.add(column.getCaption()); + } + log("Columns reordered, userOriginated: " + + event.isUserOriginated()); + log("Column order: " + columnCaptions.toString()); + }); + log("Registered a column reorder listener."); + } + } + + private void removeRegistration() { + if (registration != null) { + registration.remove(); + registration = null; + log("Removed a column reorder listener."); + } + } + }; + private static class DetailedDetailsGenerator implements DetailsGenerator<DataObject> { @@ -109,6 +140,7 @@ public class GridBasics extends AbstractTestUIWithLog { private List<DataObject> data; private int watchingCount = 0; private PersistingDetailsGenerator persistingDetails; + private List<Column<DataObject, ?>> initialColumnOrder; public GridBasics() { generators.put("NULL", null); @@ -167,9 +199,51 @@ public class GridBasics extends AbstractTestUIWithLog { createDetailsMenu(componentMenu.addItem("Details", null)); createBodyMenu(componentMenu.addItem("Body rows", null)); createHeaderMenu(componentMenu.addItem("Header", null)); + createColumnsMenu(componentMenu.addItem("Columns", null)); return menu; } + @SuppressWarnings("unchecked") + private void createColumnsMenu(MenuItem columnsMenu) { + for (int i = 0; i < grid.getColumns().size(); i++) { + final int index = i; + MenuItem columnMenu = columnsMenu.addItem("Column " + i, null); + columnMenu.addItem("Move left", selectedItem -> { + if (index > 0) { + List<Column<DataObject, ?>> columnOrder = new ArrayList<>( + grid.getColumns()); + Collections.swap(columnOrder, index, index - 1); + grid.setColumnOrder(columnOrder + .toArray(new Column[columnOrder.size()])); + } + }); + columnMenu.addItem("Move right", selectedItem -> { + if (index < grid.getColumns().size() - 1) { + List<Column<DataObject, ?>> columnOrder = new ArrayList<>( + grid.getColumns()); + Collections.swap(columnOrder, index, index + 1); + grid.setColumnOrder(columnOrder + .toArray(new Column[columnOrder.size()])); + } + }); + columnMenu + .addItem("Sortable", + selectedItem -> grid.getColumns().get(index) + .setSortable(selectedItem.isChecked())) + .setCheckable(true); + columnMenu + .addItem("Hidable", + selectedItem -> grid.getColumns().get(index) + .setHidable(selectedItem.isChecked())) + .setCheckable(true); + columnMenu + .addItem("Hidden", + selectedItem -> grid.getColumns().get(index) + .setHidden(selectedItem.isChecked())) + .setCheckable(true); + } + } + private void createSizeMenu(MenuItem sizeMenu) { MenuItem heightByRows = sizeMenu.addItem("Height by Rows", null); DecimalFormat df = new DecimalFormat("0.00"); @@ -238,6 +312,14 @@ public class GridBasics extends AbstractTestUIWithLog { } } }).setCheckable(true); + + stateMenu.addItem("Column reorder listener", + toggleReorderListenerCommand).setCheckable(true); + + stateMenu + .addItem("Column Reordering", selectedItem -> grid + .setColumnReorderingAllowed(selectedItem.isChecked())) + .setCheckable(true); } private void createRowStyleMenu(MenuItem rowStyleMenu) { diff --git a/uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridBasicsTest.java b/uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridBasicsTest.java index 1a1bc30e81..eed08032de 100644 --- a/uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridBasicsTest.java +++ b/uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridBasicsTest.java @@ -1,16 +1,24 @@ package com.vaadin.tests.components.grid.basics; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; import java.util.List; import java.util.stream.Stream; import org.junit.Before; import org.openqa.selenium.By; import org.openqa.selenium.WebElement; +import org.openqa.selenium.interactions.Actions; import org.openqa.selenium.remote.DesiredCapabilities; +import com.vaadin.testbench.TestBenchElement; import com.vaadin.testbench.customelements.GridElement; +import com.vaadin.testbench.elements.GridElement.GridCellElement; import com.vaadin.testbench.parallel.Browser; import com.vaadin.tests.tb3.MultiBrowserTest; +import com.vaadin.v7.tests.components.grid.basicfeatures.GridBasicFeaturesTest.CellSide; /** * Base class for all {@link GridBasics} UI tests @@ -41,6 +49,14 @@ public abstract class GridBasicsTest extends MultiBrowserTest { return $(GridElement.class).first(); } + protected List<TestBenchElement> getGridHeaderRowCells() { + List<TestBenchElement> headerCells = new ArrayList<>(); + for (int i = 0; i < getGridElement().getHeaderCount(); ++i) { + headerCells.addAll(getGridElement().getHeaderCells(i)); + } + return headerCells; + } + protected Stream<DataObject> getTestData() { return testData.stream(); } @@ -65,4 +81,88 @@ public abstract class GridBasicsTest extends MultiBrowserTest { "//div[contains(@class, \"v-grid-scroller-horizontal\")]")); } + protected GridCellElement getDefaultColumnHeader(int index) { + List<GridCellElement> headerRowCells = getGridElement() + .getHeaderCells(0); + return headerRowCells.get(index); + } + + protected void dragAndDropDefaultColumnHeader(int draggedColumnHeaderIndex, + int onTopOfColumnHeaderIndex, CellSide cellSide) { + GridCellElement columnHeader = getDefaultColumnHeader( + onTopOfColumnHeaderIndex); + new Actions(getDriver()) + .clickAndHold(getDefaultColumnHeader(draggedColumnHeaderIndex)) + .moveToElement(columnHeader, getHorizontalOffsetForDragAndDrop( + columnHeader, cellSide), 0) + .release().perform(); + } + + protected void dragAndDropColumnHeader(int headerRow, + int draggedColumnHeaderIndex, int onTopOfColumnHeaderIndex, + CellSide cellSide) { + GridCellElement headerCell = getGridElement().getHeaderCell(headerRow, + onTopOfColumnHeaderIndex); + new Actions(getDriver()) + .clickAndHold(getGridElement().getHeaderCell(headerRow, + draggedColumnHeaderIndex)) + .moveToElement(headerCell, + getHorizontalOffsetForDragAndDrop(headerCell, cellSide), + 0) + .release().perform(); + } + + protected void dragAndDropColumnHeader(int headerRow, + int draggedColumnHeaderIndex, int onTopOfColumnHeaderIndex, + int horizontalOffset) { + GridCellElement headerCell = getGridElement().getHeaderCell(headerRow, + onTopOfColumnHeaderIndex); + new Actions(getDriver()) + .clickAndHold(getGridElement().getHeaderCell(headerRow, + draggedColumnHeaderIndex)) + .moveToElement(headerCell, horizontalOffset, 0).release() + .perform(); + } + + private int getHorizontalOffsetForDragAndDrop(GridCellElement columnHeader, + CellSide cellSide) { + if (cellSide == CellSide.LEFT) { + return 5; + } else { + int half = columnHeader.getSize().getWidth() / 2; + return half + (half / 2); + } + } + + protected void toggleColumnReorder() { + selectMenuPath("Component", "State", "Column Reordering"); + } + + protected void assertColumnHeaderOrder(int... indices) { + List<TestBenchElement> headers = getGridHeaderRowCells(); + for (int i = 0; i < indices.length; i++) { + assertColumnHeader("HEADER CELL " + indices[i], headers.get(i)); + } + } + + protected void assertColumnHeader(String expectedHeaderCaption, + TestBenchElement testBenchElement) { + assertEquals(expectedHeaderCaption.toLowerCase(), + testBenchElement.getText().toLowerCase()); + } + + 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")); + } + + protected void focusCell(int row, int column) { + getGridElement().getCell(row, column).click(); + } + } diff --git a/uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridColumnReorderTest.java b/uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridColumnReorderTest.java new file mode 100644 index 0000000000..62156acec7 --- /dev/null +++ b/uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridColumnReorderTest.java @@ -0,0 +1,204 @@ +/* + * Copyright 2000-2016 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.basics; + +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.parallel.TestCategory; +import com.vaadin.v7.tests.components.grid.basicfeatures.GridBasicFeaturesTest.CellSide; + +/** + * + * @author Vaadin Ltd + */ +@TestCategory("grid") +public class GridColumnReorderTest extends GridBasicsTest { + + @Before + public void before() { + openTestURL(); + } + + @Test + public void testColumnReorder_onReorder_columnReorderEventTriggered() { + selectMenuPath("Component", "Header", "Prepend header row"); + selectMenuPath("Component", "State", "Column reorder listener"); + selectMenuPath("Component", "Columns", "Column " + 3, "Move left"); + + assertEquals("1. Registered a column reorder listener.", getLogRow(2)); + assertEquals("2. Columns reordered, userOriginated: false", + getLogRow(1)); + assertColumnHeaderOrder(0, 1, 3, 2); + + // trigger another event + selectMenuPath("Component", "Columns", "Column " + 3, "Move left"); + assertColumnHeaderOrder(0, 1, 2, 3); + + // test drag and drop is user originated + toggleColumnReorder(); + dragAndDropColumnHeader(0, 0, 1, CellSide.RIGHT); + assertEquals("6. Columns reordered, userOriginated: true", + getLogRow(1)); + assertColumnHeaderOrder(1, 0, 2, 3); + } + + @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 + dragAndDropDefaultColumnHeader(0, 2, CellSide.LEFT); + + // 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 + dragAndDropDefaultColumnHeader(0, 3, CellSide.LEFT); + + // 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 + dragAndDropDefaultColumnHeader(2, 0, CellSide.LEFT); + + // then + 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, CellSide.LEFT); + + // then + assertFocusedCell(2, 2); + + // when + dragAndDropDefaultColumnHeader(0, 2, CellSide.LEFT); + + // then + assertFocusedCell(2, 2); + } + + @Test + public void testColumnReorder_dragColumnFromRightToLeftOfFocusedCellColumn_focusIsKept() { + // given + toggleColumnReorder(); + focusCell(1, 3); + + // when + dragAndDropDefaultColumnHeader(4, 1, CellSide.LEFT); + + // then + assertFocusedCell(1, 4); + } + + @Test + public void testColumnReorder_dragColumnFromLeftToRightOfFocusedCellColumn_focusIsKept() { + // given + toggleColumnReorder(); + focusCell(4, 2); + + // when + dragAndDropDefaultColumnHeader(0, 4, CellSide.LEFT); + + // then + assertFocusedCell(4, 1); + } + + 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")); + } +} |