diff options
4 files changed, 602 insertions, 5 deletions
diff --git a/server/src/com/vaadin/ui/Grid.java b/server/src/com/vaadin/ui/Grid.java index d3578d8f98..3b602abf1a 100644 --- a/server/src/com/vaadin/ui/Grid.java +++ b/server/src/com/vaadin/ui/Grid.java @@ -31,6 +31,7 @@ import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; @@ -1286,7 +1287,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier, * @param <ROWTYPE> * the type of the rows in the section */ - protected static abstract class StaticSection<ROWTYPE extends StaticSection.StaticRow<?>> + abstract static class StaticSection<ROWTYPE extends StaticSection.StaticRow<?>> implements Serializable { /** @@ -1458,6 +1459,86 @@ public class Grid extends AbstractComponent implements SelectionNotifier, getRowState().styleName = styleName; } + /** + * Writes the declarative design to the given table row element. + * + * @since + * @param trElement + * Element to write design to + * @param designContext + * the design context + */ + protected void writeDesign(Element trElement, + DesignContext designContext) { + Set<CELLTYPE> visited = new HashSet<CELLTYPE>(); + for (Grid.Column column : section.grid.getColumns()) { + CELLTYPE cell = getCell(column.getPropertyId()); + if (visited.contains(cell)) { + continue; + } + visited.add(cell); + + Element cellElement = trElement + .appendElement(getCellTagName()); + cell.writeDesign(cellElement, designContext); + + for (Entry<Set<CELLTYPE>, CELLTYPE> entry : cellGroups + .entrySet()) { + if (entry.getValue() == cell) { + cellElement.attr("colspan", "" + + entry.getKey().size()); + break; + } + } + } + } + + /** + * Reads the declarative design from the given table row element. + * + * @since + * @param trElement + * Element to read design from + * @param designContext + * the design context + * @throws DesignException + * if the given table row contains unexpected children + */ + protected void readDesign(Element trElement, + DesignContext designContext) throws DesignException { + Elements cellElements = trElement.children(); + int totalColSpans = 0; + for (int i = 0; i < cellElements.size(); ++i) { + Element element = cellElements.get(i); + if (!element.tagName().equals(getCellTagName())) { + throw new DesignException( + "Unexpected element in tr while expecting " + + getCellTagName() + ": " + + element.tagName()); + } + + int columnIndex = i + totalColSpans; + + int colspan = DesignAttributeHandler.readAttribute( + "colspan", element.attributes(), 1, int.class); + + Set<CELLTYPE> cells = new HashSet<CELLTYPE>(); + for (int c = 0; c < colspan; ++c) { + cells.add(getCell(section.grid.getColumns() + .get(columnIndex + c).getPropertyId())); + } + + if (colspan > 1) { + totalColSpans += colspan - 1; + join(cells).readDesign(element, designContext); + } else { + cells.iterator().next() + .readDesign(element, designContext); + } + } + } + + abstract protected String getCellTagName(); } /** @@ -1577,6 +1658,15 @@ public class Grid extends AbstractComponent implements SelectionNotifier, } /** + * Returns the type of content stored in this cell. + * + * @return cell content type + */ + public GridStaticCellType getCellType() { + return cellState.type; + } + + /** * Returns the custom style name for this cell. * * @return the style name or null if no style name has been set @@ -1604,6 +1694,57 @@ public class Grid extends AbstractComponent implements SelectionNotifier, cellState.connector = null; } } + + /** + * Writes the declarative design to the given table cell element. + * + * @since + * @param cellElement + * Element to write design to + * @param designContext + * the design context + */ + protected void writeDesign(Element cellElement, + DesignContext designContext) { + switch (cellState.type) { + case TEXT: + DesignAttributeHandler.writeAttribute("plain-text", + cellElement.attributes(), "", null, String.class); + cellElement.appendText(getText()); + break; + case HTML: + cellElement.append(getHtml()); + break; + case WIDGET: + cellElement.appendChild(designContext + .createElement(getComponent())); + break; + } + } + + /** + * Reads the declarative design from the given table cell element. + * + * @since + * @param cellElement + * Element to read design from + * @param designContext + * the design context + */ + protected void readDesign(Element cellElement, + DesignContext designContext) { + if (!cellElement.hasAttr("plain-text")) { + if (cellElement.children().size() > 0 + && cellElement.child(0).tagName().contains("-")) { + setComponent(designContext.readDesign(cellElement + .child(0))); + } else { + setHtml(cellElement.html()); + } + } else { + setText(cellElement.html()); + } + } } protected Grid grid; @@ -1833,6 +1974,50 @@ public class Grid extends AbstractComponent implements SelectionNotifier, } return false; } + + /** + * Writes the declarative design to the given table section element. + * + * @since + * @param tableSectionElement + * Element to write design to + * @param designContext + * the design context + */ + protected void writeDesign(Element tableSectionElement, + DesignContext designContext) { + for (ROWTYPE row : rows) { + row.writeDesign(tableSectionElement.appendElement("tr"), + designContext); + } + } + + /** + * Writes the declarative design from the given table section element. + * + * @since + * @param tableSectionElement + * Element to read design from + * @param designContext + * the design context + * @throws DesignException + * if the table section contains unexpected children + */ + protected void readDesign(Element tableSectionElement, + DesignContext designContext) throws DesignException { + while (rows.size() > 0) { + removeRow(0); + } + + for (Element row : tableSectionElement.children()) { + if (!row.tagName().equals("tr")) { + throw new DesignException("Unexpected element in " + + tableSectionElement.tagName() + ": " + + row.tagName()); + } + appendRow().readDesign(row, designContext); + } + } } /** @@ -1930,6 +2115,16 @@ public class Grid extends AbstractComponent implements SelectionNotifier, } } } + + @Override + protected void readDesign(Element tableSectionElement, + DesignContext designContext) { + super.readDesign(tableSectionElement, designContext); + + if (defaultRow == null && !rows.isEmpty()) { + grid.setDefaultHeaderRow(rows.get(0)); + } + } } /** @@ -1945,10 +2140,41 @@ public class Grid extends AbstractComponent implements SelectionNotifier, getRowState().defaultRow = value; } + private boolean isDefaultRow() { + return getRowState().defaultRow; + } + @Override protected HeaderCell createCell() { return new HeaderCell(this); } + + @Override + protected String getCellTagName() { + return "th"; + } + + @Override + protected void writeDesign(Element trElement, + DesignContext designContext) { + super.writeDesign(trElement, designContext); + + if (section.grid.getDefaultHeaderRow() == this) { + DesignAttributeHandler.writeAttribute("default", + trElement.attributes(), true, null, boolean.class); + } + } + + @Override + protected void readDesign(Element trElement, DesignContext designContext) { + super.readDesign(trElement, designContext); + + boolean defaultRow = DesignAttributeHandler.readAttribute( + "default", trElement.attributes(), false, boolean.class); + if (defaultRow) { + section.grid.setDefaultHeaderRow(this); + } + } } /** @@ -2005,6 +2231,11 @@ public class Grid extends AbstractComponent implements SelectionNotifier, return new FooterCell(this); } + @Override + protected String getCellTagName() { + return "td"; + } + } /** @@ -5232,6 +5463,16 @@ public class Grid extends AbstractComponent implements SelectionNotifier, addColumn(propertyId, String.class).readDesign(col, context); ++i; } + + for (Element child : table.children()) { + if (child.tagName().equals("thead")) { + header.readDesign(child, context); + } else if (child.tagName().equals("tbody")) { + // TODO: Inline data + } else if (child.tagName().equals("tfoot")) { + footer.readDesign(child, context); + } + } } } @@ -5285,6 +5526,14 @@ public class Grid extends AbstractComponent implements SelectionNotifier, column.writeDesign(colElement, context); } + // Always write thead. Reads correctly when there no header rows + header.writeDesign(tableElement.appendElement("thead"), context); + + // TODO: Body + + if (footer.getRowCount() > 0) { + footer.writeDesign(tableElement.appendElement("tfoot"), context); + } } @Override diff --git a/server/tests/src/com/vaadin/tests/server/component/grid/declarative/GridColumnDeclarativeTest.java b/server/tests/src/com/vaadin/tests/server/component/grid/declarative/GridColumnDeclarativeTest.java index 735a1ab502..1c22a69571 100644 --- a/server/tests/src/com/vaadin/tests/server/component/grid/declarative/GridColumnDeclarativeTest.java +++ b/server/tests/src/com/vaadin/tests/server/component/grid/declarative/GridColumnDeclarativeTest.java @@ -29,6 +29,7 @@ public class GridColumnDeclarativeTest extends GridDeclarativeTestBase { + " <col sortable=false max-width='200' expand='2' property-id='Column2'>" + " <col sortable=true editable=false min-width='15' expand='1' property-id='Column3'>" + "</colgroup>" // + + "<thead />" // + "</table></v-grid>"; Grid grid = new Grid(); grid.addColumn("Column1", String.class).setWidth(100); @@ -37,6 +38,9 @@ public class GridColumnDeclarativeTest extends GridDeclarativeTestBase { grid.addColumn("Column3", String.class).setMinimumWidth(15) .setExpandRatio(1).setEditable(false); + // Remove the default header + grid.removeHeaderRow(grid.getDefaultHeaderRow()); + // Use the read grid component to do another pass on write. testRead(design, grid, true); testWrite(design, grid); diff --git a/server/tests/src/com/vaadin/tests/server/component/grid/declarative/GridDeclarativeTestBase.java b/server/tests/src/com/vaadin/tests/server/component/grid/declarative/GridDeclarativeTestBase.java index 56f4647e90..0a32ec1734 100644 --- a/server/tests/src/com/vaadin/tests/server/component/grid/declarative/GridDeclarativeTestBase.java +++ b/server/tests/src/com/vaadin/tests/server/component/grid/declarative/GridDeclarativeTestBase.java @@ -22,6 +22,10 @@ import org.junit.Assert; import com.vaadin.tests.design.DeclarativeTestBase; import com.vaadin.ui.Grid; import com.vaadin.ui.Grid.Column; +import com.vaadin.ui.Grid.FooterCell; +import com.vaadin.ui.Grid.FooterRow; +import com.vaadin.ui.Grid.HeaderCell; +import com.vaadin.ui.Grid.HeaderRow; public class GridDeclarativeTestBase extends DeclarativeTestBase<Grid> { @@ -31,15 +35,88 @@ public class GridDeclarativeTestBase extends DeclarativeTestBase<Grid> { } public Grid testRead(String design, Grid expected, boolean retestWrite) { - Grid readGrid = super.testRead(design, expected); + Grid actual = super.testRead(design, expected); - compareGridColumns(expected, readGrid); + compareGridColumns(expected, actual); + compareHeaders(expected, actual); + compareFooters(expected, actual); if (retestWrite) { - testWrite(design, readGrid); + testWrite(design, actual); } - return readGrid; + return actual; + } + + private void compareHeaders(Grid expected, Grid actual) { + Assert.assertEquals("Different header row count", + expected.getHeaderRowCount(), actual.getHeaderRowCount()); + for (int i = 0; i < expected.getHeaderRowCount(); ++i) { + HeaderRow expectedRow = expected.getHeaderRow(i); + HeaderRow actualRow = actual.getHeaderRow(i); + + if (expectedRow.equals(expected.getDefaultHeaderRow())) { + Assert.assertEquals("Different index for default header row", + actual.getDefaultHeaderRow(), actualRow); + } + + for (Column c : expected.getColumns()) { + String baseError = "Difference when comparing cell for " + + c.toString() + " on header row " + i + ": "; + Object propertyId = c.getPropertyId(); + HeaderCell expectedCell = expectedRow.getCell(propertyId); + HeaderCell actualCell = actualRow.getCell(propertyId); + + switch (expectedCell.getCellType()) { + case TEXT: + Assert.assertEquals(baseError + "Text content", + expectedCell.getText(), actualCell.getText()); + break; + case HTML: + Assert.assertEquals(baseError + "HTML content", + expectedCell.getHtml(), actualCell.getHtml()); + break; + case WIDGET: + assertEquals(baseError + "Component content", + expectedCell.getComponent(), + actualCell.getComponent()); + break; + } + } + } + } + + private void compareFooters(Grid expected, Grid actual) { + Assert.assertEquals("Different footer row count", + expected.getFooterRowCount(), actual.getFooterRowCount()); + for (int i = 0; i < expected.getFooterRowCount(); ++i) { + FooterRow expectedRow = expected.getFooterRow(i); + FooterRow actualRow = actual.getFooterRow(i); + + for (Column c : expected.getColumns()) { + String baseError = "Difference when comparing cell for " + + c.toString() + " on footer row " + i + ": "; + Object propertyId = c.getPropertyId(); + FooterCell expectedCell = expectedRow.getCell(propertyId); + FooterCell actualCell = actualRow.getCell(propertyId); + + switch (expectedCell.getCellType()) { + case TEXT: + Assert.assertEquals(baseError + "Text content", + expectedCell.getText(), actualCell.getText()); + break; + case HTML: + Assert.assertEquals(baseError + "HTML content", + expectedCell.getHtml(), actualCell.getHtml()); + break; + case WIDGET: + assertEquals(baseError + "Component content", + expectedCell.getComponent(), + actualCell.getComponent()); + break; + } + } + } } private void compareGridColumns(Grid expected, Grid actual) { diff --git a/server/tests/src/com/vaadin/tests/server/component/grid/declarative/GridHeaderFooterDeclarativeTest.java b/server/tests/src/com/vaadin/tests/server/component/grid/declarative/GridHeaderFooterDeclarativeTest.java new file mode 100644 index 0000000000..b4e82950cb --- /dev/null +++ b/server/tests/src/com/vaadin/tests/server/component/grid/declarative/GridHeaderFooterDeclarativeTest.java @@ -0,0 +1,267 @@ +/* + * 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.declarative; + +import org.junit.Test; + +import com.vaadin.shared.ui.label.ContentMode; +import com.vaadin.ui.Grid; +import com.vaadin.ui.Grid.Column; +import com.vaadin.ui.Grid.FooterRow; +import com.vaadin.ui.Grid.HeaderRow; +import com.vaadin.ui.Label; + +public class GridHeaderFooterDeclarativeTest extends GridDeclarativeTestBase { + + @Test + public void testSingleDefaultHeader() { + String design = "<v-grid><table>"// + + "<colgroup>" + + " <col sortable=true property-id='Column1'>" + + " <col sortable=true property-id='Column2'>" + + " <col sortable=true property-id='Column3'>" + + "</colgroup>" // + + "<thead>" // + + " <tr default='true'><th plain-text=''>Column1<th plain-text=''>Column2<th plain-text=''>Column3</tr>" // + + "</thead>" // + + "</table></v-grid>"; + Grid grid = new Grid(); + grid.addColumn("Column1", String.class); + grid.addColumn("Column2", String.class); + grid.addColumn("Column3", String.class); + + testWrite(design, grid); + testRead(design, grid, true); + } + + @Test + public void testSingleDefaultHTMLHeader() { + String design = "<v-grid><table>"// + + "<colgroup>" + + " <col sortable=true property-id='Column1'>" + + " <col sortable=true property-id='Column2'>" + + " <col sortable=true property-id='Column3'>" + + "</colgroup>" // + + "<thead>" // + + " <tr default='true'><th>Column1<th>Column2<th>Column3</tr>" // + + "</thead>" // + + "</table></v-grid>"; + Grid grid = new Grid(); + grid.addColumn("Column1", String.class); + grid.addColumn("Column2", String.class); + grid.addColumn("Column3", String.class); + + HeaderRow row = grid.getDefaultHeaderRow(); + for (Column c : grid.getColumns()) { + row.getCell(c.getPropertyId()).setHtml(c.getHeaderCaption()); + } + + testWrite(design, grid); + testRead(design, grid, true); + } + + @Test + public void testNoHeaderRows() { + String design = "<v-grid><table>"// + + "<colgroup>" + + " <col sortable=true property-id='Column1'>" + + "</colgroup>" // + + "<thead />" // + + "</table></v-grid>"; + + Grid grid = new Grid(); + grid.addColumn("Column1", String.class); + grid.removeHeaderRow(grid.getDefaultHeaderRow()); + + testWrite(design, grid); + testRead(design, grid, true); + } + + @Test + public void testMultipleHeadersWithColSpans() { + String design = "<v-grid><table>"// + + "<colgroup>" + + " <col sortable=true property-id='Column1'>" + + " <col sortable=true property-id='Column2'>" + + " <col sortable=true property-id='Column3'>" + + "</colgroup>" // + + "<thead>" // + + " <tr><th colspan=3>Baz</tr>" + + " <tr default='true'><th>Column1<th>Column2<th>Column3</tr>" // + + " <tr><th>Foo<th colspan=2>Bar</tr>" // + + "</thead>" // + + "</table></v-grid>"; + Grid grid = new Grid(); + grid.addColumn("Column1", String.class); + grid.addColumn("Column2", String.class); + grid.addColumn("Column3", String.class); + + HeaderRow row = grid.getDefaultHeaderRow(); + for (Column c : grid.getColumns()) { + row.getCell(c.getPropertyId()).setHtml(c.getHeaderCaption()); + } + + grid.prependHeaderRow().join("Column1", "Column2", "Column3") + .setHtml("Baz"); + row = grid.appendHeaderRow(); + row.getCell("Column1").setHtml("Foo"); + row.join("Column2", "Column3").setHtml("Bar"); + + testWrite(design, grid); + testRead(design, grid, true); + } + + @Test + public void testSingleDefaultFooter() { + String design = "<v-grid><table>"// + + "<colgroup>" + + " <col sortable=true property-id='Column1'>" + + " <col sortable=true property-id='Column2'>" + + " <col sortable=true property-id='Column3'>" + + "</colgroup>" // + + "<thead />" // No headers read or written + + "<tfoot>" // + + " <tr><td plain-text=''>Column1<td plain-text=''>Column2<td plain-text=''>Column3</tr>" // + + "</tfoot>" // + + "</table></v-grid>"; + Grid grid = new Grid(); + grid.addColumn("Column1", String.class); + grid.addColumn("Column2", String.class); + grid.addColumn("Column3", String.class); + + FooterRow row = grid.appendFooterRow(); + for (Column c : grid.getColumns()) { + row.getCell(c.getPropertyId()).setText(c.getHeaderCaption()); + } + + grid.removeHeaderRow(grid.getDefaultHeaderRow()); + + testWrite(design, grid); + testRead(design, grid, true); + } + + @Test + public void testSingleDefaultHTMLFooter() { + String design = "<v-grid><table>"// + + "<colgroup>" + + " <col sortable=true property-id='Column1'>" + + " <col sortable=true property-id='Column2'>" + + " <col sortable=true property-id='Column3'>" + + "</colgroup>" // + + "<thead />" // No headers read or written + + "<tfoot>" // + + " <tr><td>Column1<td>Column2<td>Column3</tr>" // + + "</tfoot>" // + + "</table></v-grid>"; + Grid grid = new Grid(); + grid.addColumn("Column1", String.class); + grid.addColumn("Column2", String.class); + grid.addColumn("Column3", String.class); + + FooterRow row = grid.appendFooterRow(); + for (Column c : grid.getColumns()) { + row.getCell(c.getPropertyId()).setHtml(c.getHeaderCaption()); + } + + grid.removeHeaderRow(grid.getDefaultHeaderRow()); + + testWrite(design, grid); + testRead(design, grid, true); + } + + @Test + public void testMultipleFootersWithColSpans() { + String design = "<v-grid><table>"// + + "<colgroup>" + + " <col sortable=true property-id='Column1'>" + + " <col sortable=true property-id='Column2'>" + + " <col sortable=true property-id='Column3'>" + + "</colgroup>" // + + "<thead />" // No headers read or written. + + "<tfoot>" // + + " <tr><td colspan=3>Baz</tr>" + + " <tr><td>Column1<td>Column2<td>Column3</tr>" // + + " <tr><td>Foo<td colspan=2>Bar</tr>" // + + "</tfoot>" // + + "</table></v-grid>"; + Grid grid = new Grid(); + grid.addColumn("Column1", String.class); + grid.addColumn("Column2", String.class); + grid.addColumn("Column3", String.class); + + FooterRow row = grid.appendFooterRow(); + for (Column c : grid.getColumns()) { + row.getCell(c.getPropertyId()).setHtml(c.getHeaderCaption()); + } + + grid.prependFooterRow().join("Column1", "Column2", "Column3") + .setHtml("Baz"); + row = grid.appendFooterRow(); + row.getCell("Column1").setHtml("Foo"); + row.join("Column2", "Column3").setHtml("Bar"); + + grid.removeHeaderRow(grid.getDefaultHeaderRow()); + + testWrite(design, grid); + testRead(design, grid, true); + } + + @Test + public void testComponentInGridHeader() { + String design = "<v-grid><table>"// + + "<colgroup>" + + " <col sortable=true property-id='Column1'>" + + "</colgroup>" // + + "<thead>" // + + "<tr default=true><th><v-label><b>Foo</b></v-label></tr>" + + "</thead>"// + + "</table></v-grid>"; + + Label component = new Label("<b>Foo</b>"); + component.setContentMode(ContentMode.HTML); + + Grid grid = new Grid(); + grid.addColumn("Column1", String.class); + grid.getDefaultHeaderRow().getCell("Column1").setComponent(component); + + testRead(design, grid, true); + testWrite(design, grid); + } + + @Test + public void testComponentInGridFooter() { + String design = "<v-grid><table>"// + + "<colgroup>" + + " <col sortable=true property-id='Column1'>" + + "</colgroup>" // + + "<thead />" // No headers read or written + + "<tfoot>" // + + "<tr><td><v-label><b>Foo</b></v-label></tr>"// + + "</tfoot>" // + + "</table></v-grid>"; + + Label component = new Label("<b>Foo</b>"); + component.setContentMode(ContentMode.HTML); + + Grid grid = new Grid(); + grid.addColumn("Column1", String.class); + grid.prependFooterRow().getCell("Column1").setComponent(component); + grid.removeHeaderRow(grid.getDefaultHeaderRow()); + + testRead(design, grid, true); + testWrite(design, grid); + } +} |