]> source.dussan.org Git - vaadin-framework.git/commitdiff
Reintroduce frozen columns and height by rows to Grid
authorTeemu Suo-Anttila <teemusa@vaadin.com>
Tue, 30 Aug 2016 10:01:46 +0000 (13:01 +0300)
committerArtur Signell <artur@vaadin.com>
Thu, 1 Sep 2016 10:36:14 +0000 (10:36 +0000)
Change-Id: I5fecfabd023b39dc252e47a6aa403a79034b0f3d

client/src/main/java/com/vaadin/client/connectors/grid/GridConnector.java
server/src/main/java/com/vaadin/ui/Grid.java
server/src/test/java/com/vaadin/tests/components/grid/GridDetailsTest.java [deleted file]
server/src/test/java/com/vaadin/tests/server/component/grid/GridDetailsTest.java [new file with mode: 0644]
server/src/test/java/com/vaadin/tests/server/component/grid/GridTest.java [new file with mode: 0644]
uitest/src/main/java/com/vaadin/tests/components/grid/basics/GridBasics.java
uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridBasicStructureTest.java [new file with mode: 0644]

index 3f22f419e7ad4df28291417fdb12adff71a3ba36..9f2b8971c13b8b674a2fdf73f095a702f86dcbce 100644 (file)
@@ -38,6 +38,7 @@ import com.vaadin.client.widgets.Grid.Column;
 import com.vaadin.shared.data.sort.SortDirection;
 import com.vaadin.shared.ui.Connect;
 import com.vaadin.shared.ui.grid.GridServerRpc;
+import com.vaadin.shared.ui.grid.GridState;
 
 import elemental.json.JsonObject;
 
@@ -171,4 +172,9 @@ public class GridConnector extends AbstractListingConnector
         return ensureHandlerManager()
                 .addHandler(ConnectorHierarchyChangeEvent.TYPE, handler);
     }
+
+    @Override
+    public GridState getState() {
+        return (GridState) super.getState();
+    }
 }
index c236a9c5f6a2ff5639f039a48a81486f43453785..ae7c550172651fba30e178c4ec28d102f88ef383 100644 (file)
@@ -45,6 +45,7 @@ import com.vaadin.shared.ui.grid.ColumnState;
 import com.vaadin.shared.ui.grid.GridConstants.Section;
 import com.vaadin.shared.ui.grid.GridServerRpc;
 import com.vaadin.shared.ui.grid.GridState;
+import com.vaadin.shared.ui.grid.HeightMode;
 
 import elemental.json.Json;
 import elemental.json.JsonObject;
@@ -628,6 +629,153 @@ public class Grid<T> extends AbstractListing<T, SelectionModel<T>>
         return Collections.unmodifiableSet(extensionComponents).iterator();
     }
 
+    /**
+     * Sets the number of frozen columns in this grid. Setting the count to 0
+     * means that no data columns will be frozen, but the built-in selection
+     * checkbox column will still be frozen if it's in use. Setting the count to
+     * -1 will also disable the selection column.
+     * <p>
+     * The default value is 0.
+     *
+     * @param numberOfColumns
+     *            the number of columns that should be frozen
+     *
+     * @throws IllegalArgumentException
+     *             if the column count is less than -1 or greater than the
+     *             number of visible columns
+     */
+    public void setFrozenColumnCount(int numberOfColumns) {
+        if (numberOfColumns < -1 || numberOfColumns > columnSet.size()) {
+            throw new IllegalArgumentException(
+                    "count must be between -1 and the current number of columns ("
+                            + columnSet.size() + "): " + numberOfColumns);
+        }
+
+        getState().frozenColumnCount = numberOfColumns;
+    }
+
+    /**
+     * Gets the number of frozen columns in this grid. 0 means that no data
+     * columns will be frozen, but the built-in selection checkbox column will
+     * still be frozen if it's in use. -1 means that not even the selection
+     * column is frozen.
+     * <p>
+     * <em>NOTE:</em> this count includes {@link Column#isHidden() hidden
+     * columns} in the count.
+     *
+     * @see #setFrozenColumnCount(int)
+     *
+     * @return the number of frozen columns
+     */
+    public int getFrozenColumnCount() {
+        return getState(false).frozenColumnCount;
+    }
+
+    /**
+     * Sets the number of rows that should be visible in Grid's body. This
+     * method will set the height mode to be {@link HeightMode#ROW}.
+     *
+     * @param rows
+     *            The height in terms of number of rows displayed in Grid's
+     *            body. If Grid doesn't contain enough rows, white space is
+     *            displayed instead. If <code>null</code> is given, then Grid's
+     *            height is undefined
+     * @throws IllegalArgumentException
+     *             if {@code rows} is zero or less
+     * @throws IllegalArgumentException
+     *             if {@code rows} is {@link Double#isInfinite(double) infinite}
+     * @throws IllegalArgumentException
+     *             if {@code rows} is {@link Double#isNaN(double) NaN}
+     */
+    public void setHeightByRows(double rows) {
+        if (rows <= 0.0d) {
+            throw new IllegalArgumentException(
+                    "More than zero rows must be shown.");
+        } else if (Double.isInfinite(rows)) {
+            throw new IllegalArgumentException(
+                    "Grid doesn't support infinite heights");
+        } else if (Double.isNaN(rows)) {
+            throw new IllegalArgumentException("NaN is not a valid row count");
+        }
+        getState().heightMode = HeightMode.ROW;
+        getState().heightByRows = rows;
+    }
+
+    /**
+     * Gets the amount of rows in Grid's body that are shown, while
+     * {@link #getHeightMode()} is {@link HeightMode#ROW}.
+     *
+     * @return the amount of rows that are being shown in Grid's body
+     * @see #setHeightByRows(double)
+     */
+    public double getHeightByRows() {
+        return getState(false).heightByRows;
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * <em>Note:</em> This method will set the height mode to be
+     * {@link HeightMode#CSS}.
+     *
+     * @see #setHeightMode(HeightMode)
+     */
+    @Override
+    public void setHeight(float height, Unit unit) {
+        getState().heightMode = HeightMode.CSS;
+        super.setHeight(height, unit);
+    }
+
+    /**
+     * Defines the mode in which the Grid widget's height is calculated.
+     * <p>
+     * If {@link HeightMode#CSS} is given, Grid will respect the values given
+     * via a {@code setHeight}-method, and behave as a traditional Component.
+     * <p>
+     * If {@link HeightMode#ROW} is given, Grid will make sure that the body
+     * will display as many rows as {@link #getHeightByRows()} defines.
+     * <em>Note:</em> If headers/footers are inserted or removed, the widget
+     * will resize itself to still display the required amount of rows in its
+     * body. It also takes the horizontal scrollbar into account.
+     *
+     * @param heightMode
+     *            the mode in to which Grid should be set
+     */
+    public void setHeightMode(HeightMode heightMode) {
+        /*
+         * This method is a workaround for the fact that Vaadin re-applies
+         * widget dimensions (height/width) on each state change event. The
+         * original design was to have setHeight and setHeightByRow be equals,
+         * and whichever was called the latest was considered in effect.
+         *
+         * But, because of Vaadin always calling setHeight on the widget, this
+         * approach doesn't work.
+         */
+
+        getState().heightMode = heightMode;
+    }
+
+    /**
+     * Returns the current {@link HeightMode} the Grid is in.
+     * <p>
+     * Defaults to {@link HeightMode#CSS}.
+     *
+     * @return the current HeightMode
+     */
+    public HeightMode getHeightMode() {
+        return getState(false).heightMode;
+    }
+
+    @Override
+    protected GridState getState() {
+        return getState(true);
+    }
+
+    @Override
+    protected GridState getState(boolean markAsDirty) {
+        return (GridState) super.getState(markAsDirty);
+    }
+
     private void addExtensionComponent(Component c) {
         if (extensionComponents.add(c)) {
             c.setParent(this);
diff --git a/server/src/test/java/com/vaadin/tests/components/grid/GridDetailsTest.java b/server/src/test/java/com/vaadin/tests/components/grid/GridDetailsTest.java
deleted file mode 100644 (file)
index 7590efc..0000000
+++ /dev/null
@@ -1,90 +0,0 @@
-package com.vaadin.tests.components.grid;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.List;
-
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-
-import com.vaadin.ui.Component;
-import com.vaadin.ui.Grid;
-import com.vaadin.ui.Label;
-
-public class GridDetailsTest {
-
-    private final class DummyLabel extends Label {
-        private DummyLabel(String content) {
-            super(content);
-        }
-
-        @Override
-        public String getConnectorId() {
-            return "";
-        }
-    }
-
-    public static class TestGrid extends Grid<String> {
-
-        /**
-         * Used to execute data generation
-         */
-        public void runDataGeneration() {
-            super.getDataCommunicator().beforeClientResponse(true);
-        }
-    }
-
-    private TestGrid grid;
-    private List<String> data;
-
-    @Before
-    public void setUp() {
-        grid = new TestGrid();
-        // Setup Grid and generate some details
-        data = new ArrayList<>(Arrays.asList("Foo", "Bar"));
-        grid.setItems(data);
-        grid.setDetailsGenerator(s -> new DummyLabel(s));
-
-        data.forEach(s -> grid.setDetailsVisible(s, true));
-
-        grid.runDataGeneration();
-    }
-
-    @Test
-    public void testGridComponentIteratorContainsDetailsComponents() {
-        Iterator<Component> i = grid.iterator();
-
-        while (i.hasNext()) {
-            Component c = i.next();
-            if (c instanceof Label) {
-                String value = ((Label) c).getValue();
-                Assert.assertTrue(
-                        "Unexpected label in component iterator with value "
-                                + value,
-                        data.remove(value));
-            } else {
-                Assert.fail(
-                        "Iterator contained a component that is not a label.");
-            }
-        }
-    }
-
-    @Test(expected = UnsupportedOperationException.class)
-    public void testGridComponentIteratorNotModifiable() {
-        Iterator<Component> iterator = grid.iterator();
-        iterator.next();
-        // This should fail
-        iterator.remove();
-    }
-
-    @Test
-    public void testGridComponentIteratorIsEmptyAfterHidingDetails() {
-        Assert.assertTrue("Component iterator should have components.",
-                grid.iterator().hasNext());
-        data.forEach(s -> grid.setDetailsVisible(s, false));
-        Assert.assertFalse("Component iterator should not have components.",
-                grid.iterator().hasNext());
-    }
-}
diff --git a/server/src/test/java/com/vaadin/tests/server/component/grid/GridDetailsTest.java b/server/src/test/java/com/vaadin/tests/server/component/grid/GridDetailsTest.java
new file mode 100644 (file)
index 0000000..c7f1fbd
--- /dev/null
@@ -0,0 +1,90 @@
+package com.vaadin.tests.server.component.grid;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.vaadin.ui.Component;
+import com.vaadin.ui.Grid;
+import com.vaadin.ui.Label;
+
+public class GridDetailsTest {
+
+    private final class DummyLabel extends Label {
+        private DummyLabel(String content) {
+            super(content);
+        }
+
+        @Override
+        public String getConnectorId() {
+            return "";
+        }
+    }
+
+    public static class TestGrid extends Grid<String> {
+
+        /**
+         * Used to execute data generation
+         */
+        public void runDataGeneration() {
+            super.getDataCommunicator().beforeClientResponse(true);
+        }
+    }
+
+    private TestGrid grid;
+    private List<String> data;
+
+    @Before
+    public void setUp() {
+        grid = new TestGrid();
+        // Setup Grid and generate some details
+        data = new ArrayList<>(Arrays.asList("Foo", "Bar"));
+        grid.setItems(data);
+        grid.setDetailsGenerator(s -> new DummyLabel(s));
+
+        data.forEach(s -> grid.setDetailsVisible(s, true));
+
+        grid.runDataGeneration();
+    }
+
+    @Test
+    public void testGridComponentIteratorContainsDetailsComponents() {
+        Iterator<Component> i = grid.iterator();
+
+        while (i.hasNext()) {
+            Component c = i.next();
+            if (c instanceof Label) {
+                String value = ((Label) c).getValue();
+                Assert.assertTrue(
+                        "Unexpected label in component iterator with value "
+                                + value,
+                        data.remove(value));
+            } else {
+                Assert.fail(
+                        "Iterator contained a component that is not a label.");
+            }
+        }
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void testGridComponentIteratorNotModifiable() {
+        Iterator<Component> iterator = grid.iterator();
+        iterator.next();
+        // This should fail
+        iterator.remove();
+    }
+
+    @Test
+    public void testGridComponentIteratorIsEmptyAfterHidingDetails() {
+        Assert.assertTrue("Component iterator should have components.",
+                grid.iterator().hasNext());
+        data.forEach(s -> grid.setDetailsVisible(s, false));
+        Assert.assertFalse("Component iterator should not have components.",
+                grid.iterator().hasNext());
+    }
+}
diff --git a/server/src/test/java/com/vaadin/tests/server/component/grid/GridTest.java b/server/src/test/java/com/vaadin/tests/server/component/grid/GridTest.java
new file mode 100644 (file)
index 0000000..c426ffe
--- /dev/null
@@ -0,0 +1,53 @@
+package com.vaadin.tests.server.component.grid;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.function.Function;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.vaadin.shared.ui.grid.HeightMode;
+import com.vaadin.ui.Grid;
+
+public class GridTest {
+
+    private Grid<String> grid;
+
+    @Before
+    public void setUp() {
+        grid = new Grid<>();
+        grid.addColumn("foo", String.class, Function.identity());
+    }
+
+    @Test
+    public void testGridHeightModeChange() {
+        assertEquals("Initial height mode was not CSS", HeightMode.CSS,
+                grid.getHeightMode());
+        grid.setHeightByRows(13.24);
+        assertEquals("Setting height by rows did not change height mode",
+                HeightMode.ROW, grid.getHeightMode());
+        grid.setHeight("100px");
+        assertEquals("Setting height did not change height mode.",
+                HeightMode.CSS, grid.getHeightMode());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testFrozenColumnCountTooBig() {
+        grid.setFrozenColumnCount(2);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testFrozenColumnCountTooSmall() {
+        grid.setFrozenColumnCount(-2);
+    }
+
+    @Test()
+    public void testSetFrozenColumnCount() {
+        for (int i = -1; i < 2; ++i) {
+            grid.setFrozenColumnCount(i);
+            assertEquals("Frozen column count not updated", i,
+                    grid.getFrozenColumnCount());
+        }
+    }
+}
index fa032dac4843e7e09a2dc3301866d509deeade23..3445a3e3f3102bc1e90d1583c6add83ed2425c29 100644 (file)
@@ -1,12 +1,17 @@
 package com.vaadin.tests.components.grid.basics;
 
+import java.text.DecimalFormat;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
 
+import com.vaadin.annotations.Widgetset;
 import com.vaadin.server.VaadinRequest;
+import com.vaadin.shared.ui.grid.HeightMode;
 import com.vaadin.tests.components.AbstractTestUIWithLog;
 import com.vaadin.ui.Button;
 import com.vaadin.ui.Component;
@@ -20,6 +25,7 @@ import com.vaadin.ui.Notification;
 import com.vaadin.ui.Panel;
 import com.vaadin.ui.VerticalLayout;
 
+@Widgetset("com.vaadin.DefaultWidgetSet")
 public class GridBasics extends AbstractTestUIWithLog {
 
     private static class DetailedDetailsGenerator
@@ -121,10 +127,42 @@ public class GridBasics extends AbstractTestUIWithLog {
     private Component createMenu() {
         MenuBar menu = new MenuBar();
         MenuItem componentMenu = menu.addItem("Component", null);
+        createStateMenu(componentMenu.addItem("State", null));
+        createSizeMenu(componentMenu.addItem("Size", null));
         createDetailsMenu(componentMenu.addItem("Details", null));
         return menu;
     }
 
+    private void createSizeMenu(MenuItem sizeMenu) {
+        MenuItem heightByRows = sizeMenu.addItem("Height by Rows", null);
+        DecimalFormat df = new DecimalFormat("0.00");
+        Stream.of(0.33, 0.67, 1.00, 1.33, 1.67, 2.00, 2.33, 2.67, 3.00, 3.33,
+                3.67, 4.00, 4.33, 4.67)
+                .forEach(d -> addGridMethodMenu(heightByRows,
+                        df.format(d) + " rows", d, grid::setHeightByRows));
+        sizeMenu.addItem("HeightMode Row", item -> {
+            grid.setHeightMode(
+                    item.isChecked() ? HeightMode.ROW : HeightMode.CSS);
+        }).setCheckable(true);
+
+        MenuItem heightMenu = sizeMenu.addItem("Height", null);
+        Stream.of(50, 100, 200, 400).map(i -> i + "px").forEach(
+                i -> addGridMethodMenu(heightMenu, i, i, grid::setHeight));
+    }
+
+    private void createStateMenu(MenuItem stateMenu) {
+        MenuItem frozenColMenu = stateMenu.addItem("Frozen column count", null);
+        for (int i = -1; i < 3; ++i) {
+            addGridMethodMenu(frozenColMenu, "" + i, i,
+                    grid::setFrozenColumnCount);
+        }
+    }
+
+    private <T> void addGridMethodMenu(MenuItem parent, String name, T value,
+            Consumer<T> method) {
+        parent.addItem(name, menuItem -> method.accept(value));
+    }
+
     /* DetailsGenerator related things */
 
     private void createDetailsMenu(MenuItem detailsMenu) {
diff --git a/uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridBasicStructureTest.java b/uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridBasicStructureTest.java
new file mode 100644 (file)
index 0000000..9493463
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * 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.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+import org.openqa.selenium.WebElement;
+
+import com.vaadin.testbench.TestBenchElement;
+
+public class GridBasicStructureTest extends GridBasicsTest {
+
+    @Test
+    public void testFreezingColumn() throws Exception {
+        // Freeze column 1
+        selectMenuPath("Component", "State", "Frozen column count", "1");
+
+        WebElement cell = getGridElement().getCell(0, 0);
+        assertTrue("First cell on a row should be frozen",
+                cell.getAttribute("class").contains("frozen"));
+
+        assertFalse("Second cell on a row should not be frozen",
+                getGridElement().getCell(0, 1).getAttribute("class")
+                        .contains("frozen"));
+
+        int cellX = cell.getLocation().getX();
+        scrollGridHorizontallyTo(100);
+        assertEquals("First cell should not move when scrolling", cellX,
+                cell.getLocation().getX());
+    }
+
+    @Test
+    public void testHeightByRows() throws Exception {
+        int initialHeight = getGridElement().getSize().getHeight();
+
+        selectMenuPath("Component", "Size", "HeightMode Row");
+        selectMenuPath("Component", "Size", "Height by Rows", "2.00 rows");
+
+        TestBenchElement tableWrapper = getGridElement().getTableWrapper();
+        int rowHeight = getGridElement().getRow(0).getSize().getHeight();
+
+        assertTrue("Grid height was not 3 rows", Math
+                .abs(rowHeight * 3 - tableWrapper.getSize().getHeight()) < 2);
+
+        selectMenuPath("Component", "Size", "Height by Rows", "3.33 rows");
+
+        assertTrue("Grid height was not 4.33 rows", Math.abs(
+                rowHeight * 4.33 - tableWrapper.getSize().getHeight()) < 2);
+
+        selectMenuPath("Component", "Size", "HeightMode Row");
+        assertEquals("Grid should have returned to its original size",
+                initialHeight, getGridElement().getSize().getHeight());
+    }
+
+    @Test
+    public void testHeightModeChanges() throws Exception {
+        selectMenuPath("Component", "Size", "Height by Rows", "2.00 rows");
+
+        TestBenchElement tableWrapper = getGridElement().getTableWrapper();
+        int rowHeight = getGridElement().getRow(0).getSize().getHeight();
+
+        assertTrue("Grid height mode did not become ROW", Math
+                .abs(rowHeight * 3 - tableWrapper.getSize().getHeight()) < 2);
+
+        selectMenuPath("Component", "Size", "Height", "200px");
+
+        assertEquals("Grid height mode did not become CSS", 200,
+                getGridElement().getSize().getHeight());
+
+    }
+}