Also fix some small issues in Table declarative: * Write null property values as empty strings instead of NPEing * Read/write item ids from/to <tr item-id="..."> Change-Id: Ieccc3f49c5021f8a4a50d4ea671f9086ad8f997ctags/7.5.0.beta1
@@ -2231,7 +2231,7 @@ public abstract class AbstractSelect extends AbstractField<Object> implements | |||
* if the tag name of the {@code child} element is not | |||
* {@code option}. | |||
*/ | |||
protected String readItem(Element child, Set<String> selected, | |||
protected Object readItem(Element child, Set<String> selected, | |||
DesignContext context) { | |||
if (!"option".equals(child.tagName())) { | |||
throw new DesignException("Unrecognized child element in " |
@@ -257,9 +257,9 @@ public class OptionGroup extends AbstractSelect implements | |||
} | |||
@Override | |||
protected String readItem(Element child, Set<String> selected, | |||
protected Object readItem(Element child, Set<String> selected, | |||
DesignContext context) { | |||
String itemId = super.readItem(child, selected, context); | |||
Object itemId = super.readItem(child, selected, context); | |||
if (child.hasAttr("disabled")) { | |||
setItemEnabled(itemId, false); |
@@ -6042,7 +6042,7 @@ public class Table extends AbstractSelect implements Action.Container, | |||
readColumns(design); | |||
readHeader(design); | |||
readBody(design); | |||
readBody(design, context); | |||
readFooter(design); | |||
} | |||
@@ -6150,30 +6150,43 @@ public class Table extends AbstractSelect implements Action.Container, | |||
} | |||
} | |||
private void readBody(Element design) { | |||
protected void readBody(Element design, DesignContext context) { | |||
Element tbody = design.select("> table > tbody").first(); | |||
if (tbody != null) { | |||
for (Element row : tbody.children()) { | |||
Elements cells = row.children(); | |||
if (visibleColumns.size() != cells.size()) { | |||
throw new DesignException( | |||
"Wrong number of columns in a row of a Table. Expected " | |||
+ visibleColumns.size() + ", was " | |||
+ cells.size() + "."); | |||
} | |||
Object[] data = new String[cells.size()]; | |||
for (int c = 0; c < cells.size(); ++c) { | |||
data[c] = cells.get(c).html(); | |||
} | |||
Object itemId = addItem(data, null); | |||
if (itemId == null) { | |||
throw new DesignException( | |||
"A row of a Table could not be read"); | |||
} | |||
} | |||
if (tbody == null) { | |||
return; | |||
} | |||
Set<String> selected = new HashSet<String>(); | |||
for (Element tr : tbody.children()) { | |||
readItem(tr, selected, context); | |||
} | |||
} | |||
@Override | |||
protected Object readItem(Element tr, Set<String> selected, | |||
DesignContext context) { | |||
Elements cells = tr.children(); | |||
if (visibleColumns.size() != cells.size()) { | |||
throw new DesignException( | |||
"Wrong number of columns in a Table row. Expected " | |||
+ visibleColumns.size() + ", was " + cells.size() | |||
+ "."); | |||
} | |||
Object[] data = new String[cells.size()]; | |||
for (int c = 0; c < cells.size(); ++c) { | |||
data[c] = cells.get(c).html(); | |||
} | |||
Object itemId = addItem(data, | |||
tr.hasAttr("item-id") ? tr.attr("item-id") : null); | |||
if (itemId == null) { | |||
throw new DesignException("Failed to add a Table row: " + data); | |||
} | |||
return itemId; | |||
} | |||
@Override | |||
public void writeDesign(Element design, DesignContext context) { | |||
Table def = context.getDefaultInstance(this); | |||
@@ -6260,6 +6273,9 @@ public class Table extends AbstractSelect implements Action.Container, | |||
@Override | |||
protected void writeItems(Element design, DesignContext context) { | |||
if (getVisibleColumns().length == 0) { | |||
return; | |||
} | |||
Element tbody = design.child(0).appendElement("tbody"); | |||
super.writeItems(tbody, context); | |||
} | |||
@@ -6268,10 +6284,12 @@ public class Table extends AbstractSelect implements Action.Container, | |||
protected Element writeItem(Element tbody, Object itemId, | |||
DesignContext context) { | |||
Element tr = tbody.appendElement("tr"); | |||
tr.attr("item-id", String.valueOf(itemId)); | |||
Item item = getItem(itemId); | |||
for (Object id : getVisibleColumns()) { | |||
Element td = tr.appendElement("td"); | |||
td.html(item.getItemProperty(id).getValue().toString()); | |||
Object value = item.getItemProperty(id).getValue(); | |||
td.html(value != null ? value.toString() : ""); | |||
} | |||
return tr; | |||
} |
@@ -23,9 +23,13 @@ import java.util.Collections; | |||
import java.util.HashSet; | |||
import java.util.List; | |||
import java.util.Map; | |||
import java.util.Set; | |||
import java.util.Stack; | |||
import java.util.logging.Level; | |||
import java.util.logging.Logger; | |||
import org.jsoup.nodes.Element; | |||
import com.vaadin.data.Collapsible; | |||
import com.vaadin.data.Container; | |||
import com.vaadin.data.Container.Hierarchical; | |||
@@ -41,6 +45,9 @@ import com.vaadin.ui.Tree.CollapseEvent; | |||
import com.vaadin.ui.Tree.CollapseListener; | |||
import com.vaadin.ui.Tree.ExpandEvent; | |||
import com.vaadin.ui.Tree.ExpandListener; | |||
import com.vaadin.ui.declarative.DesignAttributeHandler; | |||
import com.vaadin.ui.declarative.DesignContext; | |||
import com.vaadin.ui.declarative.DesignException; | |||
/** | |||
* TreeTable extends the {@link Table} component so that it can also visualize a | |||
@@ -888,4 +895,81 @@ public class TreeTable extends Table implements Hierarchical { | |||
} | |||
return itemIds; | |||
} | |||
@Override | |||
protected void readBody(Element design, DesignContext context) { | |||
Element tbody = design.select("> table > tbody").first(); | |||
if (tbody == null) { | |||
return; | |||
} | |||
Set<String> selected = new HashSet<String>(); | |||
Stack<Object> parents = new Stack<Object>(); | |||
int lastDepth = -1; | |||
for (Element tr : tbody.children()) { | |||
int depth = DesignAttributeHandler.readAttribute("depth", | |||
tr.attributes(), 0, int.class); | |||
if (depth < 0 || depth > lastDepth + 1) { | |||
throw new DesignException( | |||
"Malformed TreeTable item hierarchy at " + tr | |||
+ ": last depth was " + lastDepth); | |||
} else if (depth <= lastDepth) { | |||
for (int d = depth; d <= lastDepth; d++) { | |||
parents.pop(); | |||
} | |||
} | |||
Object itemId = readItem(tr, selected, context); | |||
setParent(itemId, !parents.isEmpty() ? parents.peek() : null); | |||
parents.push(itemId); | |||
lastDepth = depth; | |||
} | |||
} | |||
@Override | |||
protected Object readItem(Element tr, Set<String> selected, | |||
DesignContext context) { | |||
Object itemId = super.readItem(tr, selected, context); | |||
if (tr.hasAttr("collapsed")) { | |||
boolean collapsed = DesignAttributeHandler.readAttribute( | |||
"collapsed", tr.attributes(), boolean.class); | |||
setCollapsed(itemId, collapsed); | |||
} | |||
return itemId; | |||
} | |||
@Override | |||
protected void writeItems(Element design, DesignContext context) { | |||
if (getVisibleColumns().length == 0) { | |||
return; | |||
} | |||
Element tbody = design.child(0).appendElement("tbody"); | |||
writeItems(tbody, rootItemIds(), 0, context); | |||
} | |||
protected void writeItems(Element tbody, Collection<?> itemIds, int depth, | |||
DesignContext context) { | |||
for (Object itemId : itemIds) { | |||
Element tr = writeItem(tbody, itemId, context); | |||
DesignAttributeHandler.writeAttribute("depth", tr.attributes(), | |||
depth, 0, int.class); | |||
if (getChildren(itemId) != null) { | |||
writeItems(tbody, getChildren(itemId), depth + 1, context); | |||
} | |||
} | |||
} | |||
@Override | |||
protected Element writeItem(Element tbody, Object itemId, | |||
DesignContext context) { | |||
Element tr = super.writeItem(tbody, itemId, context); | |||
DesignAttributeHandler.writeAttribute("collapsed", tr.attributes(), | |||
isCollapsed(itemId), true, boolean.class); | |||
return tr; | |||
} | |||
} |
@@ -36,12 +36,14 @@ public class TableDeclarativeTest extends TableDeclarativeTestBase { | |||
@Test | |||
public void testBasicAttributes() { | |||
String design = "<v-table page-length=30 cache-rate=3 selectable=true editable=true " | |||
String design = "<" | |||
+ getTag() | |||
+ " page-length=30 cache-rate=3 selectable=true editable=true " | |||
+ "sortable=false sort-ascending=false sort-container-property-id=foo " | |||
+ "drag-mode=row multi-select-mode=simple column-header-mode=id row-header-mode=id " | |||
+ "column-reordering-allowed=true column-collapsing-allowed=true />"; | |||
Table table = new Table(); | |||
Table table = getTable(); | |||
table.setPageLength(30); | |||
table.setCacheRate(3); | |||
table.setSelectable(true); | |||
@@ -65,17 +67,18 @@ public class TableDeclarativeTest extends TableDeclarativeTestBase { | |||
@Test | |||
public void testColumns() { | |||
String design = "<v-table column-collapsing-allowed=true>" // | |||
String design = "<" | |||
+ getTag() | |||
+ " column-collapsing-allowed=true>" // | |||
+ " <table>" // | |||
+ " <colgroup>" | |||
+ " <col property-id='foo' width=300>" | |||
+ " <col property-id='bar' center expand=1 collapsible=false>" | |||
+ " <col property-id='baz' right expand=2 collapsed=true>" | |||
+ " </colgroup>" // | |||
+ " </table>" // | |||
+ "</v-table>"; | |||
+ " </table>"; | |||
Table table = new Table(); | |||
Table table = getTable(); | |||
table.setColumnCollapsingAllowed(true); | |||
table.addContainerProperty("foo", String.class, null); | |||
@@ -98,7 +101,8 @@ public class TableDeclarativeTest extends TableDeclarativeTestBase { | |||
@Test | |||
public void testHeadersFooters() { | |||
String design = "<v-table>" // | |||
String design = "<" + getTag() | |||
+ ">" // | |||
+ " <table>" // | |||
+ " <colgroup><col property-id=foo><col property-id=bar></colgroup>" // | |||
+ " <thead>" // | |||
@@ -107,10 +111,9 @@ public class TableDeclarativeTest extends TableDeclarativeTestBase { | |||
+ " <tfoot>" // | |||
+ " <tr><td>foo<td>bar" // | |||
+ " </tfoot>" // | |||
+ " </table>" // | |||
+ "</v-table>"; | |||
+ " </table>"; | |||
Table table = new Table(); | |||
Table table = getTable(); | |||
table.setFooterVisible(true); | |||
table.addContainerProperty("foo", String.class, null); | |||
@@ -129,27 +132,26 @@ public class TableDeclarativeTest extends TableDeclarativeTestBase { | |||
@Test | |||
public void testInlineData() { | |||
String design = "<v-table> "// | |||
String design = "<" | |||
+ getTag() | |||
+ ">" // | |||
+ " <table>" // | |||
+ " <colgroup>" | |||
+ " <col property-id='foo' />" | |||
+ " <col property-id='bar' />" | |||
+ " <col property-id='baz' />" // | |||
+ " </colgroup>" | |||
+ " <thead>" | |||
+ " </colgroup>" + " <thead>" | |||
+ " <tr><th>Description<th>Milestone<th>Status</tr>" | |||
+ " </thead>" | |||
+ " <tbody>" | |||
+ " <tr><td>r1c1</td><td>r1c2</td><td>r1c3</td>" // | |||
+ " <tr><td>r2c1</td><td>r2c2</td><td>r2c3</td>" // | |||
+ " </thead>" + " <tbody>" | |||
+ " <tr item-id=1><td>r1c1</td><td>r1c2</td><td>r1c3</td>" // | |||
+ " <tr item-id=2><td>r2c1</td><td>r2c2</td><td>r2c3</td>" // | |||
+ " </tbody>" // | |||
+ " <tfoot>" // | |||
+ " <tr><td>F1<td>F2<td>F3</tr>" // | |||
+ " </tfoot>" // | |||
+ " </table>" // | |||
+ "</v-table>"; | |||
+ " </table>"; | |||
Table table = new Table(); | |||
Table table = getTable(); | |||
table.addContainerProperty("foo", String.class, null); | |||
table.addContainerProperty("bar", String.class, null); | |||
table.addContainerProperty("baz", String.class, null); | |||
@@ -157,8 +159,8 @@ public class TableDeclarativeTest extends TableDeclarativeTestBase { | |||
table.setColumnFooter("foo", "F1"); | |||
table.setColumnFooter("bar", "F2"); | |||
table.setColumnFooter("baz", "F3"); | |||
table.addItem(new Object[] { "r1c1", "r1c2", "r1c3" }, null); | |||
table.addItem(new Object[] { "r2c1", "r2c2", "r2c3" }, null); | |||
table.addItem(new Object[] { "r1c1", "r1c2", "r1c3" }, "1"); | |||
table.addItem(new Object[] { "r2c1", "r2c2", "r2c3" }, "2"); | |||
table.setFooterVisible(true); | |||
testRead(design, table); |
@@ -20,7 +20,8 @@ import static org.junit.Assert.assertTrue; | |||
import com.vaadin.tests.design.DeclarativeTestBase; | |||
import com.vaadin.ui.Table; | |||
public class TableDeclarativeTestBase extends DeclarativeTestBase<Table> { | |||
public abstract class TableDeclarativeTestBase extends | |||
DeclarativeTestBase<Table> { | |||
@Override | |||
public Table testRead(String design, Table expected) { | |||
@@ -30,20 +31,30 @@ public class TableDeclarativeTestBase extends DeclarativeTestBase<Table> { | |||
return read; | |||
} | |||
private void compareBody(Table read, Table expected) { | |||
assertEquals(expected.getItemIds().size(), read.getItemIds().size()); | |||
protected Table getTable() { | |||
return new Table(); | |||
} | |||
protected String getTag() { | |||
return "v-table"; | |||
} | |||
protected void compareBody(Table read, Table expected) { | |||
assertEquals("number of items", expected.getItemIds().size(), read | |||
.getItemIds().size()); | |||
for (Object rowId : expected.getItemIds()) { | |||
assertTrue(read.containsId(rowId)); | |||
for (Object propertyId : read.getVisibleColumns()) { | |||
Object expectedItem = expected.getContainerProperty(rowId, | |||
propertyId); | |||
Object readItem = read.getContainerProperty(rowId, propertyId); | |||
assertEquals(expectedItem, readItem); | |||
assertEquals("property '" + propertyId + "'", expectedItem, | |||
readItem); | |||
} | |||
} | |||
} | |||
private void compareColumns(Table read, Table expected) { | |||
protected void compareColumns(Table read, Table expected) { | |||
for (Object pid : expected.getVisibleColumns()) { | |||
String col = "column '" + pid + "'"; | |||
assertEquals(col + " width", expected.getColumnWidth(pid), |
@@ -0,0 +1,156 @@ | |||
/* | |||
* 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.treetable; | |||
import org.junit.Assert; | |||
import org.junit.Test; | |||
import com.vaadin.tests.server.component.table.TableDeclarativeTest; | |||
import com.vaadin.ui.Table; | |||
import com.vaadin.ui.TreeTable; | |||
import com.vaadin.ui.declarative.DesignException; | |||
/** | |||
* Test declarative support for {@link TreeTable}. | |||
* | |||
* @since | |||
* @author Vaadin Ltd | |||
*/ | |||
public class TreeTableDeclarativeTest extends TableDeclarativeTest { | |||
@Test | |||
public void testAttributes() { | |||
String design = "<v-tree-table animations-enabled=true>"; | |||
TreeTable table = getTable(); | |||
table.setAnimationsEnabled(true); | |||
testRead(design, table); | |||
testWrite(design, table); | |||
} | |||
@Test | |||
public void testHierarchy() { | |||
String design = "<v-tree-table>" // | |||
+ "<table>" // | |||
+ "<colgroup><col property-id=''></colgroup>" // | |||
+ "<tbody>" // | |||
+ " <tr item-id='1'><td></tr>" // | |||
+ " <tr depth=1 item-id='1.1'><td></tr>" // | |||
+ " <tr depth=1 item-id='1.2'><td></tr>" // | |||
+ " <tr depth=2 item-id='1.2.1'><td></tr>" // | |||
+ " <tr depth=3 item-id='1.2.1.1'><td></tr>" // | |||
+ " <tr depth=2 item-id='1.2.2'><td></tr>" // | |||
+ " <tr item-id='2'><td></tr>" // | |||
+ " <tr depth=1 item-id='2.1'><td></tr>" // | |||
+ "</tbody>" // | |||
+ "</table>" // | |||
+ "</v-tree-table>"; | |||
TreeTable table = getTable(); | |||
table.addContainerProperty("", String.class, ""); | |||
table.addItem("1"); | |||
table.addItem("1.1"); | |||
table.setParent("1.1", "1"); | |||
table.addItem("1.2"); | |||
table.setParent("1.2", "1"); | |||
table.addItem("1.2.1"); | |||
table.setParent("1.2.1", "1.2"); | |||
table.addItem("1.2.1.1"); | |||
table.setParent("1.2.1.1", "1.2.1"); | |||
table.addItem("1.2.2"); | |||
table.setParent("1.2.2", "1.2"); | |||
table.addItem("2"); | |||
table.addItem("2.1"); | |||
table.setParent("2.1", "2"); | |||
testRead(design, table); | |||
testWrite(design, table, true); | |||
} | |||
@Test | |||
public void testCollapsed() { | |||
String design = "<v-tree-table>" // | |||
+ " <table>" // | |||
+ " <colgroup><col property-id=''></colgroup>" // | |||
+ " <tbody>" // | |||
+ " <tr item-id='1' collapsed=false><td></tr>" // | |||
+ " <tr depth=1 item-id='1.1'><td></tr>" // | |||
+ " <tr depth=2 item-id='1.1.1'><td></tr>" // | |||
+ " </tbody>" // | |||
+ " </table>" // | |||
+ "</v-tree-table>"; | |||
TreeTable table = getTable(); | |||
table.addContainerProperty("", String.class, ""); | |||
table.addItem("1"); | |||
table.setCollapsed("1", false); | |||
table.addItem("1.1"); | |||
table.setParent("1.1", "1"); | |||
table.addItem("1.1.1"); | |||
table.setParent("1.1.1", "1.1"); | |||
testRead(design, table); | |||
testWrite(design, table, true); | |||
} | |||
@Test | |||
public void testMalformedHierarchy() { | |||
assertMalformed("<tr depth=-4><td>"); | |||
assertMalformed("<tr depth=1><td>"); | |||
assertMalformed("<tr><td><tr depth=3><td>"); | |||
} | |||
protected void assertMalformed(String hierarchy) { | |||
String design = "<v-tree-table>" // | |||
+ " <table>" // | |||
+ " <colgroup><col property-id=''></colgroup>" // | |||
+ " <tbody>" + hierarchy + "</tbody>" // | |||
+ " </table>" // | |||
+ "</v-tree-table>"; | |||
try { | |||
read(design); | |||
Assert.fail("Malformed hierarchy should fail: " + hierarchy); | |||
} catch (DesignException expected) { | |||
} | |||
} | |||
@Override | |||
protected void compareBody(Table read, Table expected) { | |||
super.compareBody(read, expected); | |||
for (Object itemId : read.getItemIds()) { | |||
Assert.assertEquals("parent of item " + itemId, | |||
((TreeTable) expected).getParent(itemId), | |||
((TreeTable) read).getParent(itemId)); | |||
Assert.assertEquals("collapsed status of item " + itemId, | |||
((TreeTable) expected).isCollapsed(itemId), | |||
((TreeTable) read).isCollapsed(itemId)); | |||
} | |||
} | |||
@Override | |||
protected TreeTable getTable() { | |||
return new TreeTable(); | |||
} | |||
@Override | |||
protected String getTag() { | |||
return "v-tree-table"; | |||
} | |||
} |