]> source.dussan.org Git - vaadin-framework.git/commitdiff
Reintroduce grid column sizing
authorAleksi Hietanen <aleksi@vaadin.com>
Wed, 21 Sep 2016 06:13:12 +0000 (09:13 +0300)
committerVaadin Code Review <review@vaadin.com>
Fri, 23 Sep 2016 06:15:02 +0000 (06:15 +0000)
Change-Id: Ie5e91c3e9c8f2c9d8c05415d5602e2eaf3bd960b

client/src/main/java/com/vaadin/client/connectors/grid/ColumnConnector.java
client/src/main/java/com/vaadin/client/connectors/grid/GridConnector.java
server/src/main/java/com/vaadin/ui/Grid.java
shared/src/main/java/com/vaadin/shared/ui/grid/ColumnState.java
uitest/src/main/java/com/vaadin/tests/components/grid/GridColumnResizing.java [new file with mode: 0644]
uitest/src/test/java/com/vaadin/tests/components/grid/GridColumnResizingTest.java [new file with mode: 0644]

index d88a50b7e56bb78b4cd704f374f7eecbddaaf58f..cc3dff651da31f8456a99a5e4b9970bf1c3973a4 100644 (file)
@@ -95,6 +95,31 @@ public class ColumnConnector extends AbstractExtensionConnector {
         column.setHidable(getState().hidable);
     }
 
+    @OnStateChange("resizable")
+    void updateResizable() {
+        column.setResizable(getState().resizable);
+    }
+
+    @OnStateChange("width")
+    void updateWidth() {
+        column.setWidth(getState().width);
+    }
+
+    @OnStateChange("minWidth")
+    void updateMinWidth() {
+        column.setMinimumWidth(getState().minWidth);
+    }
+
+    @OnStateChange("maxWidth")
+    void updateMaxWidth() {
+        column.setMaximumWidth(getState().maxWidth);
+    }
+
+    @OnStateChange("expandRatio")
+    void updateExpandRatio() {
+        column.setExpandRatio(getState().expandRatio);
+    }
+
     @Override
     public void onUnregister() {
         super.onUnregister();
index fd31622cfeafdd85b79928ccfc33eb37f2f4d4e6..ed07d1d3f1526af4073429771b1dec6b42d0d945 100644 (file)
@@ -163,6 +163,11 @@ public class GridConnector
                         getColumnId(event.getColumn()), event.isHidden());
             }
         });
+        getWidget().addColumnResizeHandler(event -> {
+            Column<?, JsonObject> column = event.getColumn();
+            getRpcProxy(GridServerRpc.class).columnResized(getColumnId(column),
+                    column.getWidthActual());
+        });
 
         /* Item click events */
         getWidget().addBodyClickHandler(itemClickHandler);
@@ -366,6 +371,12 @@ public class GridConnector
     protected void sendContextClickEvent(MouseEventDetails details,
             EventTarget eventTarget) {
 
+        // if element is the resize indicator, ignore the event
+        if (isResizeHandle(eventTarget)) {
+            WidgetUtil.clearTextSelection();
+            return;
+        }
+
         EventCellReference<JsonObject> eventCell = getWidget().getEventCell();
 
         Section section = eventCell.getSection();
@@ -381,4 +392,14 @@ public class GridConnector
 
         WidgetUtil.clearTextSelection();
     }
+
+    private boolean isResizeHandle(EventTarget eventTarget) {
+        if (Element.is(eventTarget)) {
+            Element e = Element.as(eventTarget);
+            if (e.getClassName().contains("-column-resize-handle")) {
+                return true;
+            }
+        }
+        return false;
+    }
 }
index b77e2ca7f712d7d723a895e7481249def5745c87..94f8c9aee798b3dbe521d6e8cf813ee414695aa4 100644 (file)
@@ -69,6 +69,11 @@ import elemental.json.JsonValue;
  */
 public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents {
 
+    @Deprecated
+    private static final Method COLUMN_RESIZE_METHOD = ReflectTools.findMethod(
+            ColumnResizeListener.class, "columnResize",
+            ColumnResizeEvent.class);
+
     @Deprecated
     private static final Method ITEM_CLICK_METHOD = ReflectTools
             .findMethod(ItemClickListener.class, "accept", ItemClick.class);
@@ -79,6 +84,69 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents {
                     "columnVisibilityChanged",
                     ColumnVisibilityChangeEvent.class);
 
+    /**
+     * An event listener for column resize events in the Grid.
+     *
+     * @since 7.6
+     */
+    public interface ColumnResizeListener extends Serializable {
+
+        /**
+         * Called when the columns of the grid have been resized.
+         *
+         * @param event
+         *            An event providing more information
+         */
+        void columnResize(ColumnResizeEvent event);
+    }
+
+    /**
+     * 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 {
+
+        private final Column<?, ?> column;
+        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 ColumnResizeEvent(Grid<?> source, Column<?, ?> column,
+                boolean userOriginated) {
+            super(source);
+            this.column = column;
+            this.userOriginated = userOriginated;
+        }
+
+        /**
+         * Returns the column that was resized.
+         *
+         * @return the resized column.
+         */
+        public Column<?, ?> getColumn() {
+            return column;
+        }
+
+        /**
+         * Returns <code>true</code> if the column resize 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 fired when an item in the Grid has been clicked.
      *
@@ -466,7 +534,12 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents {
 
         @Override
         public void columnResized(String id, double pixels) {
-            // TODO Auto-generated method stub
+            final Column<T, ?> column = getColumn(id);
+            if (column != null && column.isResizable()) {
+                column.getState().width = pixels;
+                fireColumnResizeEvent(column, true);
+                markAsDirty();
+            }
         }
     }
 
@@ -942,6 +1015,242 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents {
             return descriptionGenerator;
         }
 
+        /**
+         * Sets the ratio with which the column expands.
+         * <p>
+         * By default, all columns expand equally (treated as if all of them had
+         * an expand ratio of 1). Once at least one column gets a defined expand
+         * ratio, the implicit expand ratio is removed, and only the defined
+         * expand ratios are taken into account.
+         * <p>
+         * If a column has a defined width ({@link #setWidth(double)}), it
+         * overrides this method's effects.
+         * <p>
+         * <em>Example:</em> A grid with three columns, with expand ratios 0, 1
+         * and 2, respectively. The column with a <strong>ratio of 0 is exactly
+         * as wide as its contents requires</strong>. The column with a ratio of
+         * 1 is as wide as it needs, <strong>plus a third of any excess
+         * space</strong>, because we have 3 parts total, and this column
+         * reserves only one of those. The column with a ratio of 2, is as wide
+         * as it needs to be, <strong>plus two thirds</strong> of the excess
+         * width.
+         *
+         * @param expandRatio
+         *            the expand ratio of this column. {@code 0} to not have it
+         *            expand at all. A negative number to clear the expand
+         *            value.
+         * @throws IllegalStateException
+         *             if the column is no longer attached to any grid
+         * @see #setWidth(double)
+         */
+        public Column<T, V> setExpandRatio(int expandRatio)
+                throws IllegalStateException {
+            checkColumnIsAttached();
+            if (expandRatio != getExpandRatio()) {
+                getState().expandRatio = expandRatio;
+                getParent().markAsDirty();
+            }
+            return this;
+        }
+
+        /**
+         * Returns the column's expand ratio.
+         *
+         * @return the column's expand ratio
+         * @see #setExpandRatio(int)
+         */
+        public int getExpandRatio() {
+            return getState(false).expandRatio;
+        }
+
+        /**
+         * Clears the expand ratio for this column.
+         * <p>
+         * Equal to calling {@link #setExpandRatio(int) setExpandRatio(-1)}
+         *
+         * @throws IllegalStateException
+         *             if the column is no longer attached to any grid
+         */
+        public Column<T, V> clearExpandRatio() throws IllegalStateException {
+            return setExpandRatio(-1);
+        }
+
+        /**
+         * Returns the width (in pixels). By default a column is 100px wide.
+         *
+         * @return the width in pixels of the column
+         * @throws IllegalStateException
+         *             if the column is no longer attached to any grid
+         */
+        public double getWidth() throws IllegalStateException {
+            checkColumnIsAttached();
+            return getState(false).width;
+        }
+
+        /**
+         * Sets the width (in pixels).
+         * <p>
+         * This overrides any configuration set by any of
+         * {@link #setExpandRatio(int)}, {@link #setMinimumWidth(double)} or
+         * {@link #setMaximumWidth(double)}.
+         *
+         * @param pixelWidth
+         *            the new pixel width of the column
+         * @return the column itself
+         *
+         * @throws IllegalStateException
+         *             if the column is no longer attached to any grid
+         * @throws IllegalArgumentException
+         *             thrown if pixel width is less than zero
+         */
+        public Column<T, V> setWidth(double pixelWidth)
+                throws IllegalStateException, IllegalArgumentException {
+            checkColumnIsAttached();
+            if (pixelWidth < 0) {
+                throw new IllegalArgumentException(
+                        "Pixel width should be greated than 0 (in " + toString()
+                                + ")");
+            }
+            if (pixelWidth != getWidth()) {
+                getState().width = pixelWidth;
+                getParent().markAsDirty();
+                getParent().fireColumnResizeEvent(this, false);
+            }
+            return this;
+        }
+
+        /**
+         * Returns whether this column has an undefined width.
+         *
+         * @since 7.6
+         * @return whether the width is undefined
+         * @throws IllegalStateException
+         *             if the column is no longer attached to any grid
+         */
+        public boolean isWidthUndefined() {
+            checkColumnIsAttached();
+            return getState(false).width < 0;
+        }
+
+        /**
+         * Marks the column width as undefined. An undefined width means the
+         * grid is free to resize the column based on the cell contents and
+         * available space in the grid.
+         *
+         * @return the column itself
+         */
+        public Column<T, V> setWidthUndefined() {
+            checkColumnIsAttached();
+            if (!isWidthUndefined()) {
+                getState().width = -1;
+                getParent().markAsDirty();
+                getParent().fireColumnResizeEvent(this, false);
+            }
+            return this;
+        }
+
+        /**
+         * Sets the minimum width for this column.
+         * <p>
+         * This defines the minimum guaranteed pixel width of the column
+         * <em>when it is set to expand</em>.
+         *
+         * @throws IllegalStateException
+         *             if the column is no longer attached to any grid
+         * @see #setExpandRatio(int)
+         */
+        public Column<T, V> setMinimumWidth(double pixels)
+                throws IllegalStateException {
+            checkColumnIsAttached();
+
+            final double maxwidth = getMaximumWidth();
+            if (pixels >= 0 && pixels > maxwidth && maxwidth >= 0) {
+                throw new IllegalArgumentException("New minimum width ("
+                        + pixels + ") was greater than maximum width ("
+                        + maxwidth + ")");
+            }
+            getState().minWidth = pixels;
+            getParent().markAsDirty();
+            return this;
+        }
+
+        /**
+         * Return the minimum width for this column.
+         *
+         * @return the minimum width for this column
+         * @see #setMinimumWidth(double)
+         */
+        public double getMinimumWidth() {
+            return getState(false).minWidth;
+        }
+
+        /**
+         * Sets the maximum width for this column.
+         * <p>
+         * This defines the maximum allowed pixel width of the column <em>when
+         * it is set to expand</em>.
+         *
+         * @param pixels
+         *            the maximum width
+         * @throws IllegalStateException
+         *             if the column is no longer attached to any grid
+         * @see #setExpandRatio(int)
+         */
+        public Column<T, V> setMaximumWidth(double pixels) {
+            checkColumnIsAttached();
+
+            final double minwidth = getMinimumWidth();
+            if (pixels >= 0 && pixels < minwidth && minwidth >= 0) {
+                throw new IllegalArgumentException("New maximum width ("
+                        + pixels + ") was less than minimum width (" + minwidth
+                        + ")");
+            }
+
+            getState().maxWidth = pixels;
+            getParent().markAsDirty();
+            return this;
+        }
+
+        /**
+         * Returns the maximum width for this column.
+         *
+         * @return the maximum width for this column
+         * @see #setMaximumWidth(double)
+         */
+        public double getMaximumWidth() {
+            return getState(false).maxWidth;
+        }
+
+        /**
+         * Sets whether this column can be resized by the user.
+         *
+         * @since 7.6
+         * @param resizable
+         *            {@code true} if this column should be resizable,
+         *            {@code false} otherwise
+         * @throws IllegalStateException
+         *             if the column is no longer attached to any grid
+         */
+        public Column<T, V> setResizable(boolean resizable) {
+            checkColumnIsAttached();
+            if (resizable != isResizable()) {
+                getState().resizable = resizable;
+                getParent().markAsDirty();
+            }
+            return this;
+        }
+
+        /**
+         * Gets the caption of the hiding toggle for this column.
+         *
+         * @since 7.5.0
+         * @see #setHidingToggleCaption(String)
+         * @return the caption for the hiding toggle for this column
+         */
+        public String getHidingToggleCaption() {
+            return getState(false).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
@@ -965,17 +1274,6 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents {
             return this;
         }
 
-        /**
-         * Gets the caption of the hiding toggle for this column.
-         *
-         * @since 7.5.0
-         * @see #setHidingToggleCaption(String)
-         * @return the caption for the hiding toggle for this column
-         */
-        public String getHidingToggleCaption() {
-            return getState(false).hidingToggleCaption;
-        }
-
         /**
          * Hides or shows the column. By default columns are visible before
          * explicitly hiding them.
@@ -1042,8 +1340,24 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents {
         }
 
         /**
-         * Checks whether this column is attached and throws an
-         * {@link IllegalStateException} if it is not.
+         * Returns whether this column can be resized by the user. Default is
+         * {@code true}.
+         * <p>
+         * <em>Note:</em> the column can be programmatically resized using
+         * {@link #setWidth(double)} and {@link #setWidthUndefined()} regardless
+         * of the returned value.
+         *
+         * @since 7.6
+         * @return {@code true} if this column is resizable, {@code false}
+         *         otherwise
+         */
+        public boolean isResizable() {
+            return getState(false).resizable;
+        }
+
+        /**
+         * Checks if column is attached and throws an
+         * {@link IllegalStateException} if it is not
          *
          * @throws IllegalStateException
          *             if the column is no longer attached to any grid
@@ -1448,6 +1762,20 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents {
         return descriptionGenerator;
     }
 
+    /**
+     * Registers a new column resize listener.
+     *
+     * @param listener
+     *            the listener to register, not null
+     * @return a registration for the listener
+     */
+    public Registration addColumnResizeListener(ColumnResizeListener listener) {
+        Objects.requireNonNull(listener, "listener cannot be null");
+        addListener(ColumnResizeEvent.class, listener, COLUMN_RESIZE_METHOD);
+        return () -> removeListener(ColumnResizeEvent.class, listener,
+                COLUMN_RESIZE_METHOD);
+    }
+
     /**
      * Adds an item click listener. The listener is called when an item of this
      * {@code Grid} is clicked.
@@ -1503,4 +1831,9 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents {
             markAsDirty();
         }
     }
+
+    private void fireColumnResizeEvent(Column<?, ?> column,
+            boolean userOriginated) {
+        fireEvent(new ColumnResizeEvent(this, column, userOriginated));
+    }
 }
index c789988cb588be84a000e5fa26da3e57d853c082..8296dd8660efde5630d2013d5f22862fea0bfef3 100644 (file)
@@ -33,5 +33,29 @@ public class ColumnState extends SharedState {
     /** Whether the column can be hidden by the user. */
     public boolean hidable = false;
 
+    /**
+     * Column width in pixels. Default column width is
+     * {@value GridConstants#DEFAULT_COLUMN_WIDTH_PX}.
+     */
+    public double width = GridConstants.DEFAULT_COLUMN_WIDTH_PX;
+
+    /** How much of the remaining space this column will reserve. */
+    public int expandRatio = GridConstants.DEFAULT_EXPAND_RATIO;
+
+    /**
+     * The maximum expansion width of this column. -1 for "no maximum". If
+     * maxWidth is less than the calculated width, maxWidth is ignored.
+     */
+    public double maxWidth = GridConstants.DEFAULT_MAX_WIDTH;
+
+    /**
+     * The minimum expansion width of this column. -1 for "no minimum". If
+     * minWidth is less than the calculated width, minWidth will win.
+     */
+    public double minWidth = GridConstants.DEFAULT_MIN_WIDTH;
+
+    /** Whether this column is resizable by the user. */
+    public boolean resizable = true;
+
     public Connector renderer;
 }
diff --git a/uitest/src/main/java/com/vaadin/tests/components/grid/GridColumnResizing.java b/uitest/src/main/java/com/vaadin/tests/components/grid/GridColumnResizing.java
new file mode 100644 (file)
index 0000000..67e6473
--- /dev/null
@@ -0,0 +1,52 @@
+package com.vaadin.tests.components.grid;
+
+import java.util.Arrays;
+
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.tests.components.AbstractTestUI;
+import com.vaadin.tests.data.bean.Person;
+import com.vaadin.ui.Grid;
+import com.vaadin.ui.Grid.Column;
+import com.vaadin.ui.Label;
+import com.vaadin.ui.TextField;
+import com.vaadin.ui.renderers.NumberRenderer;
+
+public class GridColumnResizing extends AbstractTestUI {
+
+    @Override
+    protected void setup(VaadinRequest request) {
+        TextField input = new TextField();
+        Label isResizedLabel = new Label("not resized");
+        Grid<Person> grid = new Grid<>();
+        Column<Person, String> nameColumn = grid.addColumn("Name",
+                Person::getFirstName);
+        Column<Person, Number> ageColumn = grid.addColumn("Age", Person::getAge,
+                new NumberRenderer());
+        grid.addColumnResizeListener(event -> {
+            if (event.isUserOriginated()) {
+                isResizedLabel.setValue("client resized");
+            } else {
+                isResizedLabel.setValue("server resized");
+            }
+        });
+        grid.setItems(Arrays.asList(Person.createTestPerson1(),
+                Person.createTestPerson2()));
+
+        addComponent(input);
+        addButton("set width", event -> nameColumn
+                .setWidth(Double.parseDouble(input.getValue())));
+        addButton("set expand ratio", event -> {
+            nameColumn.setExpandRatio(4);
+            ageColumn.setExpandRatio(1);
+        });
+        addButton("set min width", event -> nameColumn
+                .setMinimumWidth(Double.parseDouble(input.getValue())));
+        addButton("set max width", event -> nameColumn
+                .setMaximumWidth(Double.parseDouble(input.getValue())));
+        addButton("toggle resizable",
+                event -> nameColumn.setResizable(!nameColumn.isResizable()));
+
+        addComponents(grid, isResizedLabel);
+    }
+
+}
diff --git a/uitest/src/test/java/com/vaadin/tests/components/grid/GridColumnResizingTest.java b/uitest/src/test/java/com/vaadin/tests/components/grid/GridColumnResizingTest.java
new file mode 100644 (file)
index 0000000..3f2a08d
--- /dev/null
@@ -0,0 +1,150 @@
+package com.vaadin.tests.components.grid;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.openqa.selenium.By;
+import org.openqa.selenium.Dimension;
+import org.openqa.selenium.Keys;
+import org.openqa.selenium.interactions.Actions;
+
+import com.vaadin.testbench.elements.ButtonElement;
+import com.vaadin.testbench.elements.GridElement;
+import com.vaadin.testbench.elements.GridElement.GridCellElement;
+import com.vaadin.testbench.elements.LabelElement;
+import com.vaadin.testbench.elements.TextFieldElement;
+import com.vaadin.tests.tb3.MultiBrowserTest;
+
+public class GridColumnResizingTest extends MultiBrowserTest {
+
+    @Test
+    public void serverSetWidth() {
+        openTestURL();
+
+        serverSideSetWidth(50);
+        assertColumnWidth(50, 0);
+
+        serverSideSetWidth(500);
+        assertColumnWidth(500, 0);
+    }
+
+    @Test
+    public void setResizable() {
+        openTestURL();
+        ButtonElement toggleResizableButton = $(ButtonElement.class).get(4);
+        GridCellElement cell = getGrid().getHeaderCell(0, 0);
+
+        Assert.assertEquals(true, cell.isElementPresent(
+                By.cssSelector("div.v-grid-column-resize-handle")));
+
+        toggleResizableButton.click();
+        Assert.assertEquals(false, cell.isElementPresent(
+                By.cssSelector("div.v-grid-column-resize-handle")));
+    }
+
+    @Test
+    public void setExpandRatio() {
+        openTestURL();
+        ButtonElement setExpandRatioButton = $(ButtonElement.class).get(1);
+
+        setExpandRatioButton.click();
+        assertColumnWidthWithThreshold(375, 0, 2);
+        assertColumnWidthWithThreshold(125, 1, 2);
+    }
+
+    @Test
+    public void setMinimumWidth() {
+        openTestURL();
+
+        setMinWidth(100);
+        serverSideSetWidth(50);
+        assertColumnWidth(100, 0);
+
+        serverSideSetWidth(150);
+        dragResizeColumn(0, 0, -100);
+        assertColumnWidth(100, 0);
+    }
+
+    @Test
+    public void setMaximumWidth() {
+        openTestURL();
+
+        serverSideSetWidth(50);
+        setMaxWidth(100);
+
+        serverSideSetWidth(150);
+        assertColumnWidth(100, 0);
+
+        // TODO add the following when grid column width recalculation has been
+        // fixed in the case where the sum of column widths exceeds the visible
+        // area
+
+        // serverSideSetWidth(50);
+        // dragResizeColumn(0, 0, 200);
+        // assertColumnWidth(100, 0);
+    }
+
+    @Test
+    public void resizeEventListener() {
+        openTestURL();
+
+        Assert.assertEquals("not resized",
+                $(LabelElement.class).get(1).getText());
+
+        serverSideSetWidth(150);
+        Assert.assertEquals("server resized",
+                $(LabelElement.class).get(1).getText());
+
+        dragResizeColumn(0, 0, 100);
+        Assert.assertEquals("client resized",
+                $(LabelElement.class).get(1).getText());
+    }
+
+    private GridElement getGrid() {
+        return $(GridElement.class).first();
+    }
+
+    private void serverSideSetWidth(double width) {
+        TextFieldElement textField = $(TextFieldElement.class).first();
+        ButtonElement setWidthButton = $(ButtonElement.class).get(0);
+        textField.clear();
+        textField.sendKeys(String.valueOf(width), Keys.ENTER);
+        setWidthButton.click();
+    }
+
+    private void setMinWidth(double minWidth) {
+        TextFieldElement textField = $(TextFieldElement.class).first();
+        ButtonElement setMinWidthButton = $(ButtonElement.class).get(2);
+        textField.clear();
+        textField.sendKeys(String.valueOf(minWidth), Keys.ENTER);
+        setMinWidthButton.click();
+    }
+
+    private void setMaxWidth(double maxWidth) {
+        TextFieldElement textField = $(TextFieldElement.class).first();
+        ButtonElement setMaxWidthButton = $(ButtonElement.class).get(3);
+        textField.clear();
+        textField.sendKeys(String.valueOf(maxWidth), Keys.ENTER);
+        setMaxWidthButton.click();
+    }
+
+    private void dragResizeColumn(int columnIndex, int posX, int offset) {
+        GridCellElement headerCell = getGrid().getHeaderCell(0, columnIndex);
+        Dimension size = headerCell.getSize();
+        new Actions(getDriver())
+                .moveToElement(headerCell, size.getWidth() + posX,
+                        size.getHeight() / 2)
+                .clickAndHold().moveByOffset(offset, 0).release().perform();
+    }
+
+    private void assertColumnWidth(int width, int columnIndex) {
+        Assert.assertEquals(width,
+                getGrid().getCell(0, columnIndex).getSize().getWidth());
+    }
+
+    private void assertColumnWidthWithThreshold(int width, int columnIndex,
+            int threshold) {
+        Assert.assertTrue(
+                Math.abs(getGrid().getCell(0, columnIndex).getSize().getWidth()
+                        - width) <= threshold);
+    }
+}