aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJohannes Dahlström <johannesd@vaadin.com>2016-09-21 13:08:16 +0300
committerVaadin Code Review <review@vaadin.com>2016-09-27 14:14:38 +0000
commit680b7009d4f453dc8eec42975192094b2e0e8e2f (patch)
tree1b9685d189e29de82f4afdfff324044b8b7f461b
parent211dd5336e47f31556f3f6f42f11b7f7a62ec887 (diff)
downloadvaadin-framework-680b7009d4f453dc8eec42975192094b2e0e8e2f.tar.gz
vaadin-framework-680b7009d4f453dc8eec42975192094b2e0e8e2f.zip
Initial support for multiple headers in new Grid
Change-Id: I7a3fa34749322451ab5cef4465d4d7c76029c097
-rw-r--r--client/src/main/java/com/vaadin/client/connectors/grid/GridConnector.java51
-rw-r--r--server/src/main/java/com/vaadin/ui/Grid.java211
-rw-r--r--server/src/main/java/com/vaadin/ui/components/grid/Header.java57
-rw-r--r--server/src/main/java/com/vaadin/ui/components/grid/StaticSection.java307
-rw-r--r--server/src/test/java/com/vaadin/tests/server/component/grid/GridHeaderFooterTest.java155
-rw-r--r--shared/src/main/java/com/vaadin/shared/ui/grid/GridState.java3
-rw-r--r--shared/src/main/java/com/vaadin/shared/ui/grid/SectionState.java57
-rw-r--r--uitest/src/main/java/com/vaadin/tests/components/grid/GridColumnResizing.java2
-rw-r--r--uitest/src/main/java/com/vaadin/tests/components/grid/basics/GridBasics.java31
-rw-r--r--uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridHeaderFooterTest.java69
10 files changed, 925 insertions, 18 deletions
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 ed07d1d3f1..0da4ec3bcc 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
@@ -34,6 +34,7 @@ import com.vaadin.client.HasComponentsConnector;
import com.vaadin.client.MouseEventDetailsBuilder;
import com.vaadin.client.TooltipInfo;
import com.vaadin.client.WidgetUtil;
+import com.vaadin.client.annotations.OnStateChange;
import com.vaadin.client.connectors.AbstractListingConnector;
import com.vaadin.client.data.DataSource;
import com.vaadin.client.ui.SimpleManagedLayout;
@@ -49,6 +50,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.HeaderRow;
import com.vaadin.shared.MouseEventDetails;
import com.vaadin.shared.data.DataCommunicatorConstants;
import com.vaadin.shared.data.selection.SelectionModel;
@@ -59,6 +61,8 @@ import com.vaadin.shared.ui.grid.GridConstants;
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.SectionState;
+import com.vaadin.shared.ui.grid.SectionState.RowState;
import elemental.json.JsonObject;
@@ -102,6 +106,8 @@ public class GridConnector
/* Map to keep track of all added columns */
private Map<Column<?, JsonObject>, String> columnToIdMap = new HashMap<>();
+ private Map<String, Column<?, JsonObject>> idToColumn = new HashMap<>();
+
/* Child component list for HasComponentsConnector */
private List<ComponentConnector> childComponents;
private SpaceSelectHandler<JsonObject> spaceSelectHandler;
@@ -109,16 +115,27 @@ public class GridConnector
private ItemClickHandler itemClickHandler = new ItemClickHandler();
/**
- * Gets the string identifier of a {@link Column} in this grid.
+ * Gets the string identifier of the given column in this grid.
*
* @param column
- * the column for which the identifier is to be retrieved for
- * @return the string identifying the given column in this grid
+ * the column whose id to get
+ * @return the string id of the column
*/
- public String getColumnId(Grid.Column<?, ?> column) {
+ public String getColumnId(Column<?, ?> column) {
return columnToIdMap.get(column);
}
+ /**
+ * Gets the column corresponding to the given string identifier.
+ *
+ * @param columnId
+ * the id of the column to get
+ * @return the column with the given id
+ */
+ public Column<?, ?> getColumn(String columnId) {
+ return idToColumn.get(columnId);
+ }
+
@Override
@SuppressWarnings("unchecked")
public Grid<JsonObject> getWidget() {
@@ -202,6 +219,28 @@ public class GridConnector
layout();
}
+ @OnStateChange("header")
+ void updateHeader() {
+ final SectionState state = getState().header;
+ final Grid<JsonObject> grid = getWidget();
+
+ while (grid.getHeaderRowCount() > 0) {
+ grid.removeHeaderRow(0);
+ }
+
+ for (RowState rowState : state.rows) {
+ HeaderRow row = grid.appendHeaderRow();
+ rowState.cells.forEach((columnId, cellState) -> {
+ row.getCell(getColumn(columnId)).setText(cellState.text);
+ });
+ }
+
+ if (grid.getHeaderRowCount() > 0) {
+ // TODO Default header handling to be added in a later patch
+ grid.setDefaultHeaderRow(grid.getHeaderRow(0));
+ }
+ }
+
@Override
public void setDataSource(DataSource<JsonObject> dataSource) {
super.setDataSource(dataSource);
@@ -228,6 +267,7 @@ public class GridConnector
.containsValue(id) : "Column with given id already exists.";
getWidget().addColumn(column);
columnToIdMap.put(column, id);
+ idToColumn.put(id, column);
}
/**
@@ -241,7 +281,8 @@ public class GridConnector
assert columnToIdMap
.containsKey(column) : "Given Column does not exist.";
getWidget().removeColumn(column);
- columnToIdMap.remove(column);
+ String id = columnToIdMap.remove(column);
+ idToColumn.remove(id);
}
@Override
diff --git a/server/src/main/java/com/vaadin/ui/Grid.java b/server/src/main/java/com/vaadin/ui/Grid.java
index 94f8c9aee7..3777decbe9 100644
--- a/server/src/main/java/com/vaadin/ui/Grid.java
+++ b/server/src/main/java/com/vaadin/ui/Grid.java
@@ -49,6 +49,8 @@ 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 com.vaadin.shared.ui.grid.SectionState;
+import com.vaadin.ui.components.grid.Header;
import com.vaadin.ui.renderers.AbstractRenderer;
import com.vaadin.ui.renderers.Renderer;
import com.vaadin.ui.renderers.TextRenderer;
@@ -512,7 +514,7 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents {
if (rowKey != null) {
item = getDataCommunicator().getKeyMapper().get(rowKey);
}
- fireEvent(new GridContextClickEvent<T>(Grid.this, details, section,
+ fireEvent(new GridContextClickEvent<>(Grid.this, details, section,
rowIndex, item, getColumn(columnId)));
}
@@ -825,6 +827,15 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents {
}
/**
+ * Returns the identifier used with this Column in communication.
+ *
+ * @return the identifier string
+ */
+ public String getId() {
+ return getState(false).id;
+ }
+
+ /**
* Sets the identifier to use with this Column in communication.
*
* @param id
@@ -1408,6 +1419,53 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents {
}
}
+ /**
+ * A header row in a Grid.
+ */
+ public interface HeaderRow extends Serializable {
+
+ /**
+ * Returns the cell on this row corresponding to the given column id.
+ *
+ * @param columnId
+ * the id of the column whose header cell to get
+ * @return the header cell
+ */
+ public HeaderCell getCell(String columnId);
+
+ /**
+ * Returns the cell on this row corresponding to the given column.
+ *
+ * @param column
+ * the column whose header cell to get
+ * @return the header cell
+ */
+ public default HeaderCell getCell(Column<?, ?> column) {
+ return getCell(column.getId());
+ }
+ }
+
+ /**
+ * An individual cell on a Grid header row.
+ */
+ public interface HeaderCell extends Serializable {
+
+ /**
+ * Returns the textual caption of this cell.
+ *
+ * @return the header caption
+ */
+ public String getText();
+
+ /**
+ * Sets the textual caption of this cell.
+ *
+ * @param text
+ * the header caption to set
+ */
+ public void setText(String text);
+ }
+
private KeyMapper<Column<T, ?>> columnKeys = new KeyMapper<>();
private Set<Column<T, ?>> columnSet = new LinkedHashSet<>();
private List<SortOrder<Column<T, ?>>> sortOrder = new ArrayList<>();
@@ -1416,15 +1474,26 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents {
private StyleGenerator<T> styleGenerator = item -> null;
private DescriptionGenerator<T> descriptionGenerator;
+ private Header header = new Header() {
+ @Override
+ protected SectionState getState(boolean markAsDirty) {
+ return Grid.this.getState(markAsDirty).header;
+ }
+ };
+
/**
* Constructor for the {@link Grid} component.
*/
public Grid() {
setSelectionModel(new SingleSelection());
registerRpc(new GridServerRpcImpl());
+
+ appendHeaderRow();
+
detailsManager = new DetailsManager<>();
addExtension(detailsManager);
addDataGenerator(detailsManager);
+
addDataGenerator((item, json) -> {
String styleName = styleGenerator.apply(item);
if (styleName != null && !styleName.isEmpty()) {
@@ -1455,8 +1524,6 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents {
* the value provider
* @param renderer
* the column value class
- * @param <T>
- * the type of this grid
* @param <V>
* the column value type
*
@@ -1467,13 +1534,23 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents {
public <V> Column<T, V> addColumn(String caption,
Function<T, ? extends V> valueProvider,
AbstractRenderer<? super T, V> renderer) {
- Column<T, V> column = new Column<>(caption, valueProvider, renderer);
+ final Column<T, V> column = new Column<>(caption, valueProvider,
+ renderer);
+ final String columnId = columnKeys.key(column);
column.extend(this);
- column.setId(columnKeys.key(column));
+ column.setId(columnId);
columnSet.add(column);
addDataGenerator(column);
+ getHeader().addColumn(columnId);
+
+ if (getHeaderRowCount() > 0) {
+ // TODO Default header API to be added in a later patch
+ HeaderRow defaultHeader = getHeaderRow(0);
+ defaultHeader.getCell(columnId).setText(caption);
+ }
+
return column;
}
@@ -1762,6 +1839,130 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents {
return descriptionGenerator;
}
+ //
+ // HEADER AND FOOTER
+ //
+
+ /**
+ * Returns the header row at the given index.
+ *
+ * @param rowIndex
+ * the index of the row, where the topmost row has index zero
+ * @return the header row at the index
+ * @throws IndexOutOfBoundsException
+ * if {@code rowIndex < 0 || rowIndex >= getHeaderRowCount()}
+ */
+ public HeaderRow getHeaderRow(int rowIndex) {
+ return getHeader().getRow(rowIndex);
+ }
+
+ /**
+ * Gets the number of rows in the header section.
+ *
+ * @return the number of header rows
+ */
+ public int getHeaderRowCount() {
+ return header.getRowCount();
+ }
+
+ /**
+ * Inserts a new row at the given position to the header section. Shifts the
+ * row currently at that position and any subsequent rows down (adds one to
+ * their indices). Inserting at {@link #getHeaderRowCount()} appends the row
+ * at the bottom of the header.
+ *
+ * @param index
+ * the index at which to insert the row, where the topmost row
+ * has index zero
+ * @return the inserted header row
+ *
+ * @throws IndexOutOfBoundsException
+ * if {@code rowIndex < 0 || rowIndex > getHeaderRowCount()}
+ *
+ * @see #appendHeaderRow()
+ * @see #prependHeaderRow()
+ * @see #removeHeaderRow(HeaderRow)
+ * @see #removeHeaderRow(int)
+ */
+ public HeaderRow addHeaderRowAt(int index) {
+ return getHeader().addRowAt(index);
+ }
+
+ /**
+ * Adds a new row at the bottom of the header section.
+ *
+ * @return the appended header row
+ *
+ * @see #prependHeaderRow()
+ * @see #addHeaderRowAt(int)
+ * @see #removeHeaderRow(HeaderRow)
+ * @see #removeHeaderRow(int)
+ */
+ public HeaderRow appendHeaderRow() {
+ return addHeaderRowAt(getHeaderRowCount());
+ }
+
+ /**
+ * Adds a new row at the top of the header section.
+ *
+ * @return the prepended header row
+ *
+ * @see #appendHeaderRow()
+ * @see #addHeaderRowAt(int)
+ * @see #removeHeaderRow(HeaderRow)
+ * @see #removeHeaderRow(int)
+ */
+ public HeaderRow prependHeaderRow() {
+ return addHeaderRowAt(0);
+ }
+
+ /**
+ * Removes the given row from the header section.
+ *
+ * @param row
+ * the header row to be removed, not null
+ *
+ * @throws IllegalArgumentException
+ * if the header does not contain the row
+ *
+ * @see #removeHeaderRow(int)
+ * @see #addHeaderRowAt(int)
+ * @see #appendHeaderRow()
+ * @see #prependHeaderRow()
+ */
+ public void removeHeaderRow(HeaderRow row) {
+ getHeader().removeRow(row);
+ }
+
+ /**
+ * Removes the row at the given position from the header section.
+ *
+ * @param rowIndex
+ * the index of the row to remove, where the topmost row has
+ * index zero
+ *
+ * @throws IndexOutOfBoundsException
+ * if {@code rowIndex < 0 || rowIndex >= getHeaderRowCount()}
+ *
+ * @see #removeHeaderRow(HeaderRow)
+ * @see #addHeaderRowAt(int)
+ * @see #appendHeaderRow()
+ * @see #prependHeaderRow()
+ */
+ public void removeHeaderRow(int rowIndex) {
+ getHeader().removeRow(rowIndex);
+ }
+
+ /**
+ * Returns the header section of this grid. The default header contains a
+ * single row displaying the column captions.
+ *
+ * @return the header section
+ */
+ protected Header getHeader() {
+ return header;
+ }
+
/**
* Registers a new column resize listener.
*
diff --git a/server/src/main/java/com/vaadin/ui/components/grid/Header.java b/server/src/main/java/com/vaadin/ui/components/grid/Header.java
new file mode 100644
index 0000000000..8348795c10
--- /dev/null
+++ b/server/src/main/java/com/vaadin/ui/components/grid/Header.java
@@ -0,0 +1,57 @@
+/*
+ * 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 header section of a Grid.
+ */
+public abstract class Header extends StaticSection<Header.Row> {
+
+ public class Row extends StaticSection.StaticRow<Row.Cell>
+ implements Grid.HeaderRow {
+
+ public class Cell extends StaticSection.StaticCell implements
+ Grid.HeaderCell {
+ protected Cell() {
+ super(Row.this);
+ }
+ }
+
+ /**
+ * @param section
+ */
+ protected Row() {
+ super(Header.this);
+ }
+
+ @Override
+ protected Cell createCell() {
+ return new Cell();
+ }
+
+ @Override
+ protected String getCellTagName() {
+ return "th";
+ }
+ }
+
+ @Override
+ public Row createRow() {
+ return new Row();
+ }
+}
diff --git a/server/src/main/java/com/vaadin/ui/components/grid/StaticSection.java b/server/src/main/java/com/vaadin/ui/components/grid/StaticSection.java
new file mode 100644
index 0000000000..dbd5749bd0
--- /dev/null
+++ b/server/src/main/java/com/vaadin/ui/components/grid/StaticSection.java
@@ -0,0 +1,307 @@
+/*
+ * 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 java.io.Serializable;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import com.vaadin.shared.ui.grid.SectionState;
+import com.vaadin.shared.ui.grid.SectionState.CellState;
+import com.vaadin.shared.ui.grid.SectionState.RowState;
+
+/**
+ * Represents the header or footer section of a Grid.
+ *
+ * @author Vaadin Ltd.
+ *
+ * @param <ROW>
+ * the type of the rows in the section
+ *
+ * @since 8.0
+ */
+public abstract class StaticSection<ROW extends StaticSection.StaticRow<?>>
+ implements Serializable {
+
+ /**
+ * Abstract base class for Grid header and footer rows.
+ *
+ * @param <CELL>
+ * the type of the cells in the row
+ */
+ public abstract static class StaticRow<CELL extends StaticCell>
+ implements Serializable {
+
+ private RowState rowState = new RowState();
+ private StaticSection<?> section;
+ private Map<Object, CELL> cells = new LinkedHashMap<>();
+
+ /**
+ * Creates a new row belonging to the given section.
+ *
+ * @param section
+ * the section of the row
+ */
+ protected StaticRow(StaticSection<?> section) {
+ this.section = section;
+ }
+
+ /**
+ * Creates and returns a new instance of the cell type.
+ *
+ * @return the created cell
+ */
+ protected abstract CELL createCell();
+
+ /**
+ * Returns the declarative tag name used for the cells in this row.
+ *
+ * @return the cell tag name
+ */
+ protected abstract String getCellTagName();
+
+ /**
+ * Adds a cell to this section, corresponding to the given column id.
+ *
+ * @param columnId
+ * the id of the column for which to add a cell
+ */
+ protected void addCell(String columnId) {
+ CELL cell = createCell();
+ cell.setColumnId(columnId);
+ cells.put(columnId, cell);
+ rowState.cells.put(columnId, cell.getCellState());
+ }
+
+ /**
+ * Removes the cell from this section that corresponds to the given
+ * column id. If there is no such cell, does nothing.
+ *
+ * @param columnId
+ * the id of the column from which to remove the cell
+ */
+ protected void removeCell(Object columnId) {
+ CELL cell = cells.remove(columnId);
+ if (cell != null) {
+ rowState.cells.remove(cell.getCellState());
+ }
+ }
+
+ /**
+ * Returns the shared state of this row.
+ *
+ * @return the row state
+ */
+ protected RowState getRowState() {
+ return rowState;
+ }
+
+ /**
+ * Returns the cell in this section that corresponds to the given column
+ * id.
+ *
+ * @param columnId
+ * the id of the column
+ * @return the cell for the given column or null if not found
+ */
+ public CELL getCell(String columnId) {
+ CELL cell = cells.get(columnId);
+ return cell;
+ }
+ }
+
+ /**
+ * A header or footer cell. Has a simple textual caption.
+ */
+ abstract static class StaticCell implements Serializable {
+
+ private CellState cellState = new CellState();
+ private StaticRow<?> row;
+
+ protected StaticCell(StaticRow<?> row) {
+ this.row = row;
+ }
+
+ void setColumnId(String id) {
+ cellState.columnId = id;
+ }
+
+ String getColumnId() {
+ return cellState.columnId;
+ }
+
+ /**
+ * Gets the row where this cell is.
+ *
+ * @return row for this cell
+ */
+ public StaticRow<?> getRow() {
+ return row;
+ }
+
+ /**
+ * Returns the shared state of this cell.
+ *
+ * @return the cell state
+ */
+ protected CellState getCellState() {
+ return cellState;
+ }
+
+ /**
+ * Sets the textual caption of this cell.
+ *
+ * @param text
+ * a plain text caption, not null
+ */
+ public void setText(String text) {
+ Objects.requireNonNull(text, "text cannot be null");
+ cellState.text = text;
+ row.section.markAsDirty();
+ }
+
+ /**
+ * Returns the textual caption of this cell.
+ *
+ * @return the plain text caption
+ */
+ public String getText() {
+ return cellState.text;
+ }
+ }
+
+ private List<ROW> rows = new ArrayList<>();
+
+ /**
+ * Creates a new row instance.
+ *
+ * @return the new row
+ */
+ protected abstract ROW createRow();
+
+ /**
+ * Returns the shared state of this section.
+ *
+ * @param markAsDirty
+ * {@code true} to mark the state as modified, {@code false}
+ * otherwise
+ * @return the section state
+ */
+ protected abstract SectionState getState(boolean markAsDirty);
+
+ /**
+ * Marks the state of this section as modified.
+ */
+ protected void markAsDirty() {
+ getState(true);
+ }
+
+ /**
+ * Adds a new row at the given index.
+ *
+ * @param index
+ * the index of the new row
+ * @return the added row
+ * @throws IndexOutOfBoundsException
+ * if {@code index < 0 || index > getRowCount()}
+ */
+ public ROW addRowAt(int index) {
+ ROW row = createRow();
+ rows.add(index, row);
+ getState(true).rows.add(index, row.getRowState());
+ return row;
+ }
+
+ /**
+ * Removes the row at the given index.
+ *
+ * @param index
+ * the index of the row to remove
+ * @throws IndexOutOfBoundsException
+ * if {@code index < 0 || index >= getRowCount()}
+ */
+ public void removeRow(int index) {
+ rows.remove(index);
+ getState(true).rows.remove(index);
+ }
+
+ /**
+ * Removes the given row from this section.
+ *
+ * @param row
+ * the row to remove, not null
+ * @throws IllegalArgumentException
+ * if this section does not contain the row
+ */
+ public void removeRow(Object row) {
+ Objects.requireNonNull(row, "row cannot be null");
+ int index = rows.indexOf(row);
+ if (index < 0) {
+ throw new IllegalArgumentException(
+ "Section does not contain the given row");
+ }
+ removeRow(index);
+ }
+
+ /**
+ * Returns the row at the given index.
+ *
+ * @param index
+ * the index of the row
+ * @return the row at the index
+ * @throws IndexOutOfBoundsException
+ * if {@code index < 0 || index >= getRowCount()}
+ */
+ public ROW getRow(int index) {
+ return rows.get(index);
+ }
+
+ /**
+ * Returns the number of rows in this section.
+ *
+ * @return the number of rows
+ */
+ public int getRowCount() {
+ return rows.size();
+ }
+
+ /**
+ * Adds a cell corresponding to the given column id to this section.
+ *
+ * @param columnId
+ * the id of the column for which to add a cell
+ */
+ public void addColumn(String columnId) {
+ for (ROW row : rows) {
+ row.addCell(columnId);
+ }
+ }
+
+ /**
+ * Removes the cell corresponding to the given column id.
+ *
+ * @param columnId
+ * the id of the column whose cell to remove
+ */
+ public void removeColumn(String columnId) {
+ for (ROW row : rows) {
+ row.removeCell(columnId);
+ }
+ }
+}
diff --git a/server/src/test/java/com/vaadin/tests/server/component/grid/GridHeaderFooterTest.java b/server/src/test/java/com/vaadin/tests/server/component/grid/GridHeaderFooterTest.java
new file mode 100644
index 0000000000..87c2632b40
--- /dev/null
+++ b/server/src/test/java/com/vaadin/tests/server/component/grid/GridHeaderFooterTest.java
@@ -0,0 +1,155 @@
+/*
+ * 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.server.component.grid;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.vaadin.ui.Grid;
+import com.vaadin.ui.Grid.Column;
+import com.vaadin.ui.Grid.HeaderRow;
+
+public class GridHeaderFooterTest {
+
+ private Grid<String> grid;
+ private Column<?, ?> column1, column2;
+
+ @Before
+ public void setUp() {
+ grid = new Grid<>();
+
+ column1 = grid.addColumn("First", s -> s.substring(0, 1));
+ column2 = grid.addColumn("Rest", s -> s.substring(1));
+ }
+
+ @Test
+ public void initialState_hasDefaultHeader() {
+ assertEquals(1, grid.getHeaderRowCount());
+ HeaderRow defaultHeader = grid.getHeaderRow(0);
+ assertEquals("First", defaultHeader.getCell(column1).getText());
+ assertEquals("Rest", defaultHeader.getCell(column2).getText());
+ }
+
+ @Test
+ public void initialState_defaultHeaderRemovable() {
+ grid.removeHeaderRow(0);
+ assertEquals(0, grid.getHeaderRowCount());
+ }
+
+ @Test
+ public void appendHeaderRow_addedToBottom() {
+ HeaderRow defaultRow = grid.getHeaderRow(0);
+ HeaderRow addedRow = grid.appendHeaderRow();
+
+ assertSame(defaultRow, grid.getHeaderRow(0));
+ assertSame(addedRow, grid.getHeaderRow(1));
+ }
+
+ @Test
+ public void prependHeaderRow_addedToTop() {
+ HeaderRow defaultRow = grid.getHeaderRow(0);
+ HeaderRow addedRow = grid.prependHeaderRow();
+
+ assertSame(addedRow, grid.getHeaderRow(0));
+ assertSame(defaultRow, grid.getHeaderRow(1));
+ }
+
+ @Test
+ public void addHeaderRowAtZero_addedToTop() {
+ HeaderRow defaultRow = grid.getHeaderRow(0);
+ HeaderRow addedRow = grid.addHeaderRowAt(0);
+
+ assertSame(addedRow, grid.getHeaderRow(0));
+ assertSame(defaultRow, grid.getHeaderRow(1));
+ }
+
+ @Test
+ public void addHeaderRowAtRowCount_addedToBottom() {
+ HeaderRow defaultRow = grid.getHeaderRow(0);
+ HeaderRow addedRow = grid.addHeaderRowAt(grid.getHeaderRowCount());
+
+ assertSame(defaultRow, grid.getHeaderRow(0));
+ assertSame(addedRow, grid.getHeaderRow(1));
+ }
+
+ @Test
+ public void removeExistingHeaderRow_removed() {
+ HeaderRow defaultRow = grid.getHeaderRow(0);
+ HeaderRow addedRow = grid.appendHeaderRow();
+
+ grid.removeHeaderRow(addedRow);
+
+ assertEquals(1, grid.getHeaderRowCount());
+ assertSame(defaultRow, grid.getHeaderRow(0));
+ }
+
+ @Test
+ public void removeDefaultHeaderRow_removed() {
+ HeaderRow defaultRow = grid.getHeaderRow(0);
+ HeaderRow addedRow = grid.appendHeaderRow();
+
+ grid.removeHeaderRow(defaultRow);
+
+ assertEquals(1, grid.getHeaderRowCount());
+ assertSame(addedRow, grid.getHeaderRow(0));
+ }
+
+ @Test(expected = IndexOutOfBoundsException.class)
+ public void getHeaderRowNegativeIndex_throws() {
+ grid.getHeaderRow(-1);
+ }
+
+ @Test(expected = IndexOutOfBoundsException.class)
+ public void getHeaderRowIndexTooLarge_throws() {
+ grid.appendHeaderRow();
+ grid.getHeaderRow(2);
+ }
+
+ @Test(expected = IndexOutOfBoundsException.class)
+ public void addHeaderRowAtNegativeIndex_throws() {
+ grid.addHeaderRowAt(-1);
+ }
+
+ @Test(expected = IndexOutOfBoundsException.class)
+ public void addHeaderRowAtIndexTooLarge_throws() {
+ grid.addHeaderRowAt(2);
+ }
+
+ @Test(expected = IndexOutOfBoundsException.class)
+ public void removeHeaderRowNegativeIndex_throws() {
+ grid.removeHeaderRow(-1);
+ }
+
+ @Test(expected = IndexOutOfBoundsException.class)
+ public void removeHeaderRowIndexTooLarge_throws() {
+ grid.removeHeaderRow(1);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void removeNonExistingHeaderRow_throws() {
+ HeaderRow row = grid.getHeaderRow(0);
+ try {
+ grid.removeHeaderRow(row);
+ } catch (Exception e) {
+ fail("unexpected exception: " + e);
+ }
+ grid.removeHeaderRow(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 4f480139d5..6c9631b598 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
@@ -99,6 +99,9 @@ public class GridState extends AbstractSingleSelectState {
primaryStyleName = "v-grid";
}
+ /** The state of the header section. */
+ public SectionState header = new SectionState();
+
/**
* Column order in grid.
*/
diff --git a/shared/src/main/java/com/vaadin/shared/ui/grid/SectionState.java b/shared/src/main/java/com/vaadin/shared/ui/grid/SectionState.java
new file mode 100644
index 0000000000..55a8df99b8
--- /dev/null
+++ b/shared/src/main/java/com/vaadin/shared/ui/grid/SectionState.java
@@ -0,0 +1,57 @@
+/*
+ * 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.shared.ui.grid;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Shared state for Grid headers and footers.
+ *
+ * @author Vaadin Ltd
+ * @since 7.4
+ */
+public class SectionState implements Serializable {
+
+ /** The state of a header or footer row. */
+ public static class RowState implements Serializable {
+
+ /** The map from column ids to the cells in this row. */
+ public Map<String, CellState> cells = new HashMap<>();
+
+ /**
+ * Whether this row is the default header row. Always false for footer
+ * rows.
+ */
+ public boolean defaultHeader = false;
+ }
+
+ /** The state of a header or footer cell. */
+ public static class CellState implements Serializable {
+
+ /** The textual caption of this cell. */
+ public String text;
+
+ /** The id of the column that this cell belongs to. */
+ public String columnId;
+ }
+
+ /** The rows in this section. */
+ public List<RowState> rows = new ArrayList<>();
+}
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
index 67e6473850..a07550a20a 100644
--- a/uitest/src/main/java/com/vaadin/tests/components/grid/GridColumnResizing.java
+++ b/uitest/src/main/java/com/vaadin/tests/components/grid/GridColumnResizing.java
@@ -2,6 +2,7 @@ package com.vaadin.tests.components.grid;
import java.util.Arrays;
+import com.vaadin.annotations.Widgetset;
import com.vaadin.server.VaadinRequest;
import com.vaadin.tests.components.AbstractTestUI;
import com.vaadin.tests.data.bean.Person;
@@ -11,6 +12,7 @@ import com.vaadin.ui.Label;
import com.vaadin.ui.TextField;
import com.vaadin.ui.renderers.NumberRenderer;
+@Widgetset("com.vaadin.DefaultWidgetSet")
public class GridColumnResizing extends AbstractTestUI {
@Override
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 3a0646ed27..bf0aa3ea56 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
@@ -33,6 +33,8 @@ import com.vaadin.ui.renderers.ProgressBarRenderer;
@Widgetset("com.vaadin.DefaultWidgetSet")
public class GridBasics extends AbstractTestUIWithLog {
+ public static final String[] COLUMN_CAPTIONS = { "Column 0", "Column 1", "Column 2", "Row Number", "Date", "HTML String", "Big Random", "Small Random" };
+
public static final String ROW_STYLE_GENERATOR_ROW_NUMBERS_FOR_3_OF_4 = "Row numbers for 3/4";
public static final String ROW_STYLE_GENERATOR_NONE = "None";
public static final String ROW_STYLE_GENERATOR_ROW_NUMBERS = "Row numbers";
@@ -128,21 +130,21 @@ public class GridBasics extends AbstractTestUIWithLog {
grid = new Grid<>();
grid.setItems(data);
- grid.addColumn("Column 0",
+ grid.addColumn(COLUMN_CAPTIONS[0],
dataObj -> "(" + dataObj.getRowNumber() + ", 0)");
- grid.addColumn("Column 1",
+ grid.addColumn(COLUMN_CAPTIONS[1],
dataObj -> "(" + dataObj.getRowNumber() + ", 1)");
- grid.addColumn("Column 2",
+ grid.addColumn(COLUMN_CAPTIONS[2],
dataObj -> "(" + dataObj.getRowNumber() + ", 2)");
- grid.addColumn("Row Number", DataObject::getRowNumber,
+ grid.addColumn(COLUMN_CAPTIONS[3], DataObject::getRowNumber,
new NumberRenderer());
- grid.addColumn("Date", DataObject::getDate, new DateRenderer());
- grid.addColumn("HTML String", DataObject::getHtmlString,
+ grid.addColumn(COLUMN_CAPTIONS[4], DataObject::getDate, new DateRenderer());
+ grid.addColumn(COLUMN_CAPTIONS[5], DataObject::getHtmlString,
new HtmlRenderer());
- grid.addColumn("Big Random", DataObject::getBigRandom,
+ grid.addColumn(COLUMN_CAPTIONS[6], DataObject::getBigRandom,
new NumberRenderer());
- grid.addColumn("Small Random", data -> data.getSmallRandom() / 5d,
+ grid.addColumn(COLUMN_CAPTIONS[7], data -> data.getSmallRandom() / 5d,
new ProgressBarRenderer());
grid.addSelectionListener(e -> log("Selected: " + e.getValue()));
@@ -159,6 +161,7 @@ public class GridBasics extends AbstractTestUIWithLog {
createSizeMenu(componentMenu.addItem("Size", null));
createDetailsMenu(componentMenu.addItem("Details", null));
createBodyMenu(componentMenu.addItem("Body rows", null));
+ createHeaderMenu(componentMenu.addItem("Header", null));
return menu;
}
@@ -293,6 +296,18 @@ public class GridBasics extends AbstractTestUIWithLog {
});
}
+ private void createHeaderMenu(MenuItem headerMenu) {
+ headerMenu.addItem("Append header row", menuItem -> {
+ grid.appendHeaderRow();
+ });
+ headerMenu.addItem("Prepend header row", menuItem -> {
+ grid.prependHeaderRow();
+ });
+ headerMenu.addItem("Remove first header row", menuItem -> {
+ grid.removeHeaderRow(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
new file mode 100644
index 0000000000..c1a4cfc2b0
--- /dev/null
+++ b/uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridHeaderFooterTest.java
@@ -0,0 +1,69 @@
+/*
+ * 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 java.util.List;
+
+import org.junit.Test;
+
+import com.vaadin.testbench.By;
+import com.vaadin.testbench.elements.GridElement.GridCellElement;
+
+public class GridHeaderFooterTest extends GridBasicsTest {
+
+ @Test
+ public void initialState_defaultHeaderPresent() {
+ assertEquals(1, getGridElement().getHeaderCount());
+
+ final String[] captions = GridBasics.COLUMN_CAPTIONS;
+ List<GridCellElement> headerCells = getGridElement().getHeaderCells(0);
+
+ assertEquals(captions.length, headerCells.size());
+ for (int i = 0; i < headerCells.size(); i++) {
+ assertText(captions[i], headerCells.get(i));
+ }
+ }
+
+ @Test
+ public void appendHeaderRow_addedToBottom() {
+ selectMenuPath("Component", "Header", "Append header row");
+
+ assertEquals(2, getGridElement().getHeaderCount());
+ }
+
+ @Test
+ public void prependHeaderRow_addedToBottom() {
+ selectMenuPath("Component", "Header", "Prepend header row");
+
+ assertEquals(2, getGridElement().getHeaderCount());
+ }
+
+ @Test
+ public void removeDefaultHeaderRow_noHeaderRows() {
+ selectMenuPath("Component", "Header", "Remove first header row");
+
+ assertEquals(0, getGridElement().getHeaderCount());
+ }
+
+ 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");
+ assertEquals(expected, actual);
+ }
+}