]> source.dussan.org Git - vaadin-framework.git/commitdiff
Reintroduce reordering of grid columns
authorAleksi Hietanen <aleksi@vaadin.com>
Mon, 3 Oct 2016 07:30:05 +0000 (10:30 +0300)
committerVaadin Code Review <review@vaadin.com>
Thu, 6 Oct 2016 08:43:28 +0000 (08:43 +0000)
Change-Id: If8a23427ef5500a0177081c4be8065d2d5a0ca4c

client/src/main/java/com/vaadin/client/connectors/grid/GridConnector.java
client/src/main/java/com/vaadin/client/widget/grid/events/ColumnReorderEvent.java
client/src/main/java/com/vaadin/client/widgets/Grid.java
server/src/main/java/com/vaadin/ui/Grid.java
uitest/src/main/java/com/vaadin/tests/components/grid/basics/GridBasics.java
uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridBasicsTest.java
uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridColumnReorderTest.java [new file with mode: 0644]

index 6596137d9d9dbdb6832eb613f375866993a71032..02909e1fc309abf539857bb7bbd1aa2cf1731685 100644 (file)
@@ -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());
+    }
 }
index 72c0eb5878de61f7b59b4ab989ea4cf657e11251..0063c20b2859976464dfe038404839bc09efb2b9 100644 (file)
  */
 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() {
index eac958fdce976aee6320a9ce85afe0b78afbafce..7d6c1369b8dae0bd466e605250806d8fbca96841 100644 (file)
@@ -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));
     }
 
     /**
index e2df8b437b4b6b62f448f0178b11a3986f0628bc..891c08aa473221e769cb5a6953319e8d99b0e2d0 100644 (file)
@@ -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;
@@ -72,6 +76,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",
@@ -87,11 +96,25 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents {
                     "columnVisibilityChanged",
                     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 {
 
         /**
@@ -103,11 +126,40 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents {
         void columnResize(ColumnResizeEvent event);
     }
 
+    /**
+     * 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() {
@@ -2012,6 +2107,20 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents {
         return header;
     }
 
+    /**
+     * 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.
      *
@@ -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));
index f75dbd27cc3fb076435a47e0908bce649c76648d..28c8a987b76325ce1a7ac0b03447811777d35b54 100644 (file)
@@ -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) {
index 1a1bc30e81489144f6bb97973454c4588c23b155..eed08032de523c137add222f372c0268827c20f1 100644 (file)
@@ -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 (file)
index 0000000..62156ac
--- /dev/null
@@ -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"));
+    }
+}