From aca2902c9cafcb02db19a0cdd42b1488786c0f56 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Johannes=20Dahlstr=C3=B6m?= Date: Tue, 15 Jul 2014 20:44:59 +0300 Subject: [PATCH] Client-side Grid header/footer rewrite: add add/remove rows support (#13334) Currently supported: * Adding and removal of header and footer rows * Header is single-row by default * Footer is zero-row by default * Text captions TODO: * Column spanning * HTML content * Widget content * Component content * Sorting/Indicators * Server side API * Shared state handling Change-Id: I54b5062f31e38e872ca64394dfa02f866a1af202 --- .../src/com/vaadin/client/ui/grid/Grid.java | 12 +- .../com/vaadin/client/ui/grid/GridFooter.java | 12 +- .../com/vaadin/client/ui/grid/GridHeader.java | 12 +- .../client/ui/grid/GridStaticSection.java | 143 ++++++++++++++++-- .../grid/basicfeatures/GridFooterTest.java | 52 ++++++- .../grid/basicfeatures/GridHeaderTest.java | 76 +++++++--- .../basicfeatures/GridStaticSectionTest.java | 52 +++++++ .../client/grid/GridBasicClientFeatures.java | 89 ++++++++++- 8 files changed, 382 insertions(+), 66 deletions(-) create mode 100644 uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridStaticSectionTest.java diff --git a/client/src/com/vaadin/client/ui/grid/Grid.java b/client/src/com/vaadin/client/ui/grid/Grid.java index 4465cd0070..18c1cbae01 100644 --- a/client/src/com/vaadin/client/ui/grid/Grid.java +++ b/client/src/com/vaadin/client/ui/grid/Grid.java @@ -45,7 +45,6 @@ import com.vaadin.client.Util; import com.vaadin.client.data.DataChangeHandler; import com.vaadin.client.data.DataSource; import com.vaadin.client.ui.SubPartAware; -import com.vaadin.client.ui.grid.GridStaticSection.StaticRow; import com.vaadin.client.ui.grid.renderers.ComplexRenderer; import com.vaadin.client.ui.grid.renderers.TextRenderer; import com.vaadin.client.ui.grid.renderers.WidgetRenderer; @@ -1311,7 +1310,8 @@ public class Grid extends Composite implements @Override public void update(Row row, Iterable cellsToUpdate) { - StaticRow gridRow = section.getRow(row.getRow()); + GridStaticSection.StaticRow gridRow = section.getRow(row + .getRow()); final List columnIndices = getVisibleColumnIndices(); @@ -1365,8 +1365,10 @@ public class Grid extends Composite implements escalator.getBody().setEscalatorUpdater(createBodyUpdater()); escalator.getFooter().setEscalatorUpdater(createFooterUpdater()); - refreshHeader(); - refreshFooter(); + header.setGrid(this); + header.appendRow(); + + footer.setGrid(this); setSelectionMode(SelectionMode.SINGLE); @@ -1574,7 +1576,7 @@ public class Grid extends Composite implements GridStaticSection section) { // Add or Remove rows on demand - int rowDiff = section.getRows().size() - rows.getRowCount(); + int rowDiff = section.getRowCount() - rows.getRowCount(); if (rowDiff > 0) { rows.insertRows(0, rowDiff); } else if (rowDiff < 0) { diff --git a/client/src/com/vaadin/client/ui/grid/GridFooter.java b/client/src/com/vaadin/client/ui/grid/GridFooter.java index dc0f0054a2..7f478f7d29 100644 --- a/client/src/com/vaadin/client/ui/grid/GridFooter.java +++ b/client/src/com/vaadin/client/ui/grid/GridFooter.java @@ -15,9 +15,6 @@ */ package com.vaadin.client.ui.grid; -import java.util.Collections; -import java.util.List; - /** * Represents the footer section of a Grid. The footer is always empty. * @@ -38,8 +35,7 @@ public class GridFooter extends GridStaticSection { * A single row in a grid Footer section. * */ - public static class FooterRow extends - GridStaticSection.StaticRow { + public class FooterRow extends GridStaticSection.StaticRow { @Override protected FooterCell createCell() { @@ -51,7 +47,7 @@ public class GridFooter extends GridStaticSection { * A single cell in a grid Footer row. Has a textual caption. * */ - public static class FooterCell extends GridStaticSection.StaticCell { + public class FooterCell extends GridStaticSection.StaticCell { } @Override @@ -60,7 +56,7 @@ public class GridFooter extends GridStaticSection { } @Override - protected List createRowList() { - return Collections.emptyList(); + protected void refreshGrid() { + getGrid().refreshFooter(); } } diff --git a/client/src/com/vaadin/client/ui/grid/GridHeader.java b/client/src/com/vaadin/client/ui/grid/GridHeader.java index c23c848b8d..a2207c49c7 100644 --- a/client/src/com/vaadin/client/ui/grid/GridHeader.java +++ b/client/src/com/vaadin/client/ui/grid/GridHeader.java @@ -15,9 +15,6 @@ */ package com.vaadin.client.ui.grid; -import java.util.Arrays; -import java.util.List; - /** * Represents the header section of a Grid. A header consists of a single header * row containing a header cell for each column. Each cell has a simple textual @@ -42,8 +39,7 @@ public class GridHeader extends GridStaticSection { * A single row in a grid header section. * */ - public static class HeaderRow extends - GridStaticSection.StaticRow { + public class HeaderRow extends GridStaticSection.StaticRow { @Override protected HeaderCell createCell() { @@ -55,7 +51,7 @@ public class GridHeader extends GridStaticSection { * A single cell in a grid header row. Has a textual caption. * */ - public static class HeaderCell extends GridStaticSection.StaticCell { + public class HeaderCell extends GridStaticSection.StaticCell { } @Override @@ -64,7 +60,7 @@ public class GridHeader extends GridStaticSection { } @Override - protected List createRowList() { - return Arrays.asList(createRow()); + protected void refreshGrid() { + getGrid().refreshHeader(); } } diff --git a/client/src/com/vaadin/client/ui/grid/GridStaticSection.java b/client/src/com/vaadin/client/ui/grid/GridStaticSection.java index 3273c2dfa2..5b4523ab76 100644 --- a/client/src/com/vaadin/client/ui/grid/GridStaticSection.java +++ b/client/src/com/vaadin/client/ui/grid/GridStaticSection.java @@ -18,7 +18,6 @@ package com.vaadin.client.ui.grid; import java.util.ArrayList; import java.util.List; -import com.vaadin.client.ui.grid.GridStaticSection.StaticRow; import com.vaadin.client.ui.grid.renderers.TextRenderer; /** @@ -29,7 +28,7 @@ import com.vaadin.client.ui.grid.renderers.TextRenderer; * @param * the type of the rows in the section */ -abstract class GridStaticSection> { +abstract class GridStaticSection> { /** * A header or footer cell. Has a simple textual caption. @@ -42,6 +41,8 @@ abstract class GridStaticSection> { private String text = ""; + private GridStaticSection section; + /** * Sets the text displayed in this cell. * @@ -50,6 +51,7 @@ abstract class GridStaticSection> { */ public void setText(String text) { this.text = text; + section.refreshGrid(); } /** @@ -60,6 +62,16 @@ abstract class GridStaticSection> { public String getText() { return text; } + + protected GridStaticSection getSection() { + assert section != null; + return section; + } + + protected void setSection(GridStaticSection section) { + this.section = section; + } + } /** @@ -74,6 +86,8 @@ abstract class GridStaticSection> { private Renderer renderer = new TextRenderer(); + private GridStaticSection section; + /** * Returns the cell at the given position in this row. * @@ -88,7 +102,9 @@ abstract class GridStaticSection> { } protected void addCell(int index) { - cells.add(index, createCell()); + CELLTYPE cell = createCell(); + cell.setSection(getSection()); + cells.add(index, cell); } protected void removeCell(int index) { @@ -100,16 +116,109 @@ abstract class GridStaticSection> { } protected abstract CELLTYPE createCell(); + + protected GridStaticSection getSection() { + return section; + } + + protected void setSection(GridStaticSection section) { + this.section = section; + } + } + + private Grid grid; + + private List rows = new ArrayList(); + + /** + * Creates and returns a new instance of the row type. + * + * @return the created row + */ + protected abstract ROWTYPE createRow(); + + /** + * Informs the grid that this section should be re-rendered. + */ + protected abstract void refreshGrid(); + + /** + * Inserts a new row at the given position. + * + * @param index + * the position at which to insert the row + * @return the new row + * + * @throws IndexOutOfBoundsException + * if the index is out of bounds + */ + public ROWTYPE addRow(int index) { + ROWTYPE row = createRow(); + row.setSection(this); + for (int i = 0; i < getGrid().getColumnCount(); ++i) { + row.addCell(i); + } + rows.add(index, row); + refreshGrid(); + return row; + } + + /** + * Adds a new row at the top of this section. + * + * @return the new row + */ + public ROWTYPE prependRow() { + return addRow(0); + } + + /** + * Adds a new row at the bottom of this section. + * + * @return the new row + */ + public ROWTYPE appendRow() { + return addRow(rows.size()); + } + + /** + * Removes the row at the given position. + * + * @param index + * the position of the row + * + * @throws IndexOutOfBoundsException + * if the index is out of bounds + */ + public void removeRow(int index) { + rows.remove(index); + refreshGrid(); } - private List rows = createRowList(); + /** + * Removes the given row from the section. + * + * @param row + * the row to be removed + * + * @throws IllegalArgumentException + * if the row does not exist in this section + */ + public void removeRow(ROWTYPE row) { + if (!rows.remove(row)) { + throw new IllegalArgumentException( + "Section does not contain the given row"); + } + refreshGrid(); + } /** - * Returns the row at the given position in this section. + * Returns the row at the given position. * * @param index * the position of the row - * @return the row at the index + * @return the row with the given index + * * @throws IndexOutOfBoundsException * if the index is out of bounds */ @@ -117,25 +226,37 @@ abstract class GridStaticSection> { return rows.get(index); } + /** + * Returns the number of rows in this section. + * + * @return the number of rows + */ + public int getRowCount() { + return rows.size(); + } + protected List getRows() { return rows; } protected void addColumn(GridColumn column, int index) { - for (ROWTYPE row : getRows()) { + for (ROWTYPE row : rows) { row.addCell(index); } } protected void removeColumn(int index) { - for (ROWTYPE row : getRows()) { + for (ROWTYPE row : rows) { row.removeCell(index); } } - protected List createRowList() { - return new ArrayList(); + protected void setGrid(Grid grid) { + this.grid = grid; } - protected abstract ROWTYPE createRow(); + protected Grid getGrid() { + assert grid != null; + return grid; + } } diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridFooterTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridFooterTest.java index e126994f34..80110ddc81 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridFooterTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridFooterTest.java @@ -17,20 +17,58 @@ package com.vaadin.tests.components.grid.basicfeatures; import static org.junit.Assert.assertEquals; -import java.util.List; - import org.junit.Test; -import com.vaadin.testbench.TestBenchElement; - -public class GridFooterTest extends GridBasicClientFeaturesTest { +public class GridFooterTest extends GridStaticSectionTest { @Test public void testFooterVisibility() throws Exception { openTestURL(); // Footer should have zero rows by default - List cells = getGridFooterRowCells(); - assertEquals(0, cells.size()); + assertEquals(0, getGridFooterRowCells().size()); + } + + @Test + public void testAddRows() throws Exception { + openTestURL(); + + selectMenuPath("Component", "Footer", "Append row"); + + assertFooterCount(1); + assertFooterTexts(0, 0); + + selectMenuPath("Component", "Footer", "Prepend row"); + + assertFooterCount(2); + assertFooterTexts(1, 0); + assertFooterTexts(0, 1); + + selectMenuPath("Component", "Footer", "Append row"); + + assertFooterCount(3); + assertFooterTexts(1, 0); + assertFooterTexts(0, 1); + assertFooterTexts(2, 2); + } + + @Test + public void testRemoveRows() throws Exception { + openTestURL(); + + selectMenuPath("Component", "Footer", "Prepend row"); + selectMenuPath("Component", "Footer", "Append row"); + + selectMenuPath("Component", "Footer", "Remove top row"); + + assertFooterCount(1); + assertFooterTexts(1, 0); + + selectMenuPath("Component", "Footer", "Remove bottom row"); + assertFooterCount(0); + } + + private void assertFooterCount(int count) { + assertEquals("footer count", count, getGridElement().getFooterCount()); } } diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridHeaderTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridHeaderTest.java index 716e3b30fc..c1bc4cdd73 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridHeaderTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridHeaderTest.java @@ -23,28 +23,21 @@ import org.junit.Test; import com.vaadin.testbench.TestBenchElement; -public class GridHeaderTest extends GridBasicClientFeaturesTest { +public class GridHeaderTest extends GridStaticSectionTest { @Test public void testHeaderVisibility() throws Exception { openTestURL(); // Column headers should be visible by default - List cells = getGridHeaderRowCells(); - assertEquals(GridBasicFeatures.COLUMNS, cells.size()); + assertEquals(GridBasicFeatures.COLUMNS, getGridHeaderRowCells().size()); } @Test public void testHeaderCaptions() throws Exception { openTestURL(); - List cells = getGridHeaderRowCells(); - - int i = 0; - for (TestBenchElement cell : cells) { - assertText("Column " + i, cell); - i++; - } + assertHeaderTexts(0, 0); } @Test @@ -57,23 +50,66 @@ public class GridHeaderTest extends GridBasicClientFeaturesTest { List cells = getGridHeaderRowCells(); assertEquals(GridBasicFeatures.COLUMNS - 2, cells.size()); - assertText("Column 0", cells.get(0)); - assertText("Column 2", cells.get(1)); - assertText("Column 4", cells.get(2)); + assertText("Header (0,0)", cells.get(0)); + assertText("Header (0,2)", cells.get(1)); + assertText("Header (0,4)", cells.get(2)); selectMenuPath("Component", "Columns", "Column 3", "Visible"); cells = getGridHeaderRowCells(); assertEquals(GridBasicFeatures.COLUMNS - 1, cells.size()); - assertText("Column 0", cells.get(0)); - assertText("Column 2", cells.get(1)); - assertText("Column 3", cells.get(2)); - assertText("Column 4", cells.get(3)); + assertText("Header (0,0)", cells.get(0)); + assertText("Header (0,2)", cells.get(1)); + assertText("Header (0,3)", cells.get(2)); + assertText("Header (0,4)", cells.get(3)); + } + + @Test + public void testAddRows() throws Exception { + openTestURL(); + + selectMenuPath("Component", "Header", "Append row"); + + assertHeaderCount(2); + assertHeaderTexts(0, 0); + assertHeaderTexts(1, 1); + + selectMenuPath("Component", "Header", "Prepend row"); + + assertHeaderCount(3); + assertHeaderTexts(2, 0); + assertHeaderTexts(0, 1); + assertHeaderTexts(1, 2); + + selectMenuPath("Component", "Header", "Append row"); + + assertHeaderCount(4); + assertHeaderTexts(2, 0); + assertHeaderTexts(0, 1); + assertHeaderTexts(1, 2); + assertHeaderTexts(3, 3); + } + + @Test + public void testRemoveRows() throws Exception { + openTestURL(); + + selectMenuPath("Component", "Header", "Prepend row"); + selectMenuPath("Component", "Header", "Append row"); + + selectMenuPath("Component", "Header", "Remove top row"); + + assertHeaderCount(2); + assertHeaderTexts(0, 0); + assertHeaderTexts(2, 1); + + selectMenuPath("Component", "Header", "Remove bottom row"); + assertHeaderCount(1); + assertHeaderTexts(0, 0); } - private static void assertText(String text, TestBenchElement e) { - // TBE.getText returns "" if the element is scrolled out of view - assertEquals(text, e.getAttribute("innerHTML")); + private void assertHeaderCount(int count) { + assertEquals("header count", count, getGridElement().getHeaderCount()); } } diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridStaticSectionTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridStaticSectionTest.java new file mode 100644 index 0000000000..8f6739e16d --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridStaticSectionTest.java @@ -0,0 +1,52 @@ +/* + * Copyright 2000-2014 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.basicfeatures; + +import static org.junit.Assert.assertEquals; + +import com.vaadin.testbench.TestBenchElement; + +/** + * Abstract base class for header and footer tests. + * + * @since + * @author Vaadin Ltd + */ +public abstract class GridStaticSectionTest extends GridBasicClientFeaturesTest { + + protected void assertHeaderTexts(int headerId, int rowIndex) { + int i = 0; + for (TestBenchElement cell : getGridElement().getHeaderCells(rowIndex)) { + assertText(String.format("Header (%d,%d)", headerId, i), cell); + i++; + } + assertEquals("number of header columns", GridBasicFeatures.COLUMNS, i); + } + + protected void assertFooterTexts(int footerId, int rowIndex) { + int i = 0; + for (TestBenchElement cell : getGridElement().getFooterCells(rowIndex)) { + assertText(String.format("Footer (%d,%d)", footerId, i), cell); + i++; + } + assertEquals("number of footer columns", GridBasicFeatures.COLUMNS, i); + } + + protected static void assertText(String text, TestBenchElement e) { + // TBE.getText returns "" if the element is scrolled out of view + assertEquals(text, e.getAttribute("innerHTML")); + } +} diff --git a/uitest/src/com/vaadin/tests/widgetset/client/grid/GridBasicClientFeatures.java b/uitest/src/com/vaadin/tests/widgetset/client/grid/GridBasicClientFeatures.java index 182a5bfa29..8564b149d8 100644 --- a/uitest/src/com/vaadin/tests/widgetset/client/grid/GridBasicClientFeatures.java +++ b/uitest/src/com/vaadin/tests/widgetset/client/grid/GridBasicClientFeatures.java @@ -25,7 +25,10 @@ import com.vaadin.client.ui.grid.FlyweightCell; import com.vaadin.client.ui.grid.Grid; import com.vaadin.client.ui.grid.Grid.SelectionMode; import com.vaadin.client.ui.grid.GridColumn; -import com.vaadin.client.ui.grid.GridHeader.HeaderCell; +import com.vaadin.client.ui.grid.GridFooter; +import com.vaadin.client.ui.grid.GridFooter.FooterRow; +import com.vaadin.client.ui.grid.GridHeader; +import com.vaadin.client.ui.grid.GridHeader.HeaderRow; import com.vaadin.client.ui.grid.Renderer; import com.vaadin.client.ui.grid.datasources.ListDataSource; import com.vaadin.client.ui.grid.renderers.DateRenderer; @@ -193,12 +196,7 @@ public class GridBasicClientFeatures extends }); } - // Set captions to column headers - - for (int i = 0; i < COLUMNS; ++i) { - HeaderCell cell = grid.getHeader().getRow(0).getCell(i); - cell.setText("Column " + i); - } + setHeaderTexts(grid.getHeader().getRow(0)); // // Populate the menu @@ -206,6 +204,8 @@ public class GridBasicClientFeatures extends createStateMenu(); createColumnsMenu(); + createHeaderMenu(); + createFooterMenu(); grid.getElement().getStyle().setZIndex(0); add(grid); @@ -250,6 +250,81 @@ public class GridBasicClientFeatures extends } } + private int headerCounter = 0; + private int footerCounter = 0; + + private void setHeaderTexts(HeaderRow row) { + for (int i = 0; i < COLUMNS; ++i) { + row.getCell(i).setText("Header (" + headerCounter + "," + i + ")"); + } + headerCounter++; + } + + private void setFooterTexts(FooterRow row) { + for (int i = 0; i < COLUMNS; ++i) { + row.getCell(i).setText("Footer (" + footerCounter + "," + i + ")"); + } + footerCounter++; + } + + private void createHeaderMenu() { + final GridHeader header = grid.getHeader(); + addMenuCommand("Prepend row", new ScheduledCommand() { + @Override + public void execute() { + setHeaderTexts(header.prependRow()); + } + }, "Component", "Header"); + addMenuCommand("Append row", new ScheduledCommand() { + @Override + public void execute() { + setHeaderTexts(header.appendRow()); + } + }, "Component", "Header"); + addMenuCommand("Remove top row", new ScheduledCommand() { + @Override + public void execute() { + header.removeRow(0); + } + }, "Component", "Header"); + addMenuCommand("Remove bottom row", new ScheduledCommand() { + @Override + public void execute() { + header.removeRow(header.getRowCount() - 1); + } + }, "Component", "Header"); + } + + private void createFooterMenu() { + + final GridFooter footer = grid.getFooter(); + + addMenuCommand("Prepend row", new ScheduledCommand() { + @Override + public void execute() { + setFooterTexts(footer.prependRow()); + } + }, "Component", "Footer"); + addMenuCommand("Append row", new ScheduledCommand() { + @Override + public void execute() { + setFooterTexts(footer.appendRow()); + } + }, "Component", "Footer"); + addMenuCommand("Remove top row", new ScheduledCommand() { + @Override + public void execute() { + footer.removeRow(0); + } + }, "Component", "Footer"); + addMenuCommand("Remove bottom row", new ScheduledCommand() { + @Override + public void execute() { + footer.removeRow(footer.getRowCount() - 1); + } + }, "Component", "Footer"); + } + /** * Creates a a renderer for a {@link Renderers} */ -- 2.39.5