aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJohn Ahlroos <john@vaadin.com>2013-10-29 13:44:49 +0200
committerVaadin Code Review <review@vaadin.com>2013-11-19 11:07:24 +0000
commit1c1506ef0447b1d979a6adb4d812ae9858f00b67 (patch)
tree801cd0118eb73c18527739fee512bac3339f2457
parenta752fcb10935c73f0a9d9d9c7948bfb29fcbb2e2 (diff)
downloadvaadin-framework-1c1506ef0447b1d979a6adb4d812ae9858f00b67.tar.gz
vaadin-framework-1c1506ef0447b1d979a6adb4d812ae9858f00b67.zip
Base grid component and column API (#12829, #12830)
Change-Id: I6c4eae8a4369e9452dd56e764633cecfe9bf553a
-rw-r--r--client/src/com/vaadin/client/ui/grid/Grid.java456
-rw-r--r--client/src/com/vaadin/client/ui/grid/GridColumn.java34
-rw-r--r--client/src/com/vaadin/client/ui/grid/GridConnector.java189
-rw-r--r--server/src/com/vaadin/ui/components/grid/Grid.java260
-rw-r--r--server/src/com/vaadin/ui/components/grid/GridColumn.java186
-rw-r--r--server/tests/src/com/vaadin/tests/server/component/grid/GridColumns.java207
-rw-r--r--shared/src/com/vaadin/shared/ui/grid/GridColumnState.java61
-rw-r--r--shared/src/com/vaadin/shared/ui/grid/GridState.java52
-rw-r--r--uitest/src/com/vaadin/tests/components/grid/GridBasicFeatures.java144
9 files changed, 1589 insertions, 0 deletions
diff --git a/client/src/com/vaadin/client/ui/grid/Grid.java b/client/src/com/vaadin/client/ui/grid/Grid.java
new file mode 100644
index 0000000000..8921aa3008
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/grid/Grid.java
@@ -0,0 +1,456 @@
+/*
+ * Copyright 2000-2013 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.client.ui.grid;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import com.google.gwt.core.shared.GWT;
+import com.google.gwt.user.client.ui.Composite;
+import com.vaadin.shared.util.SharedUtil;
+
+/**
+ * A data grid view that supports columns and lazy loading of data rows from a
+ * data source.
+ *
+ * <h3>Columns</h3>
+ * <p>
+ * The {@link GridColumn} class defines the renderer used to render a cell in
+ * the grid. Implement {@link GridColumn#getValue(Object)} to retrieve the cell
+ * value from the row object and return the cell renderer to render that cell.
+ * </p>
+ * <p>
+ * {@link GridColumn}s contain other properties like the width of the column and
+ * the visiblity of the column. If you want to change a column's properties
+ * after it has been added to the grid you can get a column object for a
+ * specific column index using {@link Grid#getColumn(int)}.
+ * </p>
+ * <p>
+ *
+ * TODO Explain about headers/footers once the multiple header/footer api has
+ * been implemented
+ *
+ * <h3>Data sources</h3>
+ * <p>
+ * TODO Explain about what a data source is and how it should be implemented.
+ * </p>
+ *
+ * @since 7.2
+ * @author Vaadin Ltd
+ */
+public class Grid<T> extends Composite {
+
+ /**
+ * Escalator used internally by the grid to render the rows
+ */
+ private Escalator escalator = GWT.create(Escalator.class);
+
+ /**
+ * List of columns in the grid. Order defines the visible order.
+ */
+ private final List<GridColumn<T>> columns = new ArrayList<GridColumn<T>>();
+
+ /**
+ * Base class for grid columns internally used by the Grid. You should use
+ * {@link GridColumn} when creating new columns.
+ *
+ * @param <T>
+ * the row type
+ */
+ public static abstract class AbstractGridColumn<T> {
+
+ /**
+ * Grid associated with the column
+ */
+ private Grid<T> grid;
+
+ /**
+ * Text displayed in the column header
+ */
+ private String header;
+
+ /**
+ * Text displayed in the column footer
+ */
+ private String footer;
+
+ /**
+ * Is the column visible
+ */
+ private boolean visible;
+
+ /**
+ * Internally used by the grid to set itself
+ *
+ * @param grid
+ */
+ private void setGrid(Grid<T> grid) {
+ if (this.grid != null && grid != null) {
+ // Trying to replace grid
+ throw new IllegalStateException(
+ "Column already is attached to grid. Remove the column first from the grid and then add it.");
+ }
+
+ this.grid = grid;
+ }
+
+ /**
+ * Gets text in the header of the column. By default the header caption
+ * is empty.
+ *
+ * @return the text displayed in the column caption
+ */
+ public String getHeaderCaption() {
+ return header;
+ }
+
+ /**
+ * Sets the text in the header of the column.
+ *
+ * @param caption
+ * the text displayed in the column header
+ */
+ public void setHeaderCaption(String caption) {
+ if (SharedUtil.equals(caption, this.header)) {
+ return;
+ }
+
+ this.header = caption;
+
+ if (grid != null) {
+ grid.refreshHeader();
+ }
+ }
+
+ /**
+ * Gets text in the footer of the column. By default the footer caption
+ * is empty.
+ *
+ * @return The text displayed in the footer of the column
+ */
+ public String getFooterCaption() {
+ return footer;
+ }
+
+ /**
+ * Sets text in the footer of the column.
+ *
+ * @param caption
+ * the text displayed in the footer of the column
+ */
+ public void setFooterCaption(String caption) {
+ if (SharedUtil.equals(caption, this.footer)) {
+ return;
+ }
+
+ this.footer = caption;
+
+ if (grid != null) {
+ grid.refreshFooter();
+ }
+ }
+
+ /**
+ * Is the column visible. By default all columns are visible.
+ *
+ * @return <code>true</code> if the column is visible
+ */
+ public boolean isVisible() {
+ return visible;
+ }
+
+ /**
+ * Sets a column as visible in the grid.
+ *
+ * @param visible
+ * Set to <code>true</code> to show the column in the grid
+ */
+ public void setVisible(boolean visible) {
+ if (this.visible == visible) {
+ return;
+ }
+
+ // Remove column
+ if (grid != null) {
+ int index = findIndexOfColumn();
+ ColumnConfiguration conf = grid.escalator
+ .getColumnConfiguration();
+
+ if (visible) {
+ conf.insertColumns(index, 1);
+ } else {
+ conf.removeColumns(index, 1);
+ }
+
+ // TODO should update body as well
+ }
+
+ this.visible = visible;
+ }
+
+ /**
+ * Returns the text that should be displayed in the cell.
+ *
+ * @param row
+ * the row object that provides the cell content
+ * @return The cell content of the row
+ */
+ public abstract String getValue(T row);
+
+ /**
+ * Finds the index of this column instance
+ *
+ */
+ private int findIndexOfColumn() {
+ return grid.columns.indexOf(this);
+ }
+ }
+
+ /**
+ * Creates a new instance.
+ */
+ public Grid() {
+ initWidget(escalator);
+
+ escalator.getHeader().setEscalatorUpdater(createHeaderUpdater());
+ escalator.getBody().setEscalatorUpdater(createBodyUpdater());
+ escalator.getFooter().setEscalatorUpdater(createFooterUpdater());
+ }
+
+ /**
+ * Creates the header updater that updates the escalator header rows from
+ * the column and column group rows.
+ *
+ * @return the updater that updates the data in the escalator.
+ */
+ private EscalatorUpdater createHeaderUpdater() {
+ return new EscalatorUpdater() {
+
+ @Override
+ public void updateCells(Row row, List<Cell> cellsToUpdate) {
+ if (isHeaderVisible()) {
+ for (Cell cell : cellsToUpdate) {
+ AbstractGridColumn<T> column = columns.get(cell
+ .getColumn());
+ cell.getElement().setInnerText(
+ column.getHeaderCaption());
+ }
+ }
+ }
+ };
+ }
+
+ // TODO Should be implemented bu the data sources
+ private EscalatorUpdater createBodyUpdater() {
+ return new EscalatorUpdater() {
+
+ @Override
+ public void updateCells(Row row, List<Cell> cellsToUpdate) {
+ for (Cell cell : cellsToUpdate) {
+ cell.getElement().setInnerHTML("-");
+ }
+ }
+ };
+ }
+
+ /**
+ * Creates the footer updater that updates the escalator footer rows from
+ * the column and column group rows.
+ *
+ * @return the updater that updates the data in the escalator.
+ */
+ private EscalatorUpdater createFooterUpdater() {
+ return new EscalatorUpdater() {
+
+ @Override
+ public void updateCells(Row row, List<Cell> cellsToUpdate) {
+ if (isFooterVisible()) {
+ for (Cell cell : cellsToUpdate) {
+ AbstractGridColumn<T> column = columns.get(cell
+ .getColumn());
+ cell.getElement().setInnerText(
+ column.getFooterCaption());
+ }
+ }
+ }
+ };
+ }
+
+ /**
+ * Refreshes all header rows.
+ */
+ private void refreshHeader() {
+ RowContainer header = escalator.getHeader();
+ if (isHeaderVisible() && header.getRowCount() > 0) {
+ header.refreshRows(0, header.getRowCount());
+ }
+ }
+
+ /**
+ * Refreshes all footer rows.
+ */
+ private void refreshFooter() {
+ RowContainer footer = escalator.getFooter();
+ if (isFooterVisible() && footer.getRowCount() > 0) {
+ footer.refreshRows(0, footer.getRowCount());
+ }
+ }
+
+ /**
+ * Adds a column as the last column in the grid.
+ *
+ * @param column
+ * the column to add
+ */
+ public void addColumn(GridColumn<T> column) {
+ ColumnConfiguration conf = escalator.getColumnConfiguration();
+ addColumn(column, conf.getColumnCount());
+ }
+
+ /**
+ * Inserts a column into a specific position in the grid.
+ *
+ * @param index
+ * the index where the column should be inserted into
+ * @param column
+ * the column to add
+ */
+ public void addColumn(GridColumn<T> column, int index) {
+
+ // Register this grid instance with the column
+ ((AbstractGridColumn<T>) column).setGrid(this);
+
+ columns.add(index, column);
+
+ ColumnConfiguration conf = escalator.getColumnConfiguration();
+ conf.insertColumns(index, 1);
+ }
+
+ /**
+ * Removes a column from the grid.
+ *
+ * @param column
+ * the column to remove
+ */
+ public void removeColumn(GridColumn<T> column) {
+
+ int columnIndex = columns.indexOf(column);
+ columns.remove(columnIndex);
+
+ // de-register column with grid
+ ((AbstractGridColumn<T>) column).setGrid(null);
+
+ ColumnConfiguration conf = escalator.getColumnConfiguration();
+ conf.removeColumns(columnIndex, 1);
+ }
+
+ /**
+ * Returns the amount of columns in the grid.
+ *
+ * @return The number of columns in the grid
+ */
+ public int getColumnCount() {
+ return columns.size();
+ }
+
+ /**
+ * Returns a list of columns in the grid.
+ *
+ * @return A unmodifiable list of the columns in the grid
+ */
+ public List<GridColumn<T>> getColumns() {
+ return Collections.unmodifiableList(new ArrayList<GridColumn<T>>(
+ columns));
+ }
+
+ /**
+ * Returns a column by its index in the grid.
+ *
+ * @param index
+ * the index of the column
+ * @return The column in the given index
+ * @throws IllegalArgumentException
+ * if the column index does not exist in the grid
+ */
+ public GridColumn<T> getColumn(int index) throws IllegalArgumentException {
+ try {
+ return columns.get(index);
+ } catch (ArrayIndexOutOfBoundsException aioobe) {
+ throw new IllegalStateException("Column not found.", aioobe);
+ }
+ }
+
+ /**
+ * Sets the header row visible.
+ *
+ * @param visible
+ * true if header rows should be visible
+ */
+ public void setHeaderVisible(boolean visible) {
+ if (visible == isHeaderVisible()) {
+ return;
+ }
+
+ RowContainer header = escalator.getHeader();
+
+ // TODO Should support multiple headers
+ if (visible) {
+ header.insertRows(0, 1);
+ } else {
+ header.removeRows(0, 1);
+ }
+ }
+
+ /**
+ * Are the header row(s) visible?
+ *
+ * @return <code>true</code> if the header is visible
+ */
+ public boolean isHeaderVisible() {
+ return escalator.getHeader().getRowCount() > 0;
+ }
+
+ /**
+ * Sets the footer row(s) visible.
+ *
+ * @param visible
+ * true if header rows should be visible
+ */
+ public void setFooterVisible(boolean visible) {
+ if (visible == isFooterVisible()) {
+ return;
+ }
+
+ RowContainer footer = escalator.getFooter();
+
+ // TODO Should support multiple footers
+ if (visible) {
+ footer.insertRows(0, 1);
+ } else {
+ footer.removeRows(0, 1);
+ }
+ }
+
+ /**
+ * Are the footer row(s) visible?
+ *
+ * @return <code>true</code> if the footer is visible
+ */
+ public boolean isFooterVisible() {
+ return escalator.getFooter().getRowCount() > 0;
+ }
+}
diff --git a/client/src/com/vaadin/client/ui/grid/GridColumn.java b/client/src/com/vaadin/client/ui/grid/GridColumn.java
new file mode 100644
index 0000000000..992bfae014
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/grid/GridColumn.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2000-2013 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.client.ui.grid;
+
+/**
+ * Represents a column in the {@link Grid}.
+ *
+ * @param <T>
+ * The row type
+ *
+ * @since 7.2
+ * @author Vaadin Ltd
+ */
+public abstract class GridColumn<T> extends Grid.AbstractGridColumn<T> {
+
+ /*
+ * This class is a convenience class so you do not have to reference
+ * Grid.AbstractGridColumn in your production code. The real implementation
+ * should be in the abstract class.
+ */
+}
diff --git a/client/src/com/vaadin/client/ui/grid/GridConnector.java b/client/src/com/vaadin/client/ui/grid/GridConnector.java
new file mode 100644
index 0000000000..e35e3f1f78
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/grid/GridConnector.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2000-2013 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.client.ui.grid;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import com.vaadin.client.communication.StateChangeEvent;
+import com.vaadin.client.ui.AbstractComponentConnector;
+import com.vaadin.shared.ui.Connect;
+import com.vaadin.shared.ui.grid.GridColumnState;
+import com.vaadin.shared.ui.grid.GridState;
+
+/**
+ * Connects the client side {@link Grid} widget with the server side
+ * {@link com.vaadin.ui.components.grid.Grid} component.
+ *
+ * @since 7.2
+ * @author Vaadin Ltd
+ */
+@Connect(com.vaadin.ui.components.grid.Grid.class)
+public class GridConnector extends AbstractComponentConnector {
+
+ private class CustomGridColumn extends GridColumn<String[]> {
+
+ @Override
+ public String getValue(String[] obj) {
+ // FIXME Should return something from the data source.
+ return null;
+ }
+ }
+
+ // Maps a generated column id -> A grid column instance
+ private Map<String, CustomGridColumn> columnIdToColumn = new HashMap<String, CustomGridColumn>();
+
+ @Override
+ protected void init() {
+
+ // FIXME Escalator bug requires to do this when running compiled. Not
+ // required when in devmode. Most likely Escalator.setWidth() is called
+ // before attach and measuring from DOM does not work then.
+ getWidget().setWidth(getState().width);
+ getWidget().setHeight(getState().height);
+
+ }
+
+ @Override
+ protected Grid<String[]> createWidget() {
+ // FIXME Shouldn't be needed after #12873 has been fixed.
+ return new Grid<String[]>();
+ }
+
+ @Override
+ public Grid<String[]> getWidget() {
+ return (Grid<String[]>) super.getWidget();
+ }
+
+ @Override
+ public GridState getState() {
+ return (GridState) super.getState();
+ }
+
+ @Override
+ public void onStateChanged(StateChangeEvent stateChangeEvent) {
+ super.onStateChanged(stateChangeEvent);
+
+ // Header
+ if (stateChangeEvent.hasPropertyChanged("headerVisible")) {
+ getWidget().setHeaderVisible(getState().headerVisible);
+ }
+
+ // Footer
+ if (stateChangeEvent.hasPropertyChanged("footerVisible")) {
+ getWidget().setFooterVisible(getState().footerVisible);
+ }
+
+ // Column updates
+ if (stateChangeEvent.hasPropertyChanged("columns")) {
+
+ int totalColumns = getState().columns.size();
+ int currentColumns = getWidget().getColumnCount();
+
+ // Remove old columns
+ purgeRemovedColumns();
+
+ // Add new columns
+ for (int columnIndex = currentColumns; columnIndex < totalColumns; columnIndex++) {
+ addColumnFromStateChangeEvent(columnIndex, stateChangeEvent);
+ }
+
+ // Update old columns
+ for (int columnIndex = 0; columnIndex < currentColumns; columnIndex++) {
+ // FIXME Currently updating all column header / footers when a
+ // change in made in one column. When the framework supports
+ // quering a specific item in a list then it should do so here.
+ updateColumnFromStateChangeEvent(columnIndex, stateChangeEvent);
+ }
+ }
+ }
+
+ /**
+ * Updates a column from a state change event.
+ *
+ * @param columnIndex
+ * The index of the column to update
+ * @param stateChangeEvent
+ * The state change event that contains the changes for the
+ * column
+ */
+ private void updateColumnFromStateChangeEvent(int columnIndex,
+ StateChangeEvent stateChangeEvent) {
+ GridColumn<String[]> column = getWidget().getColumn(columnIndex);
+ GridColumnState columnState = getState().columns.get(columnIndex);
+ updateColumnFromState(column, columnState);
+ }
+
+ /**
+ * Adds a new column to the grid widget from a state change event
+ *
+ * @param columnIndex
+ * The index of the column, according to how it
+ * @param stateChangeEvent
+ */
+ private void addColumnFromStateChangeEvent(int columnIndex,
+ StateChangeEvent stateChangeEvent) {
+ GridColumnState state = getState().columns.get(columnIndex);
+ CustomGridColumn column = new CustomGridColumn();
+ updateColumnFromState(column, state);
+ columnIdToColumn.put(state.id, column);
+ getWidget().addColumn(column, columnIndex);
+ }
+
+ /**
+ * Updates fields in column from a {@link GridColumnState} DTO
+ *
+ * @param column
+ * The column to update
+ * @param state
+ * The state to update from
+ */
+ private void updateColumnFromState(GridColumn<String[]> column,
+ GridColumnState state) {
+ column.setHeaderCaption(state.header);
+ column.setFooterCaption(state.footer);
+ column.setVisible(state.visible);
+ }
+
+ /**
+ * Removes any orphan columns that has been removed from the state from the
+ * grid
+ */
+ private void purgeRemovedColumns() {
+
+ // Get columns still registered in the state
+ Set<String> columnsInState = new HashSet<String>();
+ for (GridColumnState columnState : getState().columns) {
+ columnsInState.add(columnState.id);
+ }
+
+ // Remove column no longer in state
+ Iterator<String> columnIdIterator = columnIdToColumn.keySet()
+ .iterator();
+ while (columnIdIterator.hasNext()) {
+ String id = columnIdIterator.next();
+ if (!columnsInState.contains(id)) {
+ CustomGridColumn column = columnIdToColumn.get(id);
+ columnIdIterator.remove();
+ getWidget().removeColumn(column);
+ }
+ }
+ }
+}
diff --git a/server/src/com/vaadin/ui/components/grid/Grid.java b/server/src/com/vaadin/ui/components/grid/Grid.java
new file mode 100644
index 0000000000..25ac796d47
--- /dev/null
+++ b/server/src/com/vaadin/ui/components/grid/Grid.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright 2000-2013 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.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import com.vaadin.data.Container;
+import com.vaadin.data.Container.PropertySetChangeEvent;
+import com.vaadin.data.Container.PropertySetChangeListener;
+import com.vaadin.data.Container.PropertySetChangeNotifier;
+import com.vaadin.server.KeyMapper;
+import com.vaadin.shared.ui.grid.GridColumnState;
+import com.vaadin.shared.ui.grid.GridState;
+import com.vaadin.ui.AbstractComponent;
+
+/**
+ * Data grid component
+ *
+ * <h3>Lazy loading</h3> TODO To be revised when the data data source
+ * implementation has been don.
+ *
+ * <h3>Columns</h3> The grid columns are based on the property ids of the
+ * underlying data source. Each property id represents one column in the grid.
+ * To retrive a column in the grid you can use {@link Grid#getColumn(Object)}
+ * with the property id of the column. A grid column contains properties like
+ * the width, the footer and header captions of the column.
+ *
+ * <h3>Auxiliary headers and footers</h3> TODO To be revised when column
+ * grouping is implemented.
+ *
+ * @since 7.2
+ * @author Vaadin Ltd
+ */
+public class Grid extends AbstractComponent {
+
+ private Container.Indexed datasource;
+
+ /**
+ * Property id -> Column instance mapping
+ */
+ private final Map<Object, GridColumn> columns = new HashMap<Object, GridColumn>();
+
+ /**
+ * Key generator for column server->client communication
+ */
+ private final KeyMapper<Object> columnKeys = new KeyMapper<Object>();
+
+ /**
+ * Property listener for listening to changes in data source properties.
+ */
+ private final PropertySetChangeListener propertyListener = new PropertySetChangeListener() {
+
+ @Override
+ public void containerPropertySetChange(PropertySetChangeEvent event) {
+ Collection<?> properties = new HashSet<Object>(event.getContainer()
+ .getContainerPropertyIds());
+
+ // Cleanup columns that are no longer in grid
+ List<Object> removedColumns = new LinkedList<Object>();
+ for (Object columnId : columns.keySet()) {
+ if (!properties.contains(columnId)) {
+ removedColumns.add(columnId);
+ }
+ }
+ for (Object columnId : removedColumns) {
+ GridColumn column = columns.remove(columnId);
+ columnKeys.remove(columnId);
+ getState().columns.remove(column.getState());
+ }
+
+ // Add new columns
+ for (Object propertyId : properties) {
+ if (!columns.containsKey(propertyId)) {
+ appendColumn(propertyId);
+ }
+ }
+ }
+ };
+
+ /**
+ * Creates a new Grid using the given datasource.
+ *
+ * @param datasource
+ * the data source for the grid
+ */
+ public Grid(Container.Indexed datasource) {
+ setContainerDatasource(datasource);
+ }
+
+ /**
+ * Sets the grid data source.
+ *
+ * @param container
+ * The container data source. Cannot be null.
+ * @throws IllegalArgumentException
+ * if the data source is null
+ */
+ public void setContainerDatasource(Container.Indexed container) {
+ if (container == null) {
+ throw new IllegalArgumentException(
+ "Cannot set the datasource to null");
+ }
+ if (datasource == container) {
+ return;
+ }
+
+ // Remove old listener
+ if (datasource instanceof PropertySetChangeNotifier) {
+ ((PropertySetChangeNotifier) datasource)
+ .removePropertySetChangeListener(propertyListener);
+ }
+
+ datasource = container;
+
+ // Listen to changes in properties and remove columns if needed
+ if (datasource instanceof PropertySetChangeNotifier) {
+ ((PropertySetChangeNotifier) datasource)
+ .addPropertySetChangeListener(propertyListener);
+ }
+
+ getState().columns.clear();
+
+ // Add columns
+ for (Object propertyId : datasource.getContainerPropertyIds()) {
+ if (!columns.containsKey(propertyId)) {
+ GridColumn column = appendColumn(propertyId);
+
+ // By default use property id as column caption
+ column.setHeaderCaption(String.valueOf(propertyId));
+
+ }
+ }
+ }
+
+ /**
+ * Returns the grid data source.
+ *
+ * @return the container data source of the grid
+ */
+ public Container.Indexed getContainerDatasource() {
+ return datasource;
+ }
+
+ /**
+ * Returns a column based on the property id
+ *
+ * @param propertyId
+ * the property id of the column
+ * @return the column or <code>null</code> if not found
+ */
+ public GridColumn getColumn(Object propertyId) {
+ return columns.get(propertyId);
+ }
+
+ /**
+ * Sets the header rows visible.
+ *
+ * @param visible
+ * <code>true</code> if the header rows should be visible
+ */
+ public void setHeaderVisible(boolean visible) {
+ getState().headerVisible = visible;
+ }
+
+ /**
+ * Are the header rows visible?
+ *
+ * @return <code>true</code> if the header is visible
+ */
+ public boolean isHeaderVisible() {
+ return getState(false).headerVisible;
+ }
+
+ /**
+ * Sets the footer rows visible.
+ *
+ * @param visible
+ * <code>true</code> if the header rows should be visible
+ */
+ public void setFooterVisible(boolean visible) {
+ getState().footerVisible = visible;
+ }
+
+ /**
+ * Are the footer rows visible.
+ *
+ * @return <code>true</code> if the footer rows should be visible
+ */
+ public boolean isFooterVisible() {
+ return getState(false).footerVisible;
+ }
+
+ /**
+ * Used internally by the {@link Grid} to get a {@link GridColumn} by
+ * referencing its generated state id. Also used by {@link GridColumn} to
+ * verify if it has been detached from the {@link Grid}
+ *
+ * @param columnId
+ * The client id generated for the column when the column is
+ * added to the grid
+ * @return The column with the id or <code>null</code> if not found
+ */
+ GridColumn getColumnByColumnId(String columnId) {
+ Object propertyId = columnKeys.get(columnId);
+ return getColumn(propertyId);
+ }
+
+ @Override
+ protected GridState getState() {
+ return (GridState) super.getState();
+ }
+
+ @Override
+ protected GridState getState(boolean markAsDirty) {
+ return (GridState) super.getState(markAsDirty);
+ }
+
+ /**
+ * Creates a new column based on a property id and appends it as the last
+ * column.
+ *
+ * @param datasourcePropertyId
+ * The property id of a property in the datasource
+ */
+ protected GridColumn appendColumn(Object datasourcePropertyId) {
+ if (datasourcePropertyId == null) {
+ throw new IllegalArgumentException("Property id cannot be null");
+ }
+ assert datasource.getContainerPropertyIds().contains(
+ datasourcePropertyId) : "Datasource should contain the property id";
+
+ GridColumnState columnState = new GridColumnState();
+ columnState.id = columnKeys.key(datasourcePropertyId);
+ getState().columns.add(columnState);
+
+ GridColumn column = new GridColumn(this, columnState);
+ columns.put(datasourcePropertyId, column);
+
+ return column;
+ }
+}
diff --git a/server/src/com/vaadin/ui/components/grid/GridColumn.java b/server/src/com/vaadin/ui/components/grid/GridColumn.java
new file mode 100644
index 0000000000..505919b3cf
--- /dev/null
+++ b/server/src/com/vaadin/ui/components/grid/GridColumn.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2000-2013 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.shared.ui.grid.GridColumnState;
+
+/**
+ * A column in the grid. Can be obtained by calling
+ * {@link Grid#getColumn(Object propertyId)}.
+ *
+ * @since 7.2
+ * @author Vaadin Ltd
+ */
+public class GridColumn {
+
+ /**
+ * The shared state of the column
+ */
+ private final GridColumnState state;
+
+ /**
+ * The grid this column is associated with
+ */
+ private final Grid grid;
+
+ /**
+ * Internally used constructor.
+ *
+ * @param grid
+ * The grid this column belongs to. Should not be null.
+ * @param state
+ * the shared state of this column
+ */
+ GridColumn(Grid grid, GridColumnState state) {
+ this.grid = grid;
+ this.state = state;
+ }
+
+ /**
+ * Returns the serializable state of this column that is sent to the client
+ * side connector.
+ *
+ * @return the internal state of the column
+ */
+ GridColumnState getState() {
+ return state;
+ }
+
+ /**
+ * Returns the caption of the header. By default the header caption is the
+ * property id of the column.
+ *
+ * @return the text in the header
+ *
+ * @throws IllegalStateException
+ * if the column no longer is attached to the grid
+ */
+ public String getHeaderCaption() throws IllegalStateException {
+ checkColumnIsAttached();
+ return state.header;
+ }
+
+ /**
+ * Sets the caption of the header.
+ *
+ * @param caption
+ * the text to show in the caption
+ *
+ * @throws IllegalStateException
+ * if the column is no longer attached to any grid
+ */
+ public void setHeaderCaption(String caption) throws IllegalStateException {
+ checkColumnIsAttached();
+ state.header = caption;
+ grid.markAsDirty();
+ }
+
+ /**
+ * Returns the caption of the footer. By default the captions are
+ * <code>null</code>.
+ *
+ * @return the text in the footer
+ * @throws IllegalStateException
+ * if the column is no longer attached to any grid
+ */
+ public String getFooterCaption() throws IllegalStateException {
+ checkColumnIsAttached();
+ return state.footer;
+ }
+
+ /**
+ * Sets the caption of the footer.
+ *
+ * @param caption
+ * the text to show in the caption
+ *
+ * @throws IllegalStateException
+ * if the column is no longer attached to any grid
+ */
+ public void setFooterCaption(String caption) throws IllegalStateException {
+ checkColumnIsAttached();
+ state.footer = caption;
+ grid.markAsDirty();
+ }
+
+ /**
+ * Returns the width (in pixels). By default a column is 100px wide.
+ *
+ * @return the width in pixels of the column
+ * @throws IllegalStateException
+ * if the column is no longer attached to any grid
+ */
+ public int getWidth() throws IllegalStateException {
+ checkColumnIsAttached();
+ return state.width;
+ }
+
+ /**
+ * Sets the width (in pixels).
+ *
+ * FIXME Currently not implemented.
+ *
+ * @param pixelWidth
+ * the new pixel width of the column
+ * @throws IllegalStateException
+ * if the column is no longer attached to any grid
+ */
+ public void setWidth(int pixelWidth) throws IllegalStateException {
+ checkColumnIsAttached();
+ state.width = pixelWidth;
+ grid.markAsDirty();
+ }
+
+ /**
+ * Is this column visible in the grid. By default all columns are visible.
+ *
+ * @return <code>true</code> if the column is visible
+ * @throws IllegalStateException
+ * if the column is no longer attached to any grid
+ */
+ public boolean isVisible() throws IllegalStateException {
+ checkColumnIsAttached();
+ return state.visible;
+ }
+
+ /**
+ * Set the visibility of this column
+ *
+ * @param visible
+ * is the column visible
+ * @throws IllegalStateException
+ * if the column is no longer attached to any grid
+ */
+ public void setVisible(boolean visible) throws IllegalStateException {
+ checkColumnIsAttached();
+ state.visible = visible;
+ grid.markAsDirty();
+ }
+
+ /**
+ * Checks if column is attached and throws an {@link IllegalStateException}
+ * if it is not
+ *
+ * @throws IllegalStateException
+ * if the column is no longer attached to any grid
+ */
+ protected void checkColumnIsAttached() throws IllegalStateException {
+ if (grid.getColumnByColumnId(state.id) == null) {
+ throw new IllegalStateException("Column no longer exists.");
+ }
+ }
+}
diff --git a/server/tests/src/com/vaadin/tests/server/component/grid/GridColumns.java b/server/tests/src/com/vaadin/tests/server/component/grid/GridColumns.java
new file mode 100644
index 0000000000..5989d537b4
--- /dev/null
+++ b/server/tests/src/com/vaadin/tests/server/component/grid/GridColumns.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright 2000-2013 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.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.vaadin.data.util.IndexedContainer;
+import com.vaadin.server.KeyMapper;
+import com.vaadin.shared.ui.grid.GridColumnState;
+import com.vaadin.shared.ui.grid.GridState;
+import com.vaadin.ui.components.grid.Grid;
+import com.vaadin.ui.components.grid.GridColumn;
+
+public class GridColumns {
+
+ private Grid grid;
+
+ private GridState state;
+
+ private Method getStateMethod;
+
+ private Field columnIdGeneratorField;
+
+ private KeyMapper<Object> columnIdMapper;
+
+ @Before
+ public void setup() throws Exception {
+ IndexedContainer ds = new IndexedContainer();
+ for (int c = 0; c < 10; c++) {
+ ds.addContainerProperty("column" + c, String.class, "");
+ }
+ grid = new Grid(ds);
+
+ getStateMethod = Grid.class.getDeclaredMethod("getState");
+ getStateMethod.setAccessible(true);
+
+ state = (GridState) getStateMethod.invoke(grid);
+
+ columnIdGeneratorField = Grid.class.getDeclaredField("columnKeys");
+ columnIdGeneratorField.setAccessible(true);
+
+ columnIdMapper = (KeyMapper<Object>) columnIdGeneratorField.get(grid);
+ }
+
+ @Test
+ public void testColumnGeneration() throws Exception {
+
+ for (Object propertyId : grid.getContainerDatasource()
+ .getContainerPropertyIds()) {
+
+ // All property ids should get a column
+ GridColumn column = grid.getColumn(propertyId);
+ assertNotNull(column);
+
+ // Property id should be the column header by default
+ assertEquals(propertyId.toString(), column.getHeaderCaption());
+ }
+ }
+
+ @Test
+ public void testModifyingColumnProperties() throws Exception {
+
+ // Modify first column
+ GridColumn column = grid.getColumn("column1");
+ assertNotNull(column);
+
+ column.setFooterCaption("CustomFooter");
+ assertEquals("CustomFooter", column.getFooterCaption());
+ assertEquals(column.getFooterCaption(),
+ getColumnState("column1").footer);
+
+ column.setHeaderCaption("CustomHeader");
+ assertEquals("CustomHeader", column.getHeaderCaption());
+ assertEquals(column.getHeaderCaption(),
+ getColumnState("column1").header);
+
+ column.setVisible(false);
+ assertFalse(column.isVisible());
+ assertFalse(getColumnState("column1").visible);
+
+ column.setVisible(true);
+ assertTrue(column.isVisible());
+ assertTrue(getColumnState("column1").visible);
+
+ column.setWidth(100);
+ assertEquals(100, column.getWidth());
+ assertEquals(column.getWidth(), getColumnState("column1").width);
+
+ column.setWidth(-1);
+ assertEquals(-1, column.getWidth());
+ assertEquals(-1, getColumnState("column1").width);
+ }
+
+ @Test
+ public void testRemovingColumn() throws Exception {
+
+ GridColumn column = grid.getColumn("column1");
+ assertNotNull(column);
+
+ // Remove column
+ grid.getContainerDatasource().removeContainerProperty("column1");
+
+ try {
+ column.setHeaderCaption("asd");
+ fail("Succeeded in modifying a detached column");
+ } catch (IllegalStateException ise) {
+ // Detached state should throw exception
+ }
+
+ try {
+ column.setFooterCaption("asd");
+ fail("Succeeded in modifying a detached column");
+ } catch (IllegalStateException ise) {
+ // Detached state should throw exception
+ }
+
+ try {
+ column.setVisible(false);
+ fail("Succeeded in modifying a detached column");
+ } catch (IllegalStateException ise) {
+ // Detached state should throw exception
+ }
+
+ try {
+ column.setWidth(123);
+ fail("Succeeded in modifying a detached column");
+ } catch (IllegalStateException ise) {
+ // Detached state should throw exception
+ }
+
+ assertNull(grid.getColumn("column1"));
+ assertNull(getColumnState("column1"));
+ }
+
+ @Test
+ public void testAddingColumn() {
+ grid.getContainerDatasource().addContainerProperty("columnX",
+ String.class, "");
+ GridColumn column = grid.getColumn("columnX");
+ assertNotNull(column);
+ }
+
+ @Test
+ public void testHeaderVisiblility() {
+
+ assertTrue(grid.isHeaderVisible());
+ assertTrue(state.headerVisible);
+
+ grid.setHeaderVisible(false);
+ assertFalse(grid.isHeaderVisible());
+ assertFalse(state.headerVisible);
+
+ grid.setHeaderVisible(true);
+ assertTrue(grid.isHeaderVisible());
+ assertTrue(state.headerVisible);
+ }
+
+ @Test
+ public void testFooterVisibility() {
+
+ assertTrue(grid.isFooterVisible());
+ assertTrue(state.footerVisible);
+
+ grid.setFooterVisible(false);
+ assertFalse(grid.isFooterVisible());
+ assertFalse(state.footerVisible);
+
+ grid.setFooterVisible(true);
+ assertTrue(grid.isFooterVisible());
+ assertTrue(state.footerVisible);
+ }
+
+ private GridColumnState getColumnState(Object propertyId) {
+ String columnId = columnIdMapper.key(propertyId);
+ for (GridColumnState columnState : state.columns) {
+ if (columnState.id.equals(columnId)) {
+ return columnState;
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/shared/src/com/vaadin/shared/ui/grid/GridColumnState.java b/shared/src/com/vaadin/shared/ui/grid/GridColumnState.java
new file mode 100644
index 0000000000..391eb2a65c
--- /dev/null
+++ b/shared/src/com/vaadin/shared/ui/grid/GridColumnState.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2000-2013 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;
+
+/**
+ * Column state DTO for transferring column properties from the server to the
+ * client
+ *
+ * @since 7.2
+ * @author Vaadin Ltd
+ */
+public class GridColumnState implements Serializable {
+
+ /**
+ * Id used by grid connector to map server side column with client side
+ * column
+ */
+ public String id;
+
+ /**
+ * Header caption for the column
+ *
+ * FIXME Only single header currently supported. Should support many
+ * headers.
+ */
+ public String header;
+
+ /**
+ * Footer caption for the column
+ *
+ * FIXME Only single footer currently supported. Should support many
+ * footers.
+ */
+ public String footer;
+
+ /**
+ * Has the column been hidden. By default the column is visible.
+ */
+ public boolean visible = true;
+
+ /**
+ * Column width in pixels. Default column width is 100px.
+ */
+ public int width = 100;
+
+}
diff --git a/shared/src/com/vaadin/shared/ui/grid/GridState.java b/shared/src/com/vaadin/shared/ui/grid/GridState.java
new file mode 100644
index 0000000000..e1e0fff354
--- /dev/null
+++ b/shared/src/com/vaadin/shared/ui/grid/GridState.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2000-2013 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.util.ArrayList;
+import java.util.List;
+
+import com.vaadin.shared.AbstractComponentState;
+
+/**
+ * The shared state for the {@link com.vaadin.ui.components.grid.Grid} component
+ *
+ * @since 7.2
+ * @author Vaadin Ltd
+ */
+public class GridState extends AbstractComponentState {
+ {
+ // FIXME Grid currently does not support undefined size
+ width = "400px";
+ height = "400px";
+ }
+
+ /**
+ * Columns in grid. Column order implicitly deferred from list order.
+ */
+ public List<GridColumnState> columns = new ArrayList<GridColumnState>();
+
+ /**
+ * Are the header row(s) visible. By default they are visible.
+ */
+ public boolean headerVisible = true;
+
+ /**
+ * Are the footer row(s) visible. By default they are visible.
+ */
+ public boolean footerVisible = true;
+
+}
diff --git a/uitest/src/com/vaadin/tests/components/grid/GridBasicFeatures.java b/uitest/src/com/vaadin/tests/components/grid/GridBasicFeatures.java
new file mode 100644
index 0000000000..5b3d742f19
--- /dev/null
+++ b/uitest/src/com/vaadin/tests/components/grid/GridBasicFeatures.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2000-2013 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;
+
+import java.util.ArrayList;
+
+import com.vaadin.data.util.IndexedContainer;
+import com.vaadin.tests.components.AbstractComponentTest;
+import com.vaadin.ui.components.grid.Grid;
+import com.vaadin.ui.components.grid.GridColumn;
+
+/**
+ * Tests the basic features like columns, footers and headers
+ *
+ * @since 7.2
+ * @author Vaadin Ltd
+ */
+public class GridBasicFeatures extends AbstractComponentTest<Grid> {
+
+ private final int COLUMNS = 10;
+
+ @Override
+ protected Grid constructComponent() {
+
+ // Build data source
+ IndexedContainer ds = new IndexedContainer();
+
+ for (int col = 0; col < COLUMNS; col++) {
+ ds.addContainerProperty("Column" + col, String.class, "");
+ }
+
+ Grid grid = new Grid(ds);
+
+ // Headers and footers
+ for (int col = 0; col < COLUMNS; col++) {
+ GridColumn column = grid.getColumn("Column" + col);
+ column.setHeaderCaption("Column " + col);
+ column.setFooterCaption("Footer " + col);
+ }
+
+ createColumnActions();
+
+ return grid;
+ }
+
+ protected void createColumnActions() {
+ createCategory("Columns", null);
+
+ for (int c = 0; c < COLUMNS; c++) {
+ createCategory("Column" + c, "Columns");
+
+ createBooleanAction("Visible", "Column" + c, true,
+ new Command<Grid, Boolean>() {
+
+ @Override
+ public void execute(Grid grid, Boolean value,
+ Object columnIndex) {
+ Object propertyId = (new ArrayList(grid
+ .getContainerDatasource()
+ .getContainerPropertyIds())
+ .get((Integer) columnIndex));
+ GridColumn column = grid.getColumn(propertyId);
+ column.setVisible(!column.isVisible());
+ }
+ }, c);
+
+ createBooleanAction("Footer", "Column" + c, true,
+ new Command<Grid, Boolean>() {
+
+ @Override
+ public void execute(Grid grid, Boolean value,
+ Object columnIndex) {
+ Object propertyId = (new ArrayList(grid
+ .getContainerDatasource()
+ .getContainerPropertyIds())
+ .get((Integer) columnIndex));
+ GridColumn column = grid.getColumn(propertyId);
+ String footer = column.getFooterCaption();
+ if (footer == null) {
+ column.setFooterCaption("Footer " + columnIndex);
+ } else {
+ column.setFooterCaption(null);
+ }
+ }
+ }, c);
+
+ createBooleanAction("Header", "Column" + c, true,
+ new Command<Grid, Boolean>() {
+
+ @Override
+ public void execute(Grid grid, Boolean value,
+ Object columnIndex) {
+ Object propertyId = (new ArrayList(grid
+ .getContainerDatasource()
+ .getContainerPropertyIds())
+ .get((Integer) columnIndex));
+ GridColumn column = grid.getColumn(propertyId);
+ String header = column.getHeaderCaption();
+ if (header == null) {
+ column.setHeaderCaption("Column " + columnIndex);
+ } else {
+ column.setHeaderCaption(null);
+ }
+ }
+ }, c);
+
+ createClickAction("Remove", "Column" + c,
+ new Command<Grid, String>() {
+
+ @Override
+ public void execute(Grid grid, String value, Object data) {
+ grid.getContainerDatasource()
+ .removeContainerProperty("Column" + data);
+ }
+ }, null, c);
+
+ }
+
+ }
+
+ @Override
+ protected Integer getTicketNumber() {
+ return 12829;
+ }
+
+ @Override
+ protected Class<Grid> getTestClass() {
+ return Grid.class;
+ }
+
+}