aboutsummaryrefslogtreecommitdiffstats
path: root/server
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 /server
parenta752fcb10935c73f0a9d9d9c7948bfb29fcbb2e2 (diff)
downloadvaadin-framework-1c1506ef0447b1d979a6adb4d812ae9858f00b67.tar.gz
vaadin-framework-1c1506ef0447b1d979a6adb4d812ae9858f00b67.zip
Base grid component and column API (#12829, #12830)
Change-Id: I6c4eae8a4369e9452dd56e764633cecfe9bf553a
Diffstat (limited to 'server')
-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
3 files changed, 653 insertions, 0 deletions
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;
+ }
+
+}