]> source.dussan.org Git - vaadin-framework.git/commitdiff
Implement basic footer support for Grid
authorTeemu Suo-Anttila <teemusa@vaadin.com>
Tue, 18 Oct 2016 14:31:37 +0000 (17:31 +0300)
committerTeemu Suo-Anttila <teemusa@vaadin.com>
Wed, 19 Oct 2016 11:32:25 +0000 (11:32 +0000)
Change-Id: I3db51521320767a28bc3acd9586b1453764a15bc

client/src/main/java/com/vaadin/client/connectors/grid/GridConnector.java
server/src/main/java/com/vaadin/ui/Grid.java
server/src/main/java/com/vaadin/ui/components/grid/Footer.java [new file with mode: 0644]
shared/src/main/java/com/vaadin/shared/ui/grid/GridState.java
uitest/src/main/java/com/vaadin/tests/components/grid/basics/GridBasics.java
uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridHeaderFooterTest.java

index 4686b3bd2183fb9d337938ba51e8b6eb87ef6af5..a19c955fc45312fc4164c6f188ffa2a9d5361cce 100644 (file)
@@ -53,6 +53,7 @@ import com.vaadin.client.widget.grid.sort.SortEvent;
 import com.vaadin.client.widget.grid.sort.SortOrder;
 import com.vaadin.client.widgets.Grid;
 import com.vaadin.client.widgets.Grid.Column;
+import com.vaadin.client.widgets.Grid.FooterRow;
 import com.vaadin.client.widgets.Grid.HeaderRow;
 import com.vaadin.shared.MouseEventDetails;
 import com.vaadin.shared.data.DataCommunicatorConstants;
@@ -264,6 +265,27 @@ public class GridConnector
         }
     }
 
+    /**
+     * Updates the grid footer section on state change.
+     */
+    @OnStateChange("footer")
+    void updateFooter() {
+        final Grid<JsonObject> grid = getWidget();
+        final SectionState state = getState().footer;
+
+        while (grid.getFooterRowCount() > 0) {
+            grid.removeFooterRow(0);
+        }
+
+        for (RowState rowState : state.rows) {
+            FooterRow row = grid.appendFooterRow();
+
+            rowState.cells.forEach((columnId, cellState) -> {
+                row.getCell(getColumn(columnId)).setText(cellState.text);
+            });
+        }
+    }
+
     @Override
     public void setDataSource(DataSource<JsonObject> dataSource) {
         super.setDataSource(dataSource);
index 843db35e167262d551a6754f72cd0733ad6fc409..b904565a64b128c942e255eae89ed37b52e3c8b9 100644 (file)
@@ -54,6 +54,8 @@ import com.vaadin.shared.ui.grid.GridState;
 import com.vaadin.shared.ui.grid.HeightMode;
 import com.vaadin.shared.ui.grid.SectionState;
 import com.vaadin.shared.util.SharedUtil;
+import com.vaadin.ui.Grid.FooterRow;
+import com.vaadin.ui.components.grid.Footer;
 import com.vaadin.ui.components.grid.Header;
 import com.vaadin.ui.components.grid.Header.Row;
 import com.vaadin.ui.renderers.AbstractRenderer;
@@ -1562,6 +1564,57 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents {
         public void setText(String text);
     }
 
+    /**
+     * A footer row in a Grid.
+     */
+    public interface FooterRow extends Serializable {
+
+        /**
+         * Returns the cell on this row corresponding to the given column id.
+         *
+         * @param columnId
+         *            the id of the column whose footer cell to get, not null
+         * @return the footer cell
+         * @throws IllegalArgumentException
+         *             if there is no such column in the grid
+         */
+        public FooterCell getCell(String columnId);
+
+        /**
+         * Returns the cell on this row corresponding to the given column.
+         *
+         * @param column
+         *            the column whose footer cell to get, not null
+         * @return the footer cell
+         * @throws IllegalArgumentException
+         *             if there is no such column in the grid
+         */
+        public default FooterCell getCell(Column<?, ?> column) {
+            return getCell(column.getId());
+        }
+    }
+
+    /**
+     * An individual cell on a Grid footer row.
+     */
+    public interface FooterCell extends Serializable {
+
+        /**
+         * Returns the textual caption of this cell.
+         *
+         * @return the footer caption
+         */
+        public String getText();
+
+        /**
+         * Sets the textual caption of this cell.
+         *
+         * @param text
+         *            the footer caption to set, not null
+         */
+        public void setText(String text);
+    }
+
     private class HeaderImpl extends Header {
 
         @Override
@@ -1575,6 +1628,19 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents {
         }
     };
 
+    private class FooterImpl extends Footer {
+
+        @Override
+        protected SectionState getState(boolean markAsDirty) {
+            return Grid.this.getState(markAsDirty).footer;
+        }
+
+        @Override
+        protected Collection<Column<T, ?>> getColumns() {
+            return Grid.this.getColumns();
+        }
+    };
+
     private Set<Column<T, ?>> columnSet = new LinkedHashSet<>();
     private Map<String, Column<T, ?>> columnKeys = new HashMap<>();
 
@@ -1585,6 +1651,7 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents {
     private DescriptionGenerator<T> descriptionGenerator;
 
     private Header header = new HeaderImpl();
+    private Footer footer = new FooterImpl();
 
     private int counter = 0;
 
@@ -2155,6 +2222,128 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents {
         return header;
     }
 
+    /**
+     * Returns the footer row at the given index.
+     *
+     * @param index
+     *            the index of the row, where the topmost row has index zero
+     * @return the footer row at the index
+     * @throws IndexOutOfBoundsException
+     *             if {@code rowIndex < 0 || rowIndex >= getFooterRowCount()}
+     */
+    public FooterRow getFooterRow(int index) {
+        return getFooter().getRow(index);
+    }
+
+    /**
+     * Gets the number of rows in the footer section.
+     *
+     * @return the number of footer rows
+     */
+    public int getFooterRowCount() {
+        return getFooter().getRowCount();
+    }
+
+    /**
+     * Inserts a new row at the given position to the footer section. Shifts the
+     * row currently at that position and any subsequent rows down (adds one to
+     * their indices). Inserting at {@link #getFooterRowCount()} appends the row
+     * at the bottom of the footer.
+     *
+     * @param index
+     *            the index at which to insert the row, where the topmost row
+     *            has index zero
+     * @return the inserted footer row
+     *
+     * @throws IndexOutOfBoundsException
+     *             if {@code rowIndex < 0 || rowIndex > getFooterRowCount()}
+     *
+     * @see #appendFooterRow()
+     * @see #prependFooterRow()
+     * @see #removeFooterRow(FooterRow)
+     * @see #removeFooterRow(int)
+     */
+    public FooterRow addFooterRowAt(int index) {
+        return getFooter().addRowAt(index);
+    }
+
+    /**
+     * Adds a new row at the bottom of the footer section.
+     *
+     * @return the appended footer row
+     *
+     * @see #prependFooterRow()
+     * @see #addFooterRowAt(int)
+     * @see #removeFooterRow(FooterRow)
+     * @see #removeFooterRow(int)
+     */
+    public FooterRow appendFooterRow() {
+        return addFooterRowAt(getFooterRowCount());
+    }
+
+    /**
+     * Adds a new row at the top of the footer section.
+     *
+     * @return the prepended footer row
+     *
+     * @see #appendFooterRow()
+     * @see #addFooterRowAt(int)
+     * @see #removeFooterRow(FooterRow)
+     * @see #removeFooterRow(int)
+     */
+    public FooterRow prependFooterRow() {
+        return addFooterRowAt(0);
+    }
+
+    /**
+     * Removes the given row from the footer section. Removing a default row
+     * sets the Grid to have no default row.
+     *
+     * @param row
+     *            the footer row to be removed, not null
+     *
+     * @throws IllegalArgumentException
+     *             if the footer does not contain the row
+     *
+     * @see #removeFooterRow(int)
+     * @see #addFooterRowAt(int)
+     * @see #appendFooterRow()
+     * @see #prependFooterRow()
+     */
+    public void removeFooterRow(FooterRow row) {
+        getFooter().removeRow(row);
+    }
+
+    /**
+     * Removes the row at the given position from the footer section.
+     *
+     * @param index
+     *            the index of the row to remove, where the topmost row has
+     *            index zero
+     *
+     * @throws IndexOutOfBoundsException
+     *             if {@code index < 0 || index >= getFooterRowCount()}
+     *
+     * @see #removeFooterRow(FooterRow)
+     * @see #addFooterRowAt(int)
+     * @see #appendFooterRow()
+     * @see #prependFooterRow()
+     */
+    public void removeFooterRow(int index) {
+        getFooter().removeRow(index);
+    }
+
+    /**
+     * Returns the footer section of this grid. The default footer contains a
+     * single row, set as the {@linkplain #setDefaultFooterRow(FooterRow)
+     * default row}.
+     *
+     * @return the footer section
+     */
+    protected Footer getFooter() {
+        return footer;
+    }
+
     /**
      * Registers a new column reorder listener.
      *
diff --git a/server/src/main/java/com/vaadin/ui/components/grid/Footer.java b/server/src/main/java/com/vaadin/ui/components/grid/Footer.java
new file mode 100644 (file)
index 0000000..9423e2d
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * 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.ui.components.grid;
+
+import com.vaadin.ui.Grid;
+
+/**
+ * Represents the footer section of a Grid.
+ *
+ * @author Vaadin Ltd.
+ *
+ * @since 8.0
+ */
+public abstract class Footer extends StaticSection<Footer.Row> {
+
+    /**
+     * A row in a Grid Footer.
+     */
+    public class Row extends StaticSection.StaticRow<Row.Cell>
+            implements Grid.FooterRow {
+
+        /**
+         * A cell in a Grid footer row.
+         */
+        public class Cell extends StaticSection.StaticCell
+                implements Grid.FooterCell {
+            /**
+             * Creates a new footer cell.
+             */
+            protected Cell() {
+                super(Row.this);
+            }
+        }
+
+        /**
+         * Creates a new footer row.
+         */
+        protected Row() {
+            super(Footer.this);
+        }
+
+        @Override
+        protected Cell createCell() {
+            return new Cell();
+        }
+
+        @Override
+        protected String getCellTagName() {
+            return "td";
+        }
+    }
+
+    @Override
+    public Row createRow() {
+        return new Row();
+    }
+}
index 6c9631b59835c886a10c62c8b899b711ef1e72ca..a8abf82c759dfd6e4088e19779db8449c4fa70d5 100644 (file)
@@ -102,6 +102,9 @@ public class GridState extends AbstractSingleSelectState {
     /** The state of the header section. */
     public SectionState header = new SectionState();
 
+    /** The state of the footer section. */
+    public SectionState footer = new SectionState();
+
     /**
      * Column order in grid.
      */
index 9fce271c6d9edeccc263e5c610429566d1351a8c..f56767e3d3c97ce4c8e2d79e27e91adc7c1cca80 100644 (file)
@@ -20,6 +20,7 @@ import com.vaadin.ui.Component;
 import com.vaadin.ui.Grid;
 import com.vaadin.ui.Grid.Column;
 import com.vaadin.ui.Grid.DetailsGenerator;
+import com.vaadin.ui.Grid.FooterRow;
 import com.vaadin.ui.Grid.HeaderRow;
 import com.vaadin.ui.Label;
 import com.vaadin.ui.MenuBar;
@@ -199,6 +200,7 @@ public class GridBasics extends AbstractReindeerTestUIWithLog {
         createDetailsMenu(componentMenu.addItem("Details", null));
         createBodyMenu(componentMenu.addItem("Body rows", null));
         createHeaderMenu(componentMenu.addItem("Header", null));
+        createFooterMenu(componentMenu.addItem("Footer", null));
         createColumnsMenu(componentMenu.addItem("Columns", null));
         return menu;
     }
@@ -409,6 +411,35 @@ public class GridBasics extends AbstractReindeerTestUIWithLog {
         });
     }
 
+    private void createFooterMenu(MenuItem footerMenu) {
+        footerMenu.addItem("Add default footer row", menuItem -> {
+            FooterRow defaultFooter = grid.appendFooterRow();
+            grid.getColumns().forEach(
+                    column -> defaultFooter.getCell(column).setText(grid
+                            .getDefaultHeaderRow().getCell(column).getText()));
+            footerMenu.removeChild(menuItem);
+        });
+        footerMenu.addItem("Append footer row", menuItem -> {
+            FooterRow row = grid.appendFooterRow();
+
+            int i = 0;
+            for (Column<?, ?> column : grid.getColumns()) {
+                row.getCell(column).setText("Footer cell " + i++);
+            }
+        });
+        footerMenu.addItem("Prepend footer row", menuItem -> {
+            FooterRow row = grid.prependFooterRow();
+
+            int i = 0;
+            for (Column<?, ?> column : grid.getColumns()) {
+                row.getCell(column).setText("Footer cell " + i++);
+            }
+        });
+        footerMenu.addItem("Remove first footer row", menuItem -> {
+            grid.removeFooterRow(0);
+        });
+    }
+
     /* DetailsGenerator related things */
 
     private void createDetailsMenu(MenuItem detailsMenu) {
index 5b594a980d1393be476ed96000c640ae77869f45..681da7443fdcdae5972c6085caf692dc6c77ca18 100644 (file)
@@ -31,8 +31,18 @@ public class GridHeaderFooterTest extends GridBasicsTest {
 
     protected static final String[] HEADER_TEXTS = IntStream
             .range(0, GridBasics.COLUMN_CAPTIONS.length)
-            .mapToObj(i -> "Header cell " + i)
-            .toArray(String[]::new);
+            .mapToObj(i -> "Header cell " + i).toArray(String[]::new);
+
+    protected static final String[] FOOTER_TEXTS = IntStream
+            .range(0, GridBasics.COLUMN_CAPTIONS.length)
+            .mapToObj(i -> "Footer cell " + i).toArray(String[]::new);
+
+    @Override
+    public void setUp() {
+        super.setUp();
+
+        selectMenuPath("Component", "Footer", "Add default footer row");
+    }
 
     @Test
     public void initialState_defaultHeaderPresent() {
@@ -103,16 +113,40 @@ public class GridHeaderFooterTest extends GridBasicsTest {
         assertNoSortIndicator(headerCell, "sort-desc");
     }
 
+    @Test
+    public void initialState_defaultFooterPresent() {
+        assertEquals(1, getGridElement().getFooterCount());
+        assertFooterTexts(0, GridBasics.COLUMN_CAPTIONS);
+    }
+
+    @Test
+    public void appendFooterRow_addedToBottom() {
+        selectMenuPath("Component", "Footer", "Append footer row");
+
+        assertEquals(2, getGridElement().getFooterCount());
+        assertFooterTexts(0, GridBasics.COLUMN_CAPTIONS);
+        assertFooterTexts(1, FOOTER_TEXTS);
+    }
+
+    @Test
+    public void prependFooterRow_addedToTop() {
+        selectMenuPath("Component", "Footer", "Prepend footer row");
+
+        assertEquals(2, getGridElement().getFooterCount());
+        assertFooterTexts(0, FOOTER_TEXTS);
+        assertFooterTexts(1, GridBasics.COLUMN_CAPTIONS);
+    }
+
     protected static void assertText(String expected, GridCellElement e) {
         // TBE.getText returns "" if the element is scrolled out of view
-        String actual = e.findElement(By.tagName("div")).getAttribute(
-                "innerHTML");
+        String actual = e.findElement(By.tagName("div"))
+                .getAttribute("innerHTML");
         assertEquals(expected, actual);
     }
 
     protected void assertHeaderTexts(int rowIndex, String[] texts) {
-        List<GridCellElement> headerCells = getGridElement().getHeaderCells(
-                rowIndex);
+        List<GridCellElement> headerCells = getGridElement()
+                .getHeaderCells(rowIndex);
 
         assertEquals(texts.length, headerCells.size());
         for (int i = 0; i < headerCells.size(); i++) {
@@ -120,9 +154,19 @@ public class GridHeaderFooterTest extends GridBasicsTest {
         }
     }
 
+    protected void assertFooterTexts(int rowIndex, String[] texts) {
+        List<GridCellElement> footerCells = getGridElement()
+                .getFooterCells(rowIndex);
+
+        assertEquals(texts.length, footerCells.size());
+        for (int i = 0; i < footerCells.size(); i++) {
+            assertText(texts[i], footerCells.get(i));
+        }
+    }
+
     protected void assertSortIndicator(GridCellElement cell, String classname) {
-        assertTrue("Header cell should have sort indicator " + classname, cell
-                .getAttribute("class").contains(classname));
+        assertTrue("Header cell should have sort indicator " + classname,
+                cell.getAttribute("class").contains(classname));
     }
 
     protected void assertNoSortIndicator(GridCellElement cell,