summaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorTeemu Suo-Anttila <teemusa@vaadin.com>2015-04-08 13:41:37 +0300
committerVaadin Code Review <review@vaadin.com>2015-04-10 12:40:00 +0000
commit3cb1cfec55f568838798a5afb5bf27b13ba82b42 (patch)
treea7792ed6ad3ecd81784d1e2b2227895ea15c177f /server
parent38e35c5db6621b20929d924707be149e22032997 (diff)
downloadvaadin-framework-3cb1cfec55f568838798a5afb5bf27b13ba82b42.tar.gz
vaadin-framework-3cb1cfec55f568838798a5afb5bf27b13ba82b42.zip
Fix GridLayout declarative support (#16594)
Change-Id: I25e52a9246c9ccbc406d1f162b4e9ff69bb411ff
Diffstat (limited to 'server')
-rw-r--r--server/src/com/vaadin/ui/GridLayout.java325
-rw-r--r--server/tests/src/com/vaadin/tests/design/AbstractComponentSetResponsiveTest.java17
-rw-r--r--server/tests/src/com/vaadin/tests/server/component/gridlayout/GridLayoutDeclarativeTest.java200
3 files changed, 533 insertions, 9 deletions
diff --git a/server/src/com/vaadin/ui/GridLayout.java b/server/src/com/vaadin/ui/GridLayout.java
index 96854c5b1b..35110b39ab 100644
--- a/server/src/com/vaadin/ui/GridLayout.java
+++ b/server/src/com/vaadin/ui/GridLayout.java
@@ -17,12 +17,21 @@
package com.vaadin.ui;
import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
+import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.Set;
+
+import org.jsoup.nodes.Attributes;
+import org.jsoup.nodes.Element;
+import org.jsoup.select.Elements;
import com.vaadin.event.LayoutEvents.LayoutClickEvent;
import com.vaadin.event.LayoutEvents.LayoutClickListener;
@@ -36,6 +45,8 @@ import com.vaadin.shared.ui.MarginInfo;
import com.vaadin.shared.ui.gridlayout.GridLayoutServerRpc;
import com.vaadin.shared.ui.gridlayout.GridLayoutState;
import com.vaadin.shared.ui.gridlayout.GridLayoutState.ChildComponentData;
+import com.vaadin.ui.declarative.DesignAttributeHandler;
+import com.vaadin.ui.declarative.DesignContext;
/**
* A layout where the components are laid out on a grid using cell coordinates.
@@ -1282,4 +1293,316 @@ public class GridLayout extends AbstractLayout implements
public boolean isHideEmptyRowsAndColumns() {
return getState(false).hideEmptyRowsAndColumns;
}
-}
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * After reading the design, cursorY is set to point to a row outside of the
+ * GridLayout area. CursorX is reset to 0.
+ */
+ @Override
+ public void readDesign(Element design, DesignContext designContext) {
+ super.readDesign(design, designContext);
+
+ // Prepare a 2D map for reading column contents
+ Elements rowElements = design.getElementsByTag("row");
+ List<Map<Integer, Component>> rows = new ArrayList<Map<Integer, Component>>();
+ for (int i = 0; i < rowElements.size(); ++i) {
+ rows.add(new HashMap<Integer, Component>());
+ }
+ setRows(Math.max(rows.size(), 1));
+
+ List<Integer> columnExpandRatios = new ArrayList<Integer>();
+ for (int row = 0; row < rowElements.size(); ++row) {
+ Element rowElement = rowElements.get(row);
+
+ // Row Expand
+ if (rowElement.hasAttr("expand")) {
+ int expand = DesignAttributeHandler.readAttribute("expand",
+ rowElement.attributes(), int.class);
+ setRowExpandRatio(row, expand);
+ }
+
+ Elements cols = rowElement.children();
+
+ // Amount of skipped columns due to spanned components
+ int skippedColumns = 0;
+
+ for (int column = 0; column < cols.size(); ++column) {
+ while (rows.get(row).containsKey(column + skippedColumns)) {
+ // Skip any spanned components
+ skippedColumns++;
+ }
+
+ Element col = cols.get(column);
+ Component child = null;
+
+ if (col.children().size() > 0) {
+ child = designContext.readDesign(col.child(0));
+ // TODO: Currently ignoring any extra children.
+ // Needs Error handling?
+ } // Else: Empty placeholder. No child component.
+
+ // Handle rowspan and colspan for this child component
+ Attributes attr = col.attributes();
+ int colspan = DesignAttributeHandler.readAttribute("colspan",
+ attr, 1, int.class);
+ int rowspan = DesignAttributeHandler.readAttribute("rowspan",
+ attr, 1, int.class);
+
+ for (int rowIndex = row; rowIndex < row + rowspan; ++rowIndex) {
+ for (int colIndex = column; colIndex < column + colspan; ++colIndex) {
+ if (rowIndex == rows.size()) {
+ // Rowspan with not enough rows. Fix by adding rows.
+ rows.add(new HashMap<Integer, Component>());
+ }
+ rows.get(rowIndex)
+ .put(colIndex + skippedColumns, child);
+ }
+ }
+
+ // Read column expand ratios if handling the first row.
+ if (row == 0) {
+ if (col.hasAttr("expand")) {
+ for (String expand : col.attr("expand").split(",")) {
+ columnExpandRatios.add(Integer.parseInt(expand));
+ }
+ } else {
+ for (int c = 0; c < colspan; ++c) {
+ columnExpandRatios.add(0);
+ }
+ }
+ }
+
+ skippedColumns += (colspan - 1);
+ }
+ }
+
+ // Calculate highest column count and set columns
+ int colMax = 0;
+ for (Map<Integer, Component> cols : rows) {
+ if (colMax < cols.size()) {
+ colMax = cols.size();
+ }
+ }
+ setColumns(Math.max(colMax, 1));
+
+ for (int i = 0; i < columnExpandRatios.size(); ++i) {
+ setColumnExpandRatio(i, columnExpandRatios.get(i));
+ }
+
+ // Reiterate through the 2D map and add components to GridLayout
+ Set<Component> visited = new HashSet<Component>();
+
+ // Ignore any missing components
+ visited.add(null);
+
+ for (int i = 0; i < rows.size(); ++i) {
+ Map<Integer, Component> row = rows.get(i);
+ for (int j = 0; j < colMax; ++j) {
+ Component child = row.get(j);
+ if (visited.contains(child)) {
+ // Empty location or already handled child
+ continue;
+ }
+ visited.add(child);
+
+ // Figure out col and rowspan from 2D map
+ int colspan = 0;
+ while (j + colspan + 1 < row.size()
+ && row.get(j + colspan + 1) == child) {
+ ++colspan;
+ }
+
+ int rowspan = 0;
+ while (i + rowspan + 1 < rows.size()
+ && rows.get(i + rowspan + 1).get(j) == child) {
+ ++rowspan;
+ }
+
+ // Add component with area
+ addComponent(child, j, i, j + colspan, i + rowspan);
+ }
+ }
+ // Set cursor position explicitly
+ setCursorY(getRows());
+ setCursorX(0);
+ }
+
+ @Override
+ public void writeDesign(Element design, DesignContext designContext) {
+ super.writeDesign(design, designContext);
+
+ GridLayout def = designContext.getDefaultInstance(this);
+ if (components.isEmpty()
+ || !designContext.shouldWriteChildren(this, def)) {
+ return;
+ }
+
+ final Map<Connector, ChildComponentData> childData = getState().childData;
+
+ // Make a 2D map of component areas.
+ Component[][] componentMap = new Component[getState().rows][getState().columns];
+ final Component dummyComponent = new Label("");
+
+ for (Component component : components) {
+ ChildComponentData coords = childData.get(component);
+ for (int row = coords.row1; row <= coords.row2; ++row) {
+ for (int col = coords.column1; col <= coords.column2; ++col) {
+ componentMap[row][col] = component;
+ }
+ }
+ }
+
+ // Go through the map and write only needed column tags
+ Set<Connector> visited = new HashSet<Connector>();
+
+ // Skip the dummy placeholder
+ visited.add(dummyComponent);
+
+ for (int i = 0; i < componentMap.length; ++i) {
+ Element row = design.appendElement("row");
+
+ // Row Expand
+ DesignAttributeHandler.writeAttribute("expand", row.attributes(),
+ (int) getRowExpandRatio(i), 0, int.class);
+
+ int colspan = 1;
+ Element col;
+ for (int j = 0; j < componentMap[i].length; ++j) {
+ Component child = componentMap[i][j];
+ if (child != null) {
+ if (visited.contains(child)) {
+ // Child has already been written in the design
+ continue;
+ }
+ visited.add(child);
+
+ Element childElement = designContext.createElement(child);
+ col = row.appendElement("column");
+
+ // Write child data into design
+ ChildComponentData coords = childData.get(child);
+
+ Alignment alignment = getComponentAlignment(child);
+ if (alignment.isMiddle()) {
+ childElement.attr(":middle", "");
+ } else if (alignment.isBottom()) {
+ childElement.attr(":bottom", "");
+ }
+ if (alignment.isCenter()) {
+ childElement.attr(":center", "");
+ } else if (alignment.isRight()) {
+ childElement.attr(":right", "");
+ }
+
+ col.appendChild(childElement);
+ if (coords.row1 != coords.row2) {
+ col.attr("rowspan", ""
+ + (1 + coords.row2 - coords.row1));
+ }
+
+ colspan = 1 + coords.column2 - coords.column1;
+ if (colspan > 1) {
+ col.attr("colspan", "" + colspan);
+ }
+
+ } else {
+ boolean hasExpands = false;
+ if (i == 0
+ && lastComponentOnRow(componentMap[i], j, visited)) {
+ // A column with expand and no content in the end of
+ // first row needs to be present.
+ for (int c = j; c < componentMap[i].length; ++c) {
+ if ((int) getColumnExpandRatio(c) > 0) {
+ hasExpands = true;
+ }
+ }
+ }
+
+ if (lastComponentOnRow(componentMap[i], j, visited)
+ && !hasExpands) {
+ continue;
+ }
+
+ // Empty placeholder tag.
+ col = row.appendElement("column");
+
+ // Use colspan to make placeholders more pleasant
+ while (j + colspan < componentMap[i].length
+ && componentMap[i][j + colspan] == child) {
+ ++colspan;
+ }
+
+ int rowspan = getRowSpan(componentMap, i, j, colspan, child);
+ if (colspan > 1) {
+ col.attr("colspan", "" + colspan);
+ }
+ if (rowspan > 1) {
+ col.attr("rowspan", "" + rowspan);
+ }
+ for (int x = 0; x < rowspan; ++x) {
+ for (int y = 0; y < colspan; ++y) {
+ // Mark handled columns
+ componentMap[i + x][j + y] = dummyComponent;
+ }
+ }
+ }
+
+ // Column expands
+ if (i == 0) {
+ // Only do expands on first row
+ String expands = "";
+ boolean expandRatios = false;
+ for (int c = 0; c < colspan; ++c) {
+ int colExpand = (int) getColumnExpandRatio(j + c);
+ if (colExpand > 0) {
+ expandRatios = true;
+ }
+ expands += (c > 0 ? "," : "") + colExpand;
+ }
+ if (expandRatios) {
+ col.attr("expand", expands);
+ }
+ }
+
+ j += colspan - 1;
+ }
+ }
+ }
+
+ private int getRowSpan(Component[][] compMap, int i, int j, int colspan,
+ Component child) {
+ int rowspan = 1;
+ while (i + rowspan < compMap.length && compMap[i + rowspan][j] == child) {
+ for (int k = 0; k < colspan; ++k) {
+ if (compMap[i + rowspan][j + k] != child) {
+ return rowspan;
+ }
+ }
+ rowspan++;
+ }
+ return rowspan;
+ }
+
+ private boolean lastComponentOnRow(Component[] componentArray, int j,
+ Set<Connector> visited) {
+ while ((++j) < componentArray.length) {
+ Component child = componentArray[j];
+ if (child != null && !visited.contains(child)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ protected Collection<String> getCustomAttributes() {
+ Collection<String> result = super.getCustomAttributes();
+ result.add("cursor-x");
+ result.add("cursor-y");
+ result.add("rows");
+ result.add("columns");
+ return result;
+ }
+} \ No newline at end of file
diff --git a/server/tests/src/com/vaadin/tests/design/AbstractComponentSetResponsiveTest.java b/server/tests/src/com/vaadin/tests/design/AbstractComponentSetResponsiveTest.java
index f7dbd0c97e..83b3e577dc 100644
--- a/server/tests/src/com/vaadin/tests/design/AbstractComponentSetResponsiveTest.java
+++ b/server/tests/src/com/vaadin/tests/design/AbstractComponentSetResponsiveTest.java
@@ -17,21 +17,22 @@ package com.vaadin.tests.design;
import org.junit.Test;
-import com.vaadin.tests.design.DeclarativeTestBase;
-import com.vaadin.ui.GridLayout;
+import com.vaadin.shared.ui.label.ContentMode;
+import com.vaadin.ui.Label;
public class AbstractComponentSetResponsiveTest extends
- DeclarativeTestBase<GridLayout> {
+ DeclarativeTestBase<Label> {
@Test
public void testResponsiveFlag() {
- GridLayout gl = new GridLayout();
- gl.setResponsive(true);
+ Label label = new Label();
+ label.setContentMode(ContentMode.HTML);
+ label.setResponsive(true);
- String design = "<v-grid-layout responsive='true' />";
+ String design = "<v-label responsive='true' />";
- testWrite(design, gl);
- testRead(design, gl);
+ testWrite(design, label);
+ testRead(design, label);
}
}
diff --git a/server/tests/src/com/vaadin/tests/server/component/gridlayout/GridLayoutDeclarativeTest.java b/server/tests/src/com/vaadin/tests/server/component/gridlayout/GridLayoutDeclarativeTest.java
new file mode 100644
index 0000000000..7c9c126707
--- /dev/null
+++ b/server/tests/src/com/vaadin/tests/server/component/gridlayout/GridLayoutDeclarativeTest.java
@@ -0,0 +1,200 @@
+/*
+ * 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.gridlayout;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.vaadin.tests.design.DeclarativeTestBase;
+import com.vaadin.ui.Button;
+import com.vaadin.ui.Component;
+import com.vaadin.ui.GridLayout;
+import com.vaadin.ui.declarative.DesignContext;
+
+public class GridLayoutDeclarativeTest extends DeclarativeTestBase<GridLayout> {
+
+ @Test
+ public void testSimpleGridLayout() {
+ Button b1 = new Button("Button 0,0");
+ Button b2 = new Button("Button 0,1");
+ Button b3 = new Button("Button 1,0");
+ Button b4 = new Button("Button 1,1");
+ b1.setCaptionAsHtml(true);
+ b2.setCaptionAsHtml(true);
+ b3.setCaptionAsHtml(true);
+ b4.setCaptionAsHtml(true);
+ String design = "<v-grid-layout><row>" //
+ + "<column expand=1>" + writeChild(b1) + "</column>" //
+ + "<column expand=3>" + writeChild(b2) + "</column>" //
+ + "</row><row>" //
+ + "<column>" + writeChild(b3) + "</column>" //
+ + "<column>" + writeChild(b4) + "</column>" //
+ + "</row></v-grid-layout>";
+ GridLayout gl = new GridLayout(2, 2);
+ gl.addComponent(b1);
+ gl.addComponent(b2);
+ gl.addComponent(b3);
+ gl.addComponent(b4);
+ gl.setColumnExpandRatio(0, 1.0f);
+ gl.setColumnExpandRatio(1, 3.0f);
+ testWrite(design, gl);
+ testRead(design, gl);
+ }
+
+ @Test
+ public void testOneBigComponentGridLayout() {
+ Button b1 = new Button("Button 0,0 -> 1,1");
+ b1.setCaptionAsHtml(true);
+ String design = "<v-grid-layout><row>" //
+ + "<column colspan=2 rowspan=2>" + writeChild(b1) + "</column>" //
+ + "</row><row expand=2>" //
+ + "</row></v-grid-layout>";
+ GridLayout gl = new GridLayout(2, 2);
+ gl.addComponent(b1, 0, 0, 1, 1);
+ gl.setRowExpandRatio(1, 2);
+ testWrite(design, gl);
+ testRead(design, gl);
+ }
+
+ @Test
+ public void testMultipleSpannedComponentsGridLayout() {
+ GridLayout gl = new GridLayout(5, 5);
+ Button b1 = new Button("Button 0,0 -> 0,2");
+ b1.setCaptionAsHtml(true);
+ gl.addComponent(b1, 0, 0, 2, 0);
+
+ Button b2 = new Button("Button 0,3 -> 3,3");
+ b2.setCaptionAsHtml(true);
+ gl.addComponent(b2, 3, 0, 3, 3);
+
+ Button b3 = new Button("Button 0,4 -> 1,4");
+ b3.setCaptionAsHtml(true);
+ gl.addComponent(b3, 4, 0, 4, 1);
+
+ Button b4 = new Button("Button 1,0 -> 3,1");
+ b4.setCaptionAsHtml(true);
+ gl.addComponent(b4, 0, 1, 1, 3);
+
+ Button b5 = new Button("Button 2,2");
+ b5.setCaptionAsHtml(true);
+ gl.addComponent(b5, 2, 2);
+
+ Button b6 = new Button("Button 3,4 -> 4,4");
+ b6.setCaptionAsHtml(true);
+ gl.addComponent(b6, 4, 3, 4, 4);
+
+ Button b7 = new Button("Button 4,1 -> 4,2");
+ b7.setCaptionAsHtml(true);
+ gl.addComponent(b7, 2, 4, 3, 4);
+
+ /*
+ * Buttons in the GridLayout
+ */
+
+ // 1 1 1 2 3
+ // 4 4 - 2 3
+ // 4 4 5 2 -
+ // 4 4 - 2 6
+ // - - 7 7 6
+
+ String design = "<v-grid-layout><row>" //
+ + "<column colspan=3>" + writeChild(b1) + "</column>" //
+ + "<column rowspan=4>" + writeChild(b2) + "</column>" //
+ + "<column rowspan=2>" + writeChild(b3) + "</column>" //
+ + "</row><row>" //
+ + "<column rowspan=3 colspan=2>" + writeChild(b4) + "</column>" //
+ + "</row><row>" //
+ + "<column>" + writeChild(b5) + "</column>" //
+ + "</row><row>" //
+ + "<column />" // Empty placeholder
+ + "<column rowspan=2>" + writeChild(b6) + "</column>" //
+ + "</row><row>" //
+ + "<column colspan=2 />" // Empty placeholder
+ + "<column colspan=2>" + writeChild(b7) + "</column>" //
+ + "</row></v-grid-layout>";
+ testWrite(design, gl);
+ testRead(design, gl);
+ }
+
+ @Test
+ public void testManyExtraGridLayoutSlots() {
+ GridLayout gl = new GridLayout(5, 5);
+ Button b1 = new Button("Button 0,4 -> 4,4");
+ b1.setCaptionAsHtml(true);
+ gl.addComponent(b1, 4, 0, 4, 4);
+ gl.setColumnExpandRatio(2, 2.0f);
+
+ String design = "<v-grid-layout><row>" //
+ + "<column colspan=4 rowspan=5 expand='0,0,2,0' />" //
+ + "<column rowspan=5>" + writeChild(b1) + "</column>" //
+ + "</row><row>" //
+ + "</row><row>" //
+ + "</row><row>" //
+ + "</row><row>" //
+ + "</row></v-grid-layout>";
+ testWrite(design, gl);
+ testRead(design, gl);
+ }
+
+ @Test
+ public void testManyEmptyColumnsWithOneExpand() {
+ GridLayout gl = new GridLayout(5, 5);
+ Button b1 = new Button("Button 0,4 -> 4,4");
+ b1.setCaptionAsHtml(true);
+ gl.addComponent(b1, 0, 0, 0, 4);
+ gl.setColumnExpandRatio(4, 2.0f);
+
+ String design = "<v-grid-layout><row>" //
+ + "<column rowspan=5>" + writeChild(b1) + "</column>" //
+ + "<column colspan=4 rowspan=5 expand='0,0,0,2' />" //
+ + "</row><row>" //
+ + "</row><row>" //
+ + "</row><row>" //
+ + "</row><row>" //
+ + "</row></v-grid-layout>";
+ testWrite(design, gl);
+ testRead(design, gl);
+ }
+
+ @Test
+ public void testEmptyGridLayout() {
+ GridLayout gl = new GridLayout();
+ String design = "<v-grid-layout />";
+ testWrite(design, gl);
+ testRead(design, gl);
+ }
+
+ private String writeChild(Component childComponent) {
+ return new DesignContext().createElement(childComponent).toString();
+ }
+
+ @Override
+ public GridLayout testRead(String design, GridLayout expected) {
+ expected.setCursorX(0);
+ expected.setCursorY(expected.getRows());
+
+ GridLayout result = super.testRead(design, expected);
+ for (int row = 0; row < expected.getRows(); ++row) {
+ Assert.assertTrue(Math.abs(expected.getRowExpandRatio(row)
+ - result.getRowExpandRatio(row)) < 0.00001);
+ }
+ for (int col = 0; col < expected.getColumns(); ++col) {
+ Assert.assertTrue(Math.abs(expected.getColumnExpandRatio(col)
+ - result.getColumnExpandRatio(col)) < 0.00001);
+ }
+ return result;
+ }
+}