aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-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
4 files changed, 725 insertions, 5 deletions
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);
+ }
+}