Browse Source

Fix Grid Header/Footer declarative support (#16596)

Change-Id: Iedd02738840b4d1a82681cf090c744f07166fdd4
tags/7.5.0.beta1
Teemu Suo-Anttila 9 years ago
parent
commit
cda732dd1f

+ 250
- 1
server/src/com/vaadin/ui/Grid.java View File

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

/**
@@ -1576,6 +1657,15 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
row.section.markAsDirty();
}

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

+ 4
- 0
server/tests/src/com/vaadin/tests/server/component/grid/declarative/GridColumnDeclarativeTest.java View File

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

+ 81
- 4
server/tests/src/com/vaadin/tests/server/component/grid/declarative/GridDeclarativeTestBase.java View File

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

+ 267
- 0
server/tests/src/com/vaadin/tests/server/component/grid/declarative/GridHeaderFooterDeclarativeTest.java View File

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

Loading…
Cancel
Save