aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--client/src/main/java/com/vaadin/client/connectors/grid/GridConnector.java9
-rw-r--r--client/src/main/java/com/vaadin/client/widgets/Grid.java36
-rw-r--r--server/src/main/java/com/vaadin/ui/Grid.java35
-rw-r--r--server/src/main/java/com/vaadin/ui/components/grid/Header.java53
-rw-r--r--server/src/main/java/com/vaadin/ui/components/grid/StaticSection.java35
-rw-r--r--shared/src/main/java/com/vaadin/shared/ui/grid/SectionState.java4
-rw-r--r--uitest/src/main/java/com/vaadin/tests/components/grid/basics/GridBasics.java50
-rw-r--r--uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridHeaderFooterTest.java55
8 files changed, 232 insertions, 45 deletions
diff --git a/client/src/main/java/com/vaadin/client/connectors/grid/GridConnector.java b/client/src/main/java/com/vaadin/client/connectors/grid/GridConnector.java
index b7b2fb957d..617a0480ba 100644
--- a/client/src/main/java/com/vaadin/client/connectors/grid/GridConnector.java
+++ b/client/src/main/java/com/vaadin/client/connectors/grid/GridConnector.java
@@ -21,6 +21,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.stream.Collectors;
import com.google.gwt.dom.client.Element;
@@ -235,6 +236,14 @@ public class GridConnector extends AbstractListingConnector
updateHeaderCellFromState(row.getCell(getColumn(columnId)),
cellState);
});
+ for (Map.Entry<CellState, Set<String>> cellGroupEntry : rowState.cellGroups.entrySet()) {
+ Set<String> group = cellGroupEntry.getValue();
+
+ Grid.Column<?, ?>[] columns =
+ group.stream().map(idToColumn::get).toArray(size->new Grid.Column<?, ?>[size]);
+ // Set state to be the same as first in group.
+ updateHeaderCellFromState(row.join(columns), cellGroupEntry.getKey());
+ }
}
}
diff --git a/client/src/main/java/com/vaadin/client/widgets/Grid.java b/client/src/main/java/com/vaadin/client/widgets/Grid.java
index 276cde7666..e1633fdbd3 100644
--- a/client/src/main/java/com/vaadin/client/widgets/Grid.java
+++ b/client/src/main/java/com/vaadin/client/widgets/Grid.java
@@ -442,9 +442,9 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
private StaticSection<?> section;
/**
- * Map from set of spanned columns to cell meta data.
+ * Map from cell meta data to sets of spanned columns .
*/
- private Map<Set<Column<?, ?>>, CELLTYPE> cellGroups = new HashMap<>();
+ private Map<CELLTYPE, Set<Column<?, ?>>> cellGroups = new HashMap<>();
/**
* A custom style name for the row or null if none is set.
@@ -461,9 +461,9 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
* null if not found
*/
public CELLTYPE getCell(Column<?, ?> column) {
- Set<Column<?, ?>> cellGroup = getCellGroupForColumn(column);
- if (cellGroup != null) {
- return cellGroups.get(cellGroup);
+ CELLTYPE cell = getMergedCellForColumn(column);
+ if (cell != null) {
+ return cell;
}
return cells.get(column);
}
@@ -500,7 +500,7 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
throw new IllegalArgumentException(
"Given column does not exists on row "
+ column);
- } else if (getCellGroupForColumn(column) != null) {
+ } else if (getMergedCellForColumn(column) != null) {
throw new IllegalStateException(
"Column is already in a group.");
}
@@ -508,7 +508,7 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
}
CELLTYPE joinedCell = createCell();
- cellGroups.put(columnGroup, joinedCell);
+ cellGroups.put(joinedCell, columnGroup);
joinedCell.setSection(getSection());
calculateColspans();
@@ -549,12 +549,10 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
return join(columns);
}
- private Set<Column<?, ?>> getCellGroupForColumn(
+ private CELLTYPE getMergedCellForColumn(
Column<?, ?> column) {
- for (Set<Column<?, ?>> group : cellGroups.keySet()) {
- if (group.contains(column)) {
- return group;
- }
+ for (Entry<CELLTYPE, Set<Column<?, ?>>> entry : cellGroups.entrySet()) {
+ if (entry.getValue().contains(column)) return entry.getKey();
}
return null;
}
@@ -565,22 +563,22 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
cell.setColspan(1);
}
// Set colspan for grouped cells
- for (Set<Column<?, ?>> group : cellGroups.keySet()) {
- if (!checkMergedCellIsContinuous(group)) {
+ for (Entry<CELLTYPE, Set<Column<?, ?>>> entry : cellGroups.entrySet()) {
+ CELLTYPE mergedCell = entry.getKey();
+ if (!checkMergedCellIsContinuous(entry.getValue())) {
// on error simply break the merged cell
- cellGroups.get(group).setColspan(1);
+ mergedCell.setColspan(1);
} else {
int colSpan = 0;
- for (Column<?, ?> column : group) {
+ for (Column<?, ?> column : entry.getValue()) {
if (!column.isHidden()) {
colSpan++;
}
}
// colspan can't be 0
- cellGroups.get(group).setColspan(Math.max(1, colSpan));
+ mergedCell.setColspan(Math.max(1, colSpan));
}
}
-
}
private boolean checkMergedCellIsContinuous(
@@ -2399,7 +2397,7 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
* An initial height that is given to new details rows before rendering the
* appropriate widget that we then can be measure
*
- * @see GridSpacerUpdater
+ * @see Grid.GridSpacerUpdater
*/
private static final double DETAILS_ROW_INITIAL_HEIGHT = 50;
diff --git a/server/src/main/java/com/vaadin/ui/Grid.java b/server/src/main/java/com/vaadin/ui/Grid.java
index b9f5f5afbb..ee1547bfce 100644
--- a/server/src/main/java/com/vaadin/ui/Grid.java
+++ b/server/src/main/java/com/vaadin/ui/Grid.java
@@ -1540,6 +1540,25 @@ public class Grid<T> extends AbstractListing<T> implements HasComponents {
public default HeaderCell getCell(Column<?, ?> column) {
return getCell(column.getId());
}
+
+ /**
+ * Merges columns cells in a row
+ *
+ * @param cells
+ * The cells to merge. Must be from the same row.
+ * @return The remaining visible cell after the merge
+ */
+ HeaderCell join(Set<HeaderCell> cellsToMerge);
+
+ /**
+ * Merges columns cells in a row
+ *
+ * @param cells
+ * The cells to merge. Must be from the same row.
+ * @return The remaining visible cell after the merge
+ */
+ HeaderCell join(HeaderCell ... cellsToMerge);
+
}
/**
@@ -1599,6 +1618,13 @@ public class Grid<T> extends AbstractListing<T> implements HasComponents {
* @return cell content type
*/
public GridStaticCellType getCellType();
+
+ /**
+ * Gets the column id where this cell is.
+ *
+ * @return column id for this cell
+ */
+ public String getColumnId();
}
/**
@@ -1894,11 +1920,12 @@ public class Grid<T> extends AbstractListing<T> implements HasComponents {
*/
public void removeColumn(Column<T, ?> column) {
if (columnSet.remove(column)) {
- columnKeys.remove(column.getId());
- removeDataGenerator(column);
- getHeader().removeColumn(column.getId());
+ String columnId = column.getId();
+ columnKeys.remove(columnId);
column.remove();
-
+ getHeader().removeColumn(columnId);
+ getFooter().removeColumn(columnId);
+ getState(true).columnOrder.remove(columnId);
}
}
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
index 31f80a9add..6a4b72c5f9 100644
--- a/server/src/main/java/com/vaadin/ui/components/grid/Header.java
+++ b/server/src/main/java/com/vaadin/ui/components/grid/Header.java
@@ -17,11 +17,15 @@ package com.vaadin.ui.components.grid;
import com.vaadin.ui.Grid;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
/**
* Represents the header section of a Grid.
- *
+ *
* @author Vaadin Ltd.
- *
+ *
* @since 8.0
*/
public abstract class Header extends StaticSection<Header.Row> {
@@ -64,7 +68,7 @@ public abstract class Header extends StaticSection<Header.Row> {
/**
* Returns whether this row is the default header row.
- *
+ *
* @return {@code true} if this row is the default row, {@code false}
* otherwise.
*/
@@ -74,13 +78,52 @@ public abstract class Header extends StaticSection<Header.Row> {
/**
* Sets whether this row is the default header row.
- *
+ *
* @param defaultHeader
* {@code true} to set to default, {@code false} otherwise.
*/
protected void setDefault(boolean defaultHeader) {
getRowState().defaultHeader = defaultHeader;
}
+
+ /**
+ * Merges columns cells in a row
+ *
+ * @param cellsToMerge
+ * the cells which should be merged
+ * @return the remaining visible cell after the merge
+ */
+ @Override
+ public Grid.HeaderCell join(Set<Grid.HeaderCell> cellsToMerge) {
+ for (Grid.HeaderCell cell : cellsToMerge) {
+ checkIfAlreadyMerged(cell.getColumnId());
+ }
+
+ // Create new cell data for the group
+ Cell newCell = createCell();
+
+ Set<String> columnGroup = new HashSet<>();
+ for (Grid.HeaderCell cell : cellsToMerge) {
+ columnGroup.add(cell.getColumnId());
+ }
+ addMergedCell(newCell, columnGroup);
+ markAsDirty();
+ return newCell;
+ }
+
+ /**
+ * Merges columns cells in a row
+ *
+ * @param cellsToMerge
+ * the cells which should be merged
+ * @return the remaining visible cell after the merge
+ */
+ @Override
+ public Grid.HeaderCell join(Grid.HeaderCell... cellsToMerge) {
+ Set<Grid.HeaderCell> headerCells = new HashSet<>(Arrays.asList(cellsToMerge));
+ return join(headerCells);
+ }
+
}
@Override
@@ -99,7 +142,7 @@ public abstract class Header extends StaticSection<Header.Row> {
/**
* Returns the default row of this header. The default row displays column
* captions and sort indicators.
- *
+ *
* @return the default row, or {@code null} if there is no default row
*/
public Row getDefaultRow() {
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
index 678ab2a935..5c7d682b17 100644
--- a/server/src/main/java/com/vaadin/ui/components/grid/StaticSection.java
+++ b/server/src/main/java/com/vaadin/ui/components/grid/StaticSection.java
@@ -19,10 +19,12 @@ import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import com.vaadin.shared.ui.grid.GridStaticCellType;
import com.vaadin.shared.ui.grid.SectionState;
@@ -56,7 +58,7 @@ public abstract class StaticSection<ROW extends StaticSection.StaticRow<?>>
private final RowState rowState = new RowState();
private final StaticSection<?> section;
- private final Map<Object, CELL> cells = new LinkedHashMap<>();
+ private final Map<String, CELL> cells = new LinkedHashMap<>();
/**
* Creates a new row belonging to the given section.
@@ -102,10 +104,17 @@ public abstract class StaticSection<ROW extends StaticSection.StaticRow<?>>
* @param columnId
* the id of the column from which to remove the cell
*/
- protected void removeCell(Object columnId) {
+ protected void removeCell(String columnId) {
CELL cell = cells.remove(columnId);
if (cell != null) {
- rowState.cells.remove(cell.getCellState());
+ rowState.cells.remove(columnId);
+ for (Iterator<Set<String>> iterator = rowState.cellGroups.values().iterator(); iterator.hasNext(); ) {
+ Set<String> group = iterator.next();
+ group.remove(columnId);
+ if(group.size() < 2) {
+ iterator.remove();
+ }
+ }
}
}
@@ -143,6 +152,23 @@ public abstract class StaticSection<ROW extends StaticSection.StaticRow<?>>
cell.detach();
}
}
+
+ void checkIfAlreadyMerged(String columnId) {
+ for (Set<String> cellGroup : getRowState().cellGroups.values()) {
+ if (cellGroup.contains(columnId)) {
+ throw new IllegalArgumentException(
+ "Cell " + columnId + " is already merged");
+ }
+ }
+ if (!cells.containsKey(columnId)) {
+ throw new IllegalArgumentException(
+ "Cell " + columnId + " does not exist on this row");
+ }
+ }
+
+ void addMergedCell(CELL newCell, Set<String> columnGroup) {
+ rowState.cellGroups.put(newCell.getCellState(), columnGroup);
+ }
}
/**
@@ -161,7 +187,7 @@ public abstract class StaticSection<ROW extends StaticSection.StaticRow<?>>
cellState.columnId = id;
}
- String getColumnId() {
+ public String getColumnId() {
return cellState.columnId;
}
@@ -411,6 +437,7 @@ public abstract class StaticSection<ROW extends StaticSection.StaticRow<?>>
for (ROW row : rows) {
row.removeCell(columnId);
}
+ markAsDirty();
}
/**
diff --git a/shared/src/main/java/com/vaadin/shared/ui/grid/SectionState.java b/shared/src/main/java/com/vaadin/shared/ui/grid/SectionState.java
index 39d2d2f2e9..dda6d3fe9f 100644
--- a/shared/src/main/java/com/vaadin/shared/ui/grid/SectionState.java
+++ b/shared/src/main/java/com/vaadin/shared/ui/grid/SectionState.java
@@ -20,6 +20,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import com.vaadin.shared.Connector;
@@ -37,6 +38,9 @@ public class SectionState implements Serializable {
/** The map from column ids to the cells in this row. */
public Map<String, CellState> cells = new HashMap<>();
+ /** The map from a joint cell to column id sets in this row. */
+ public Map<CellState, Set<String>> cellGroups = new HashMap<>();
+
/**
* Whether this row is the default header row. Always false for footer
* rows.
diff --git a/uitest/src/main/java/com/vaadin/tests/components/grid/basics/GridBasics.java b/uitest/src/main/java/com/vaadin/tests/components/grid/basics/GridBasics.java
index 7b48acd400..f5f02bbf60 100644
--- a/uitest/src/main/java/com/vaadin/tests/components/grid/basics/GridBasics.java
+++ b/uitest/src/main/java/com/vaadin/tests/components/grid/basics/GridBasics.java
@@ -1,15 +1,5 @@
package com.vaadin.tests.components.grid.basics;
-import java.text.DecimalFormat;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.function.Consumer;
-import java.util.stream.Stream;
-
import com.vaadin.annotations.Widgetset;
import com.vaadin.server.VaadinRequest;
import com.vaadin.shared.Registration;
@@ -36,6 +26,18 @@ import com.vaadin.ui.renderers.HtmlRenderer;
import com.vaadin.ui.renderers.NumberRenderer;
import com.vaadin.ui.renderers.ProgressBarRenderer;
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+
@Widgetset("com.vaadin.DefaultWidgetSet")
public class GridBasics extends AbstractTestUIWithLog {
@@ -50,9 +52,9 @@ public class GridBasics extends AbstractTestUIWithLog {
public static final String CELL_STYLE_GENERATOR_EMPTY = "Empty string";
public static final String CELL_STYLE_GENERATOR_NULL = "Null";
- public static final String[] COLUMN_CAPTIONS = { "Column 0", "Column 1",
+ public static final String[] COLUMN_CAPTIONS = {"Column 0", "Column 1",
"Column 2", "Row Number", "Date", "HTML String", "Big Random",
- "Small Random" };
+ "Small Random"};
private final Command toggleReorderListenerCommand = new Command() {
private Registration registration = null;
@@ -272,6 +274,9 @@ public class GridBasics extends AbstractTestUIWithLog {
selectedItem -> col
.setHidden(selectedItem.isChecked()))
.setCheckable(true);
+ columnMenu
+ .addItem("Remove",
+ selectedItem -> grid.removeColumn(col));
}
}
@@ -396,7 +401,7 @@ public class GridBasics extends AbstractTestUIWithLog {
}
private <T> void addGridMethodMenu(MenuItem parent, String name, T value,
- Consumer<T> method) {
+ Consumer<T> method) {
parent.addItem(name, menuItem -> method.accept(value));
}
@@ -437,6 +442,25 @@ public class GridBasics extends AbstractTestUIWithLog {
headerMenu.addItem("Set no default row", menuItem -> {
grid.setDefaultHeaderRow(null);
});
+ headerMenu.addItem("Merge Header Cells [0,0..1]", menuItem -> {
+ mergeHeaderСells(0, "0+1", 0, 1);
+ });
+ headerMenu.addItem("Merge Header Cells [1,1..3]", menuItem -> {
+ mergeHeaderСells(1, "1+2+3", 1, 2, 3);
+ });
+ headerMenu.addItem("Merge Header Cells [0,6..7]", menuItem -> {
+ mergeHeaderСells(0, "6+7", 6, 7);
+ });
+ }
+
+ private void mergeHeaderСells(int rowIndex, String jointCellText, int... columnIndexes) {
+ HeaderRow headerRow = grid.getHeaderRow(rowIndex);
+ List<Column<DataObject, ?>> columns = grid.getColumns();
+ Set<Grid.HeaderCell> toMerge = new HashSet<>();
+ for (int columnIndex : columnIndexes) {
+ toMerge.add(headerRow.getCell(columns.get(columnIndex)));
+ }
+ headerRow.join(toMerge).setText(jointCellText);
}
private void createFooterMenu(MenuItem footerMenu) {
diff --git a/uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridHeaderFooterTest.java b/uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridHeaderFooterTest.java
index bff780ee75..94f30fe279 100644
--- a/uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridHeaderFooterTest.java
+++ b/uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridHeaderFooterTest.java
@@ -260,6 +260,61 @@ public class GridHeaderFooterTest extends GridBasicsTest {
assertEquals("Column 1", getColumnHidingToggle(1).getText());
}
+ @Test
+ public void testHeaderMergedRemoveColumn() {
+ selectMenuPath("Component", "Header", "Append header row");
+ selectMenuPath("Component", "Header", "Merge Header Cells [0,0..1]");
+
+ GridCellElement c00 = getGridElement().getHeaderCell(0, 0);
+ assertEquals("0+1", c00.getText());
+ assertEquals("Colspan of cell [0,0]", "2", c00.getAttribute("colspan"));
+
+ selectMenuPath("Component", "Columns", "Column 1", "Remove");
+ selectMenuPath("Component", "Header", "Append header row");
+
+ c00 = getGridElement().getHeaderCell(0, 0);
+ assertEquals("Column 0", c00.getText());
+ assertEquals("Colspan of cell [0,0]", "1", c00.getAttribute("colspan"));
+
+ GridCellElement c01 = getGridElement().getHeaderCell(0, 1);
+ assertEquals("Column 2", c01.getText());
+
+ GridCellElement c10 = getGridElement().getHeaderCell(1, 0);
+ assertEquals("Header cell 0", c10.getText());
+
+ GridCellElement c11 = getGridElement().getHeaderCell(1, 1);
+ assertEquals("Header cell 2", c11.getText());
+
+ GridCellElement c20 = getGridElement().getHeaderCell(2, 0);
+ assertEquals("Header cell 0", c20.getText());
+
+ GridCellElement c21 = getGridElement().getHeaderCell(2, 1);
+ assertEquals("Header cell 1", c21.getText());
+
+
+ }
+
+ @Test
+ public void testHeaderMerge() {
+ selectMenuPath("Component", "Header", "Append header row");
+ selectMenuPath("Component", "Header", "Merge Header Cells [0,0..1]");
+ selectMenuPath("Component", "Header", "Merge Header Cells [1,1..3]");
+ selectMenuPath("Component", "Header", "Merge Header Cells [0,6..7]");
+
+ GridCellElement mergedCell1 = getGridElement().getHeaderCell(0, 0);
+ assertEquals("0+1", mergedCell1.getText());
+ assertEquals("Colspan, cell [0,0]", "2", mergedCell1.getAttribute("colspan"));
+
+ GridCellElement mergedCell2 = getGridElement().getHeaderCell(1, 1);
+ assertEquals("1+2+3", mergedCell2.getText());
+ assertEquals("Colspan of cell [1,1]", "3", mergedCell2.getAttribute("colspan"));
+
+ GridCellElement mergedCell3 = getGridElement().getHeaderCell(0, 6);
+ assertEquals("6+7", mergedCell3.getText());
+ assertEquals("Colspan of cell [0,6]", "2", mergedCell3.getAttribute("colspan"));
+
+ }
+
private void toggleColumnHidable(int index) {
selectMenuPath("Component", "Columns", "Column " + index, "Hidable");
}