From 5bc6d1802e2da2c600fb8559474e86ec1b3b4bf7 Mon Sep 17 00:00:00 2001 From: Teemu Suo-Anttila Date: Tue, 18 Oct 2016 17:31:37 +0300 Subject: Implement basic footer support for Grid Change-Id: I3db51521320767a28bc3acd9586b1453764a15bc --- .../client/connectors/grid/GridConnector.java | 22 +++ server/src/main/java/com/vaadin/ui/Grid.java | 189 +++++++++++++++++++++ .../java/com/vaadin/ui/components/grid/Footer.java | 70 ++++++++ .../java/com/vaadin/shared/ui/grid/GridState.java | 3 + .../tests/components/grid/basics/GridBasics.java | 31 ++++ .../grid/basics/GridHeaderFooterTest.java | 60 ++++++- 6 files changed, 367 insertions(+), 8 deletions(-) create mode 100644 server/src/main/java/com/vaadin/ui/components/grid/Footer.java diff --git a/client/src/main/java/com/vaadin/client/connectors/grid/GridConnector.java b/client/src/main/java/com/vaadin/client/connectors/grid/GridConnector.java index 4686b3bd21..a19c955fc4 100644 --- a/client/src/main/java/com/vaadin/client/connectors/grid/GridConnector.java +++ b/client/src/main/java/com/vaadin/client/connectors/grid/GridConnector.java @@ -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 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 dataSource) { super.setDataSource(dataSource); diff --git a/server/src/main/java/com/vaadin/ui/Grid.java b/server/src/main/java/com/vaadin/ui/Grid.java index 843db35e16..b904565a64 100644 --- a/server/src/main/java/com/vaadin/ui/Grid.java +++ b/server/src/main/java/com/vaadin/ui/Grid.java @@ -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 extends AbstractSingleSelect 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 extends AbstractSingleSelect implements HasComponents { } }; + private class FooterImpl extends Footer { + + @Override + protected SectionState getState(boolean markAsDirty) { + return Grid.this.getState(markAsDirty).footer; + } + + @Override + protected Collection> getColumns() { + return Grid.this.getColumns(); + } + }; + private Set> columnSet = new LinkedHashSet<>(); private Map> columnKeys = new HashMap<>(); @@ -1585,6 +1651,7 @@ public class Grid extends AbstractSingleSelect implements HasComponents { private DescriptionGenerator descriptionGenerator; private Header header = new HeaderImpl(); + private Footer footer = new FooterImpl(); private int counter = 0; @@ -2155,6 +2222,128 @@ public class Grid extends AbstractSingleSelect 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 index 0000000000..9423e2d842 --- /dev/null +++ b/server/src/main/java/com/vaadin/ui/components/grid/Footer.java @@ -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 { + + /** + * A row in a Grid Footer. + */ + public class Row extends StaticSection.StaticRow + 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(); + } +} diff --git a/shared/src/main/java/com/vaadin/shared/ui/grid/GridState.java b/shared/src/main/java/com/vaadin/shared/ui/grid/GridState.java index 6c9631b598..a8abf82c75 100644 --- a/shared/src/main/java/com/vaadin/shared/ui/grid/GridState.java +++ b/shared/src/main/java/com/vaadin/shared/ui/grid/GridState.java @@ -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. */ diff --git a/uitest/src/main/java/com/vaadin/tests/components/grid/basics/GridBasics.java b/uitest/src/main/java/com/vaadin/tests/components/grid/basics/GridBasics.java index 9fce271c6d..f56767e3d3 100644 --- a/uitest/src/main/java/com/vaadin/tests/components/grid/basics/GridBasics.java +++ b/uitest/src/main/java/com/vaadin/tests/components/grid/basics/GridBasics.java @@ -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) { diff --git a/uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridHeaderFooterTest.java b/uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridHeaderFooterTest.java index 5b594a980d..681da7443f 100644 --- a/uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridHeaderFooterTest.java +++ b/uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridHeaderFooterTest.java @@ -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 headerCells = getGridElement().getHeaderCells( - rowIndex); + List 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 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, -- cgit v1.2.3