diff options
author | Teemu Suo-Anttila <teemusa@vaadin.com> | 2014-07-30 15:06:36 +0300 |
---|---|---|
committer | Vaadin Code Review <review@vaadin.com> | 2014-08-05 07:29:30 +0000 |
commit | df68ce33bde680de12cdf6457311c35662252da2 (patch) | |
tree | 6f96a99f5a1ea317518df082faa415a0db13610d | |
parent | 681e0d6dc574e6c7f2993f04e40700179f0ef69f (diff) | |
download | vaadin-framework-df68ce33bde680de12cdf6457311c35662252da2.tar.gz vaadin-framework-df68ce33bde680de12cdf6457311c35662252da2.zip |
Add support for server side colspans in Headers and Footers (#13334)
Change-Id: I311aade38f4e725405a190ca1b6f114b4ac07053
11 files changed, 403 insertions, 12 deletions
diff --git a/client/src/com/vaadin/client/ui/grid/FlyweightCell.java b/client/src/com/vaadin/client/ui/grid/FlyweightCell.java index 17301214c8..de003f865c 100644 --- a/client/src/com/vaadin/client/ui/grid/FlyweightCell.java +++ b/client/src/com/vaadin/client/ui/grid/FlyweightCell.java @@ -79,6 +79,14 @@ public class FlyweightCell { } /** + * Return the colspan attribute of the element of the cell. + */ + public int getColSpan() { + assertSetup(); + return element.getPropertyInt(COLSPAN_ATTR); + } + + /** * Sets the DOM element for this FlyweightCell, either a <code>TD</code> or * a <code>TH</code>. It is the caller's responsibility to actually insert * the given element to the document when needed. diff --git a/client/src/com/vaadin/client/ui/grid/Grid.java b/client/src/com/vaadin/client/ui/grid/Grid.java index f800a8cb3b..f280b26493 100644 --- a/client/src/com/vaadin/client/ui/grid/Grid.java +++ b/client/src/com/vaadin/client/ui/grid/Grid.java @@ -123,10 +123,13 @@ public class Grid<T> extends Composite implements RowContainer cellContainer) { int cellRow = cell.getRow(); int cellColumn = cell.getColumn(); + int colSpan = cell.getColSpan(); + boolean columnActive = Range.withLength(cellColumn, colSpan) + .contains(activeColumn); if (cellContainer == container) { // Cell is in the current container - if (cellRow == activeRow && cellColumn == activeColumn) { + if (cellRow == activeRow && columnActive) { if (cellWithActiveStyle != cell.getElement()) { // Cell is correct but it does not have active style if (cellWithActiveStyle != null) { @@ -152,7 +155,7 @@ public class Grid<T> extends Composite implements || cellContainer == escalator.getFooter()) { // Correct header and footer column also needs highlighting setStyleName(cell.getElement(), headerFooterActiveStyleName, - cellColumn == activeColumn); + columnActive); } } diff --git a/client/src/com/vaadin/client/ui/grid/GridConnector.java b/client/src/com/vaadin/client/ui/grid/GridConnector.java index 2a06aba3e6..2bbedaaecf 100644 --- a/client/src/com/vaadin/client/ui/grid/GridConnector.java +++ b/client/src/com/vaadin/client/ui/grid/GridConnector.java @@ -36,11 +36,13 @@ import com.vaadin.client.data.DataSource.RowHandle; import com.vaadin.client.data.RpcDataSourceConnector.RpcDataSource; import com.vaadin.client.ui.AbstractComponentConnector; import com.vaadin.client.ui.grid.GridHeader.HeaderRow; +import com.vaadin.client.ui.grid.GridStaticSection.StaticCell; import com.vaadin.client.ui.grid.GridStaticSection.StaticRow; import com.vaadin.client.ui.grid.renderers.AbstractRendererConnector; import com.vaadin.client.ui.grid.selection.AbstractRowHandleSelectionModel; import com.vaadin.client.ui.grid.selection.SelectionChangeEvent; import com.vaadin.client.ui.grid.selection.SelectionChangeHandler; +import com.vaadin.client.ui.grid.selection.SelectionModel; import com.vaadin.client.ui.grid.selection.SelectionModelMulti; import com.vaadin.client.ui.grid.selection.SelectionModelNone; import com.vaadin.client.ui.grid.selection.SelectionModelSingle; @@ -288,9 +290,24 @@ public class GridConnector extends AbstractComponentConnector { assert rowState.cells.size() == getWidget().getColumnCount(); + int diff = 1; + if (getWidget().getSelectionModel() instanceof SelectionModel.None) { + diff = 0; + } + int i = 0; for (CellState cellState : rowState.cells) { - row.getCell(i++).setText(cellState.text); + StaticCell cell = row.getCell(diff + (i++)); + cell.setText(cellState.text); + } + + for (List<Integer> group : rowState.cellGroups) { + GridColumn<?, ?>[] columns = new GridColumn<?, ?>[group.size()]; + i = 0; + for (Integer colIndex : group) { + columns[i++] = getWidget().getColumn(diff + colIndex); + } + row.join(columns); } if (section instanceof GridHeader && rowState.defaultRow) { diff --git a/server/src/com/vaadin/ui/components/grid/GridFooter.java b/server/src/com/vaadin/ui/components/grid/GridFooter.java index e4a7eab5d1..84b2b70090 100644 --- a/server/src/com/vaadin/ui/components/grid/GridFooter.java +++ b/server/src/com/vaadin/ui/components/grid/GridFooter.java @@ -54,7 +54,7 @@ public class GridFooter extends GridStaticSection<GridFooter.FooterRow> { } @Override - protected GridStaticSectionState getState() { + protected GridStaticSectionState getSectionState() { return footerState; } diff --git a/server/src/com/vaadin/ui/components/grid/GridHeader.java b/server/src/com/vaadin/ui/components/grid/GridHeader.java index f8bd3c6642..67f7bfdf69 100644 --- a/server/src/com/vaadin/ui/components/grid/GridHeader.java +++ b/server/src/com/vaadin/ui/components/grid/GridHeader.java @@ -57,7 +57,7 @@ public class GridHeader extends GridStaticSection<GridHeader.HeaderRow> { HeaderRow row = createRow(); rows.add(row); setDefaultRow(row); - getState().rows.add(row.getRowState()); + getSectionState().rows.add(row.getRowState()); } /** @@ -103,7 +103,7 @@ public class GridHeader extends GridStaticSection<GridHeader.HeaderRow> { } @Override - protected GridStaticSectionState getState() { + protected GridStaticSectionState getSectionState() { return headerState; } diff --git a/server/src/com/vaadin/ui/components/grid/GridStaticSection.java b/server/src/com/vaadin/ui/components/grid/GridStaticSection.java index 80d822e7bc..8c983052ea 100644 --- a/server/src/com/vaadin/ui/components/grid/GridStaticSection.java +++ b/server/src/com/vaadin/ui/components/grid/GridStaticSection.java @@ -17,6 +17,10 @@ package com.vaadin.ui.components.grid; import java.io.Serializable; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -49,6 +53,7 @@ abstract class GridStaticSection<ROWTYPE extends GridStaticSection.StaticRow<?>> private RowState rowState = new RowState(); protected GridStaticSection<?> section; private Map<Object, CELLTYPE> cells = new LinkedHashMap<Object, CELLTYPE>(); + private Collection<List<CELLTYPE>> cellGroups = new HashSet<List<CELLTYPE>>(); protected StaticRow(GridStaticSection<?> section) { this.section = section; @@ -83,6 +88,98 @@ abstract class GridStaticSection<ROWTYPE extends GridStaticSection.StaticRow<?>> public CELLTYPE getCell(Object propertyId) { return cells.get(propertyId); } + + /** + * Merges cells in a row + * + * @param cells + * The cells to be merged + * @return The first cell of the merged cells + */ + protected CELLTYPE join(List<CELLTYPE> cells) { + assert cells.size() > 1 : "You cannot merge less than 2 cells together"; + + // Ensure no cell is already grouped + for (CELLTYPE cell : cells) { + if (getCellGroupForCell(cell) != null) { + throw new IllegalStateException("Cell " + cell.getText() + + " is already grouped."); + } + } + + // Ensure continuous range + Iterator<CELLTYPE> cellIterator = this.cells.values().iterator(); + CELLTYPE current = null; + int firstIndex = 0; + + while (cellIterator.hasNext()) { + current = cellIterator.next(); + if (current == cells.get(0)) { + break; + } + firstIndex++; + } + + for (int i = 1; i < cells.size(); ++i) { + current = cellIterator.next(); + + if (current != cells.get(i)) { + throw new IllegalStateException( + "Cell range must be a continous range"); + } + } + + // Create a new group + final ArrayList<CELLTYPE> cellGroup = new ArrayList<CELLTYPE>(cells); + cellGroups.add(cellGroup); + + // Add group to state + List<Integer> stateGroup = new ArrayList<Integer>(); + for (int i = 0; i < cells.size(); ++i) { + stateGroup.add(firstIndex + i); + } + rowState.cellGroups.add(stateGroup); + section.markAsDirty(); + + // Returns first cell of group + return cells.get(0); + } + + /** + * Merges columns cells in a row + * + * @param properties + * The column properties which header should be merged + * @return The remaining visible cell after the merge + */ + public CELLTYPE join(Object... properties) { + List<CELLTYPE> cells = new ArrayList<CELLTYPE>(); + for (int i = 0; i < properties.length; ++i) { + cells.add(getCell(properties[i])); + } + + return join(cells); + } + + /** + * 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 + */ + public CELLTYPE join(CELLTYPE... cells) { + return join(Arrays.asList(cells)); + } + + private List<CELLTYPE> getCellGroupForCell(CELLTYPE cell) { + for (List<CELLTYPE> group : cellGroups) { + if (group.contains(cell)) { + return group; + } + } + return null; + } } /** @@ -148,8 +245,8 @@ abstract class GridStaticSection<ROWTYPE extends GridStaticSection.StaticRow<?>> * true to show this section, false to hide */ public void setVisible(boolean visible) { - if (getState().visible != visible) { - getState().visible = visible; + if (getSectionState().visible != visible) { + getSectionState().visible = visible; markAsDirty(); } } @@ -160,7 +257,7 @@ abstract class GridStaticSection<ROWTYPE extends GridStaticSection.StaticRow<?>> * @return true if visible, false otherwise. */ public boolean isVisible() { - return getState().visible; + return getSectionState().visible; } /** @@ -174,7 +271,7 @@ abstract class GridStaticSection<ROWTYPE extends GridStaticSection.StaticRow<?>> */ public ROWTYPE removeRow(int rowIndex) { ROWTYPE row = rows.remove(rowIndex); - getState().rows.remove(rowIndex); + getSectionState().rows.remove(rowIndex); markAsDirty(); return row; @@ -240,7 +337,7 @@ abstract class GridStaticSection<ROWTYPE extends GridStaticSection.StaticRow<?>> public ROWTYPE addRowAt(int index) { ROWTYPE row = createRow(); rows.add(index, row); - getState().rows.add(index, row.getRowState()); + getSectionState().rows.add(index, row.getRowState()); Indexed dataSource = grid.getContainerDatasource(); for (Object id : dataSource.getContainerPropertyIds()) { @@ -260,7 +357,7 @@ abstract class GridStaticSection<ROWTYPE extends GridStaticSection.StaticRow<?>> return rows.size(); } - protected abstract GridStaticSectionState getState(); + protected abstract GridStaticSectionState getSectionState(); protected abstract ROWTYPE createRow(); diff --git a/server/tests/src/com/vaadin/tests/server/component/grid/GridStaticSection.java b/server/tests/src/com/vaadin/tests/server/component/grid/GridStaticSection.java new file mode 100644 index 0000000000..e89f6a8c6e --- /dev/null +++ b/server/tests/src/com/vaadin/tests/server/component/grid/GridStaticSection.java @@ -0,0 +1,105 @@ +/* + * Copyright 2000-2014 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 org.junit.Before; +import org.junit.Test; + +import com.vaadin.data.Container.Indexed; +import com.vaadin.data.util.IndexedContainer; +import com.vaadin.ui.components.grid.Grid; +import com.vaadin.ui.components.grid.GridFooter; +import com.vaadin.ui.components.grid.GridFooter.FooterRow; +import com.vaadin.ui.components.grid.GridHeader; +import com.vaadin.ui.components.grid.GridHeader.HeaderRow; + +public class GridStaticSection { + + private Indexed dataSource = new IndexedContainer(); + private Grid grid; + + @Before + public void setUp() { + dataSource.addContainerProperty("firstName", String.class, ""); + dataSource.addContainerProperty("lastName", String.class, ""); + dataSource.addContainerProperty("streetAddress", String.class, ""); + dataSource.addContainerProperty("zipCode", Integer.class, null); + grid = new Grid(dataSource); + } + + @Test + public void testAddAndRemoveHeaders() { + + final GridHeader section = grid.getHeader(); + assertEquals(1, section.getRowCount()); + section.prependRow(); + assertEquals(2, section.getRowCount()); + section.removeRow(0); + assertEquals(1, section.getRowCount()); + section.removeRow(0); + assertEquals(0, section.getRowCount()); + assertEquals(null, section.getDefaultRow()); + HeaderRow row = section.appendRow(); + assertEquals(1, section.getRowCount()); + assertEquals(null, section.getDefaultRow()); + section.setDefaultRow(row); + assertEquals(row, section.getDefaultRow()); + } + + @Test + public void testAddAndRemoveFooters() { + final GridFooter section = grid.getFooter(); + + // By default there are no footer rows + assertEquals(0, section.getRowCount()); + FooterRow row = section.appendRow(); + + assertEquals(1, section.getRowCount()); + section.prependRow(); + assertEquals(2, section.getRowCount()); + assertEquals(row, section.getRow(1)); + section.removeRow(0); + assertEquals(1, section.getRowCount()); + section.removeRow(0); + assertEquals(0, section.getRowCount()); + } + + @Test + public void testJoinHeaderCells() { + final GridHeader section = grid.getHeader(); + HeaderRow mergeRow = section.prependRow(); + mergeRow.join("firstName", "lastName").setText("Name"); + mergeRow.join(mergeRow.getCell("streetAddress"), + mergeRow.getCell("zipCode")); + } + + @Test(expected = IllegalStateException.class) + public void testJoinHeaderCellsIncorrectly() { + final GridHeader section = grid.getHeader(); + HeaderRow mergeRow = section.prependRow(); + mergeRow.join("firstName", "zipCode").setText("Name"); + } + + @Test + public void testJoinAllFooterrCells() { + final GridFooter section = grid.getFooter(); + FooterRow mergeRow = section.prependRow(); + mergeRow.join(dataSource.getContainerPropertyIds().toArray()).setText( + "All the stuff."); + } +} diff --git a/shared/src/com/vaadin/shared/ui/grid/GridStaticSectionState.java b/shared/src/com/vaadin/shared/ui/grid/GridStaticSectionState.java index 859e01f089..41f56199da 100644 --- a/shared/src/com/vaadin/shared/ui/grid/GridStaticSectionState.java +++ b/shared/src/com/vaadin/shared/ui/grid/GridStaticSectionState.java @@ -35,6 +35,8 @@ public class GridStaticSectionState implements Serializable { public List<CellState> cells = new ArrayList<CellState>(); public boolean defaultRow = false; + + public List<List<Integer>> cellGroups = new ArrayList<List<Integer>>(); } public List<RowState> rows = new ArrayList<RowState>(); diff --git a/uitest/src/com/vaadin/tests/components/grid/GridColspans.java b/uitest/src/com/vaadin/tests/components/grid/GridColspans.java new file mode 100644 index 0000000000..be12c2bcb2 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/grid/GridColspans.java @@ -0,0 +1,81 @@ +/* + * Copyright 2000-2014 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 com.vaadin.data.Container.Indexed; +import com.vaadin.data.Item; +import com.vaadin.data.util.IndexedContainer; +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUI; +import com.vaadin.ui.components.grid.Grid; +import com.vaadin.ui.components.grid.GridFooter; +import com.vaadin.ui.components.grid.GridFooter.FooterRow; +import com.vaadin.ui.components.grid.GridHeader; +import com.vaadin.ui.components.grid.GridHeader.HeaderRow; +import com.vaadin.ui.components.grid.renderers.NumberRenderer; + +public class GridColspans extends AbstractTestUI { + + @Override + protected void setup(VaadinRequest request) { + Indexed dataSource = new IndexedContainer(); + Grid grid; + + dataSource.addContainerProperty("firstName", String.class, ""); + dataSource.addContainerProperty("lastName", String.class, ""); + dataSource.addContainerProperty("streetAddress", String.class, ""); + dataSource.addContainerProperty("zipCode", Integer.class, null); + dataSource.addContainerProperty("city", String.class, ""); + Item i = dataSource.addItem(0); + i.getItemProperty("firstName").setValue("Rudolph"); + i.getItemProperty("lastName").setValue("Reindeer"); + i.getItemProperty("streetAddress").setValue("Ruukinkatu 2-4"); + i.getItemProperty("zipCode").setValue(20540); + i.getItemProperty("city").setValue("Turku"); + grid = new Grid(dataSource); + grid.setWidth("600px"); + grid.getColumn("zipCode").setRenderer(new NumberRenderer()); + addComponent(grid); + + GridHeader header = grid.getHeader(); + HeaderRow row = header.prependRow(); + row.join("firstName", "lastName").setText("Full Name"); + row.join("streetAddress", "zipCode", "city").setText("Address"); + header.prependRow() + .join(dataSource.getContainerPropertyIds().toArray()) + .setText("All the stuff"); + + GridFooter footer = grid.getFooter(); + FooterRow footerRow = footer.appendRow(); + footerRow.join("firstName", "lastName").setText("Full Name"); + footerRow.join("streetAddress", "zipCode", "city").setText("Address"); + footer.appendRow().join(dataSource.getContainerPropertyIds().toArray()) + .setText("All the stuff"); + + footer.setVisible(true); + } + + @Override + protected String getTestDescription() { + return "Grid header and footer colspans"; + } + + @Override + protected Integer getTicketNumber() { + return 13334; + } + +} diff --git a/uitest/src/com/vaadin/tests/components/grid/GridColspansTest.java b/uitest/src/com/vaadin/tests/components/grid/GridColspansTest.java new file mode 100644 index 0000000000..dad9399466 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/grid/GridColspansTest.java @@ -0,0 +1,73 @@ +/* + * Copyright 2000-2014 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 static org.junit.Assert.assertEquals; + +import java.io.IOException; + +import org.junit.Test; +import org.openqa.selenium.Keys; +import org.openqa.selenium.interactions.Actions; + +import com.vaadin.tests.annotations.TestCategory; +import com.vaadin.tests.tb3.MultiBrowserTest; + +@TestCategory("grid") +public class GridColspansTest extends MultiBrowserTest { + + @Test + public void testHeaderColSpans() { + openTestURL(); + + GridElement grid = $(GridElement.class).first(); + assertEquals("5", grid.getHeaderCell(0, 1).getAttribute("colspan")); + assertEquals("2", grid.getHeaderCell(1, 1).getAttribute("colspan")); + assertEquals("3", grid.getHeaderCell(1, 3).getAttribute("colspan")); + } + + @Test + public void testFooterColSpans() { + openTestURL(); + + GridElement grid = $(GridElement.class).first(); + assertEquals("5", grid.getFooterCell(1, 1).getAttribute("colspan")); + assertEquals("2", grid.getFooterCell(0, 1).getAttribute("colspan")); + assertEquals("3", grid.getFooterCell(0, 3).getAttribute("colspan")); + } + + @Test + public void testActiveHeaderColumnsWithNavigation() throws IOException { + openTestURL(); + + GridElement grid = $(GridElement.class).first(); + grid.getCell(0, 1).click(); + + compareScreen("beforeNavigation"); + + for (int i = 1; i <= 6; ++i) { + assertEquals(true, grid.getFooterCell(1, 1).isActiveHeader()); + assertEquals(i < 3, grid.getFooterCell(0, 1).isActiveHeader()); + assertEquals(i >= 3, grid.getFooterCell(0, 3).isActiveHeader()); + assertEquals(true, grid.getHeaderCell(0, 1).isActiveHeader()); + assertEquals(i < 3, grid.getHeaderCell(1, 1).isActiveHeader()); + assertEquals(i >= 3, grid.getHeaderCell(1, 3).isActiveHeader()); + new Actions(getDriver()).sendKeys(Keys.ARROW_RIGHT).perform(); + } + + compareScreen("afterNavigation"); + } +} diff --git a/uitest/src/com/vaadin/tests/components/grid/GridElement.java b/uitest/src/com/vaadin/tests/components/grid/GridElement.java index 5027c603d9..3b28c4eaa2 100644 --- a/uitest/src/com/vaadin/tests/components/grid/GridElement.java +++ b/uitest/src/com/vaadin/tests/components/grid/GridElement.java @@ -38,10 +38,15 @@ public class GridElement extends AbstractComponentElement { public static class GridCellElement extends AbstractElement { private String ACTIVE_CLASS_NAME = "-cell-active"; + private String ACTIVE_HEADER_CLASS_NAME = "-header-active"; public boolean isActive() { return getAttribute("class").contains(ACTIVE_CLASS_NAME); } + + public boolean isActiveHeader() { + return getAttribute("class").contains(ACTIVE_HEADER_CLASS_NAME); + } } public static class GridRowElement extends AbstractElement { |