diff options
author | John Ahlroos <john@vaadin.com> | 2013-11-06 10:35:03 +0200 |
---|---|---|
committer | Vaadin Code Review <review@vaadin.com> | 2013-11-22 12:59:10 +0000 |
commit | 4caa2f5b6e26ade52a4fba66a0a020b79f9008ea (patch) | |
tree | 4aa4d2b8d4d0566fda4b336d22a1ebbe7ac9d712 /server | |
parent | c2d38fa6c2d67457065fd3dce7e0d939ae0a1278 (diff) | |
download | vaadin-framework-4caa2f5b6e26ade52a4fba66a0a020b79f9008ea.tar.gz vaadin-framework-4caa2f5b6e26ade52a4fba66a0a020b79f9008ea.zip |
Multiple headers and footer rows #3153
Change-Id: Iadb0d8b051d0f0ef1303e0d7d740cf476cd81971
Diffstat (limited to 'server')
5 files changed, 593 insertions, 44 deletions
diff --git a/server/src/com/vaadin/ui/components/grid/ColumnGroup.java b/server/src/com/vaadin/ui/components/grid/ColumnGroup.java new file mode 100644 index 0000000000..0ab1f61a46 --- /dev/null +++ b/server/src/com/vaadin/ui/components/grid/ColumnGroup.java @@ -0,0 +1,141 @@ +/* + * 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.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import com.vaadin.shared.ui.grid.ColumnGroupState; + +/** + * Column groups are used to group columns together for adding common auxiliary + * headers and footers. Columns groups are added to {@link ColumnGroupRow}'s. + * + * @since 7.2 + * @author Vaadin Ltd + */ +public class ColumnGroup implements Serializable { + + /** + * List of property ids belonging to this group + */ + private List<Object> columns; + + /** + * The grid the column group is associated with + */ + private final Grid grid; + + /** + * The common state between the server and the client + */ + private final ColumnGroupState state; + + /** + * Constructs a new column group + * + * @param grid + * the grid the column group is associated with + * @param state + * the state representing the data of the grid. Sent to the + * client + * @param propertyIds + * the property ids of the columns that belongs to the group + * @param groups + * the sub groups who should be included in this group + * + */ + ColumnGroup(Grid grid, ColumnGroupState state, List<Object> propertyIds) { + if (propertyIds == null) { + throw new IllegalArgumentException( + "propertyIds cannot be null. Use empty list instead."); + } + + this.state = state; + columns = Collections.unmodifiableList(new ArrayList<Object>( + propertyIds)); + this.grid = grid; + } + + /** + * Sets the text displayed in the header of the column group. + * + * @param header + * the text displayed in the header of the column + */ + public void setHeaderCaption(String header) { + state.header = header; + grid.markAsDirty(); + } + + /** + * Sets the text displayed in the header of the column group. + * + * @return the text displayed in the header of the column + */ + public String getHeaderCaption() { + return state.header; + } + + /** + * Sets the text displayed in the footer of the column group. + * + * @param footer + * the text displayed in the footer of the column + */ + public void setFooterCaption(String footer) { + state.footer = footer; + grid.markAsDirty(); + } + + /** + * The text displayed in the footer of the column group. + * + * @return the text displayed in the footer of the column + */ + public String getFooterCaption() { + return state.footer; + } + + /** + * Is a property id in this group or in some sub group of this group. + * + * @param propertyId + * the property id to check for + * @return <code>true</code> if the property id is included in this group. + */ + public boolean isColumnInGroup(Object propertyId) { + if (columns.contains(propertyId)) { + return true; + } + return false; + } + + /** + * Returns a list of property ids where all also the child groups property + * ids are included. + * + * @return a unmodifiable list with all the columns in the group. Includes + * any subgroup columns as well. + */ + public List<Object> getColumns() { + return columns; + } + +} diff --git a/server/src/com/vaadin/ui/components/grid/ColumnGroupRow.java b/server/src/com/vaadin/ui/components/grid/ColumnGroupRow.java new file mode 100644 index 0000000000..326d2826f5 --- /dev/null +++ b/server/src/com/vaadin/ui/components/grid/ColumnGroupRow.java @@ -0,0 +1,255 @@ +/* + * 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.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import com.vaadin.server.KeyMapper; +import com.vaadin.shared.ui.grid.ColumnGroupRowState; +import com.vaadin.shared.ui.grid.ColumnGroupState; + +/** + * A column group row represents an auxiliary header or footer row added to the + * grid. A column group row includes column groups that group columns together. + * + * @since 7.2 + * @author Vaadin Ltd + */ +public class ColumnGroupRow implements Serializable { + + /** + * The common state shared between the client and server + */ + private final ColumnGroupRowState state; + + /** + * The column groups in this row + */ + private List<ColumnGroup> groups = new ArrayList<ColumnGroup>(); + + /** + * Grid that the group row belongs to + */ + private final Grid grid; + + /** + * The column keys used to identify the column on the client side + */ + private final KeyMapper<Object> columnKeys; + + /** + * Constructs a new column group + * + * @param grid + * The grid that the column group is associated to + * @param state + * The shared state which contains the data shared between server + * and client + * @param columnKeys + * The column key mapper for converting property ids to client + * side column identifiers + */ + ColumnGroupRow(Grid grid, ColumnGroupRowState state, + KeyMapper<Object> columnKeys) { + this.grid = grid; + this.columnKeys = columnKeys; + this.state = state; + } + + /** + * Gets the shared state for the column group row. Used internally to send + * the group row to the client. + * + * @return The current state of the row + */ + ColumnGroupRowState getState() { + return state; + } + + /** + * Add a new group to the row by using property ids for the columns. + * + * @param propertyIds + * The property ids of the columns that should be included in the + * group. A column can only belong in group on a row at a time. + * @return a column group representing the collection of columns added to + * the group + */ + public ColumnGroup addGroup(Object... propertyIds) { + assert propertyIds != null : "propertyIds cannot be null."; + + for (Object propertyId : propertyIds) { + if (hasColumnBeenGrouped(propertyId)) { + throw new IllegalArgumentException("Column " + + String.valueOf(propertyId) + + " already belongs to another group."); + } + } + + ColumnGroupState state = new ColumnGroupState(); + for (Object propertyId : propertyIds) { + assert propertyId != null : "null items in columns array not supported."; + state.columns.add(columnKeys.key(propertyId)); + } + this.state.groups.add(state); + + ColumnGroup group = new ColumnGroup(grid, state, + Arrays.asList(propertyIds)); + groups.add(group); + + grid.markAsDirty(); + return group; + } + + /** + * Add a new group to the row by using column instances. + * + * @param columns + * the columns that should belong to the group + * @return a column group representing the collection of columns added to + * the group + */ + public ColumnGroup addGroup(GridColumn... columns) { + assert columns != null : "columns cannot be null"; + + List<Object> propertyIds = new ArrayList<Object>(); + for (GridColumn column : columns) { + assert column != null : "null items in columns array not supported."; + + String columnId = column.getState().id; + Object propertyId = grid.getPropertyIdByColumnId(columnId); + propertyIds.add(propertyId); + } + return addGroup(propertyIds.toArray()); + } + + /** + * Add a new group to the row by using other already greated groups + * + * @param groups + * the subgroups of the group + * @return a column group representing the collection of columns added to + * the group + * + */ + public ColumnGroup addGroup(ColumnGroup... groups) { + assert groups != null : "groups cannot be null"; + + // Gather all groups columns into one list + List<Object> propertyIds = new ArrayList<Object>(); + for (ColumnGroup group : groups) { + propertyIds.addAll(group.getColumns()); + } + + ColumnGroupState state = new ColumnGroupState(); + ColumnGroup group = new ColumnGroup(grid, state, propertyIds); + this.groups.add(group); + + // Update state + for (Object propertyId : group.getColumns()) { + state.columns.add(columnKeys.key(propertyId)); + } + this.state.groups.add(state); + + grid.markAsDirty(); + return group; + } + + /** + * Removes a group from the row. Does not remove the group from subgroups, + * to remove it from the subgroup invoke removeGroup on the subgroup. + * + * @param group + * the group to remove + */ + public void removeGroup(ColumnGroup group) { + int index = groups.indexOf(group); + groups.remove(index); + state.groups.remove(index); + grid.markAsDirty(); + } + + /** + * Get the groups in the row. + * + * @return unmodifiable list of groups in this row + */ + public List<ColumnGroup> getGroups() { + return Collections.unmodifiableList(groups); + } + + /** + * Checks if a property id has been added to a group in this row. + * + * @param propertyId + * the property id to check for + * @return <code>true</code> if the column is included in a group + */ + private boolean hasColumnBeenGrouped(Object propertyId) { + for (ColumnGroup group : groups) { + if (group.isColumnInGroup(propertyId)) { + return true; + } + } + return false; + } + + /** + * Is the header visible for the row. + * + * @return <code>true</code> if header is visible + */ + public boolean isHeaderVisible() { + return state.headerVisible; + } + + /** + * Sets the header visible for the row. + * + * @param visible + * should the header be shown + */ + public void setHeaderVisible(boolean visible) { + state.headerVisible = visible; + grid.markAsDirty(); + } + + /** + * Is the footer visible for the row. + * + * @return <code>true</code> if footer is visible + */ + public boolean isFooterVisible() { + return state.footerVisible; + } + + /** + * Sets the footer visible for the row. + * + * @param visible + * should the footer be shown + */ + public void setFooterVisible(boolean visible) { + state.footerVisible = visible; + grid.markAsDirty(); + } + +} diff --git a/server/src/com/vaadin/ui/components/grid/Grid.java b/server/src/com/vaadin/ui/components/grid/Grid.java index 25ac796d47..2b19043d93 100644 --- a/server/src/com/vaadin/ui/components/grid/Grid.java +++ b/server/src/com/vaadin/ui/components/grid/Grid.java @@ -16,7 +16,9 @@ package com.vaadin.ui.components.grid; +import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; @@ -28,6 +30,7 @@ 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.ColumnGroupRowState; import com.vaadin.shared.ui.grid.GridColumnState; import com.vaadin.shared.ui.grid.GridState; import com.vaadin.ui.AbstractComponent; @@ -52,19 +55,27 @@ import com.vaadin.ui.AbstractComponent; */ public class Grid extends AbstractComponent { + /** + * The data source attached to the grid + */ private Container.Indexed datasource; /** - * Property id -> Column instance mapping + * Property id to column instance mapping */ private final Map<Object, GridColumn> columns = new HashMap<Object, GridColumn>(); /** - * Key generator for column server->client communication + * Key generator for column server-to-client communication */ private final KeyMapper<Object> columnKeys = new KeyMapper<Object>(); /** + * The column groups added to the grid + */ + private final List<ColumnGroupRow> columnGroupRows = new ArrayList<ColumnGroupRow>(); + + /** * Property listener for listening to changes in data source properties. */ private final PropertySetChangeListener propertyListener = new PropertySetChangeListener() { @@ -144,11 +155,11 @@ public class Grid extends AbstractComponent { if (!columns.containsKey(propertyId)) { GridColumn column = appendColumn(propertyId); - // By default use property id as column caption + // Add by default property id as column header column.setHeaderCaption(String.valueOf(propertyId)); - } } + } /** @@ -177,27 +188,27 @@ public class Grid extends AbstractComponent { * @param visible * <code>true</code> if the header rows should be visible */ - public void setHeaderVisible(boolean visible) { - getState().headerVisible = visible; + public void setColumnHeadersVisible(boolean visible) { + getState().columnHeadersVisible = visible; } /** * Are the header rows visible? * - * @return <code>true</code> if the header is visible + * @return <code>true</code> if the headers of the columns are visible */ - public boolean isHeaderVisible() { - return getState(false).headerVisible; + public boolean isColumnHeadersVisible() { + return getState(false).columnHeadersVisible; } /** * Sets the footer rows visible. * * @param visible - * <code>true</code> if the header rows should be visible + * <code>true</code> if the footer rows should be visible */ - public void setFooterVisible(boolean visible) { - getState().footerVisible = visible; + public void setColumnFootersVisible(boolean visible) { + getState().columnFootersVisible = visible; } /** @@ -205,25 +216,110 @@ public class Grid extends AbstractComponent { * * @return <code>true</code> if the footer rows should be visible */ - public boolean isFooterVisible() { - return getState(false).footerVisible; + public boolean isColumnFootersVisible() { + return getState(false).columnFootersVisible; + } + + /** + * <p> + * Adds a new column group to the grid. + * + * <p> + * Column group rows are rendered in the header and footer of the grid. + * Column group rows are made up of column groups which groups together + * columns for adding a common auxiliary header or footer for the columns. + * </p> + * </p> + * + * <p> + * Example usage: + * + * <pre> + * // Add a new column group row to the grid + * ColumnGroupRow row = grid.addColumnGroupRow(); + * + * // Group "Column1" and "Column2" together to form a header in the row + * ColumnGroup column12 = row.addGroup("Column1", "Column2"); + * + * // Set a common header for "Column1" and "Column2" + * column12.setHeader("Column 1&2"); + * </pre> + * + * </p> + * + * @return a column group instance you can use to add column groups + */ + public ColumnGroupRow addColumnGroupRow() { + ColumnGroupRowState state = new ColumnGroupRowState(); + ColumnGroupRow row = new ColumnGroupRow(this, state, columnKeys); + columnGroupRows.add(row); + getState().columnGroupRows.add(state); + return row; + } + + /** + * Adds a new column group to the grid at a specific index + * + * @param rowIndex + * the index of the row + * @return a column group instance you can use to add column groups + */ + public ColumnGroupRow addColumnGroupRow(int rowIndex) { + ColumnGroupRowState state = new ColumnGroupRowState(); + ColumnGroupRow row = new ColumnGroupRow(this, state, columnKeys); + columnGroupRows.add(rowIndex, row); + getState().columnGroupRows.add(rowIndex, state); + return row; + } + + /** + * Removes a column group. + * + * @param row + * the row to remove + */ + public void removeColumnGroupRow(ColumnGroupRow row) { + columnGroupRows.remove(row); + getState().columnGroupRows.remove(row.getState()); + } + + /** + * Gets the column group rows. + * + * @return an unmodifiable list of column group rows + */ + public List<ColumnGroupRow> getColumnGroupRows() { + return Collections.unmodifiableList(new ArrayList<ColumnGroupRow>( + columnGroupRows)); } /** * 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} + * verify if it has been detached from the {@link Grid}. * * @param columnId - * The client id generated for the column when the column is + * 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 + * @return the column with the id or <code>null</code> if not found */ GridColumn getColumnByColumnId(String columnId) { - Object propertyId = columnKeys.get(columnId); + Object propertyId = getPropertyIdByColumnId(columnId); return getColumn(propertyId); } + /** + * Used internally by the {@link Grid} to get a property id by referencing + * the columns generated state id. + * + * @param columnId + * The state id of the column + * @return The column instance or null if not found + */ + Object getPropertyIdByColumnId(String columnId) { + return columnKeys.get(columnId); + } + @Override protected GridState getState() { return (GridState) super.getState(); @@ -241,7 +337,7 @@ public class Grid extends AbstractComponent { * @param datasourcePropertyId * The property id of a property in the datasource */ - protected GridColumn appendColumn(Object datasourcePropertyId) { + private GridColumn appendColumn(Object datasourcePropertyId) { if (datasourcePropertyId == null) { throw new IllegalArgumentException("Property id cannot be null"); } diff --git a/server/src/com/vaadin/ui/components/grid/GridColumn.java b/server/src/com/vaadin/ui/components/grid/GridColumn.java index 505919b3cf..dde0669238 100644 --- a/server/src/com/vaadin/ui/components/grid/GridColumn.java +++ b/server/src/com/vaadin/ui/components/grid/GridColumn.java @@ -16,6 +16,8 @@ package com.vaadin.ui.components.grid; +import java.io.Serializable; + import com.vaadin.shared.ui.grid.GridColumnState; /** @@ -25,10 +27,10 @@ import com.vaadin.shared.ui.grid.GridColumnState; * @since 7.2 * @author Vaadin Ltd */ -public class GridColumn { +public class GridColumn implements Serializable { /** - * The shared state of the column + * The state of the column shared to the client */ private final GridColumnState state; @@ -138,9 +140,16 @@ public class GridColumn { * the new pixel width of the column * @throws IllegalStateException * if the column is no longer attached to any grid + * @throws IllegalArgumentException + * thrown if pixel width is less than zero */ - public void setWidth(int pixelWidth) throws IllegalStateException { + public void setWidth(int pixelWidth) throws IllegalStateException, + IllegalArgumentException { checkColumnIsAttached(); + if (pixelWidth < 0) { + throw new IllegalArgumentException( + "Pixel width should be greated than 0"); + } state.width = pixelWidth; grid.markAsDirty(); } 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 index 5989d537b4..85864160a8 100644 --- a/server/tests/src/com/vaadin/tests/server/component/grid/GridColumns.java +++ b/server/tests/src/com/vaadin/tests/server/component/grid/GridColumns.java @@ -32,6 +32,8 @@ 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.ColumnGroup; +import com.vaadin.ui.components.grid.ColumnGroupRow; import com.vaadin.ui.components.grid.Grid; import com.vaadin.ui.components.grid.GridColumn; @@ -110,9 +112,15 @@ public class GridColumns { assertEquals(100, column.getWidth()); assertEquals(column.getWidth(), getColumnState("column1").width); - column.setWidth(-1); - assertEquals(-1, column.getWidth()); - assertEquals(-1, getColumnState("column1").width); + try { + column.setWidth(-1); + fail("Setting width to -1 should throw exception"); + } catch (IllegalArgumentException iae) { + + } + + assertEquals(100, column.getWidth()); + assertEquals(100, getColumnState("column1").width); } @Test @@ -126,6 +134,7 @@ public class GridColumns { try { column.setHeaderCaption("asd"); + fail("Succeeded in modifying a detached column"); } catch (IllegalStateException ise) { // Detached state should throw exception @@ -157,7 +166,7 @@ public class GridColumns { } @Test - public void testAddingColumn() { + public void testAddingColumn() throws Exception { grid.getContainerDatasource().addContainerProperty("columnX", String.class, ""); GridColumn column = grid.getColumn("columnX"); @@ -165,33 +174,72 @@ public class GridColumns { } @Test - public void testHeaderVisiblility() { + public void testHeaderVisiblility() throws Exception { - assertTrue(grid.isHeaderVisible()); - assertTrue(state.headerVisible); + assertTrue(grid.isColumnHeadersVisible()); + assertTrue(state.columnHeadersVisible); - grid.setHeaderVisible(false); - assertFalse(grid.isHeaderVisible()); - assertFalse(state.headerVisible); + grid.setColumnHeadersVisible(false); + assertFalse(grid.isColumnHeadersVisible()); + assertFalse(state.columnHeadersVisible); - grid.setHeaderVisible(true); - assertTrue(grid.isHeaderVisible()); - assertTrue(state.headerVisible); + grid.setColumnHeadersVisible(true); + assertTrue(grid.isColumnHeadersVisible()); + assertTrue(state.columnHeadersVisible); } @Test - public void testFooterVisibility() { + public void testFooterVisibility() throws Exception { + + assertFalse(grid.isColumnFootersVisible()); + assertFalse(state.columnFootersVisible); - assertTrue(grid.isFooterVisible()); - assertTrue(state.footerVisible); + grid.setColumnFootersVisible(false); + assertFalse(grid.isColumnFootersVisible()); + assertFalse(state.columnFootersVisible); - grid.setFooterVisible(false); - assertFalse(grid.isFooterVisible()); - assertFalse(state.footerVisible); + grid.setColumnFootersVisible(true); + assertTrue(grid.isColumnFootersVisible()); + assertTrue(state.columnFootersVisible); + } - grid.setFooterVisible(true); - assertTrue(grid.isFooterVisible()); - assertTrue(state.footerVisible); + @Test + public void testColumnGroups() throws Exception { + + // Add a new row + ColumnGroupRow row = grid.addColumnGroupRow(); + assertTrue(state.columnGroupRows.size() == 1); + + // Add a group by property id + ColumnGroup columns12 = row.addGroup("column1", "column2"); + assertTrue(state.columnGroupRows.get(0).groups.size() == 1); + + // Set header of column + columns12.setHeaderCaption("Column12"); + assertEquals("Column12", + state.columnGroupRows.get(0).groups.get(0).header); + + // Set footer of column + columns12.setFooterCaption("Footer12"); + assertEquals("Footer12", + state.columnGroupRows.get(0).groups.get(0).footer); + + // Add another group by column instance + ColumnGroup columns34 = row.addGroup(grid.getColumn("column3"), + grid.getColumn("column4")); + assertTrue(state.columnGroupRows.get(0).groups.size() == 2); + + // add another group row + ColumnGroupRow row2 = grid.addColumnGroupRow(); + assertTrue(state.columnGroupRows.size() == 2); + + // add a group by combining the two previous groups + ColumnGroup columns1234 = row2.addGroup(columns12, columns34); + assertTrue(columns1234.getColumns().size() == 4); + + // Insert a group as the second group + ColumnGroupRow newRow2 = grid.addColumnGroupRow(1); + assertTrue(state.columnGroupRows.size() == 3); } private GridColumnState getColumnState(Object propertyId) { |