Browse Source

Grid merging header cells

Change-Id: Ia52bbef412fc8701f6b862960dfed9c08c17ff7a
tags/8.0.0.alpha8
elmot 7 years ago
parent
commit
4e8eb29c54

+ 9
- 0
client/src/main/java/com/vaadin/client/connectors/grid/GridConnector.java View File

@@ -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());
}
}
}


+ 17
- 19
client/src/main/java/com/vaadin/client/widgets/Grid.java View File

@@ -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;


+ 31
- 4
server/src/main/java/com/vaadin/ui/Grid.java View File

@@ -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);
}
}


+ 48
- 5
server/src/main/java/com/vaadin/ui/components/grid/Header.java View File

@@ -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() {

+ 31
- 4
server/src/main/java/com/vaadin/ui/components/grid/StaticSection.java View File

@@ -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();
}

/**

+ 4
- 0
shared/src/main/java/com/vaadin/shared/ui/grid/SectionState.java View File

@@ -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.

+ 37
- 13
uitest/src/main/java/com/vaadin/tests/components/grid/basics/GridBasics.java View File

@@ -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) {

+ 55
- 0
uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridHeaderFooterTest.java View File

@@ -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");
}

Loading…
Cancel
Save