summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatti Hosio <mhosio@vaadin.com>2014-12-02 15:55:41 +0200
committerMatti Hosio <mhosio@vaadin.com>2014-12-02 15:55:41 +0200
commitd3af8be5a52365a51a0dd177617976ad566ead0e (patch)
tree25e200070b1ddf894816ac4edce5e9223dfee702
parent070b7863cdbf121c74f7486e3e4e89cf57f0c427 (diff)
downloadvaadin-framework-d3af8be5a52365a51a0dd177617976ad566ead0e.tar.gz
vaadin-framework-d3af8be5a52365a51a0dd177617976ad566ead0e.zip
Declarative support for AbstractOrderedLayout
-rw-r--r--server/src/com/vaadin/ui/AbstractOrderedLayout.java115
-rw-r--r--server/src/com/vaadin/ui/declarative/DesignAttributeHandler.java26
-rw-r--r--server/src/com/vaadin/ui/declarative/DesignContext.java368
-rw-r--r--server/tests/src/com/vaadin/tests/server/component/abstractorderedlayout/TestSynchronizeFromDesign.java109
-rw-r--r--server/tests/src/com/vaadin/tests/server/component/abstractorderedlayout/TestSynchronizeToDesign.java139
5 files changed, 748 insertions, 9 deletions
diff --git a/server/src/com/vaadin/ui/AbstractOrderedLayout.java b/server/src/com/vaadin/ui/AbstractOrderedLayout.java
index 638f6bc3f9..e45a1362b0 100644
--- a/server/src/com/vaadin/ui/AbstractOrderedLayout.java
+++ b/server/src/com/vaadin/ui/AbstractOrderedLayout.java
@@ -18,6 +18,11 @@ package com.vaadin.ui;
import java.util.Iterator;
import java.util.LinkedList;
+import java.util.logging.Logger;
+
+import org.jsoup.nodes.Attributes;
+import org.jsoup.nodes.Element;
+import org.jsoup.nodes.Node;
import com.vaadin.event.LayoutEvents.LayoutClickEvent;
import com.vaadin.event.LayoutEvents.LayoutClickListener;
@@ -26,10 +31,13 @@ import com.vaadin.server.Sizeable;
import com.vaadin.shared.Connector;
import com.vaadin.shared.EventId;
import com.vaadin.shared.MouseEventDetails;
+import com.vaadin.shared.ui.AlignmentInfo;
import com.vaadin.shared.ui.MarginInfo;
import com.vaadin.shared.ui.orderedlayout.AbstractOrderedLayoutServerRpc;
import com.vaadin.shared.ui.orderedlayout.AbstractOrderedLayoutState;
import com.vaadin.shared.ui.orderedlayout.AbstractOrderedLayoutState.ChildComponentData;
+import com.vaadin.ui.declarative.DesignAttributeHandler;
+import com.vaadin.ui.declarative.DesignContext;
@SuppressWarnings("serial")
public abstract class AbstractOrderedLayout extends AbstractLayout implements
@@ -459,4 +467,111 @@ public abstract class AbstractOrderedLayout extends AbstractLayout implements
setExpandRatio(target, expandRatio);
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.ui.AbstractComponent#synchronizeFromDesign(org.jsoup.nodes
+ * .Node, com.vaadin.ui.declarative.DesignContext)
+ */
+ @Override
+ public void synchronizeFromDesign(Node design, DesignContext designContext) {
+ // process default attributes
+ super.synchronizeFromDesign(design, designContext);
+ // remove current children
+ removeAllComponents();
+ // handle children
+ for (Node childComponent : design.childNodes()) {
+ if (childComponent instanceof Element) {
+ Attributes attr = childComponent.attributes();
+ DesignSynchronizable newChild = designContext
+ .createChild(childComponent);
+ newChild.synchronizeFromDesign(childComponent, designContext);
+ addComponent(newChild);
+ // handle alignment
+ int bitMask = 0;
+ if (attr.hasKey(":middle")) {
+ bitMask += AlignmentInfo.Bits.ALIGNMENT_VERTICAL_CENTER;
+ } else if (attr.hasKey(":bottom")) {
+ bitMask += AlignmentInfo.Bits.ALIGNMENT_BOTTOM;
+ } else {
+ bitMask += AlignmentInfo.Bits.ALIGNMENT_TOP;
+ }
+ if (attr.hasKey(":center")) {
+ bitMask += AlignmentInfo.Bits.ALIGNMENT_HORIZONTAL_CENTER;
+ } else if (attr.hasKey(":right")) {
+ bitMask += AlignmentInfo.Bits.ALIGNMENT_RIGHT;
+ } else {
+ bitMask += AlignmentInfo.Bits.ALIGNMENT_LEFT;
+ }
+ setComponentAlignment(newChild, new Alignment(bitMask));
+ // handle expand ratio
+ if (attr.hasKey(":expand")) {
+ String value = attr.get(":expand");
+ if (value.length() > 0) {
+ try {
+ float ratio = Float.valueOf(value);
+ setExpandRatio(newChild, ratio);
+ } catch (NumberFormatException nfe) {
+ getLogger().info(
+ "Failed to parse expand ratio " + value);
+ }
+ } else {
+ setExpandRatio(newChild, 1.0f);
+ }
+ }
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.ui.AbstractComponent#synchronizeToDesign(org.jsoup.nodes.Node,
+ * com.vaadin.ui.declarative.DesignContext)
+ */
+ @Override
+ public void synchronizeToDesign(Node design, DesignContext designContext) {
+ // synchronize default attributes
+ super.synchronizeToDesign(design, designContext);
+ // handle children
+ if (this instanceof HasComponents) {
+ if (!(design instanceof Element)) {
+ throw new RuntimeException(
+ "Attempted to create child elements for a node that cannot have children.");
+ }
+ Element designElement = (Element) design;
+ for (Component child : this) {
+ DesignSynchronizable childComponent = (DesignSynchronizable) child;
+ Node childNode = designContext.createNode(childComponent);
+ designElement.appendChild(childNode);
+ childComponent.synchronizeToDesign(childNode, designContext);
+ // handle alignment
+ Alignment alignment = getComponentAlignment(child);
+ if (alignment.isMiddle()) {
+ childNode.attr(":middle", "");
+ } else if (alignment.isBottom()) {
+ childNode.attr(":bottom", "");
+ }
+ if (alignment.isCenter()) {
+ childNode.attr(":center", "");
+ } else if (alignment.isRight()) {
+ childNode.attr(":right", "");
+ }
+ // handle expand ratio
+ float expandRatio = getExpandRatio(child);
+ if (expandRatio == 1.0f) {
+ childNode.attr(":expand", "");
+ } else if (expandRatio > 0) {
+ childNode.attr(":expand", DesignAttributeHandler
+ .formatDesignAttribute(expandRatio));
+ }
+ }
+ }
+ }
+
+ private static Logger getLogger() {
+ return Logger.getLogger(AbstractOrderedLayout.class.getName());
+ }
}
diff --git a/server/src/com/vaadin/ui/declarative/DesignAttributeHandler.java b/server/src/com/vaadin/ui/declarative/DesignAttributeHandler.java
index 9faf3fb0a5..a4667fed03 100644
--- a/server/src/com/vaadin/ui/declarative/DesignAttributeHandler.java
+++ b/server/src/com/vaadin/ui/declarative/DesignAttributeHandler.java
@@ -297,9 +297,9 @@ public class DesignAttributeHandler {
} else if (widthAuto) {
attributes.put("width-auto", "true");
} else {
- DecimalFormat fmt = new DecimalFormat();
- attributes.put("width", fmt.format(component.getWidth())
- + component.getWidthUnits().getSymbol());
+ attributes.put("width",
+ formatDesignAttribute(component.getWidth())
+ + component.getWidthUnits().getSymbol());
}
}
if (!areEqualHeight(component, defaultInstance)) {
@@ -309,15 +309,29 @@ public class DesignAttributeHandler {
} else if (heightAuto) {
attributes.put("height-auto", "true");
} else {
- DecimalFormat fmt = new DecimalFormat();
- attributes.put("height", fmt.format(component.getHeight())
- + component.getHeightUnits().getSymbol());
+ attributes.put("height",
+ formatDesignAttribute(component.getHeight())
+ + component.getHeightUnits().getSymbol());
}
}
}
}
/**
+ * Formats the given design attribute value. The method is provided to
+ * ensure consistent number formatting for design attribute values
+ *
+ * @since 7.4
+ * @param number
+ * the number to be formatted
+ * @return the formatted number
+ */
+ public static String formatDesignAttribute(float number) {
+ DecimalFormat fmt = new DecimalFormat();
+ return fmt.format(number);
+ }
+
+ /**
* Returns the design attribute name corresponding the given method name.
* For example given a method name <code>setPrimaryStyleName</code> the
* return value would be <code>primary-style-name</code>
diff --git a/server/src/com/vaadin/ui/declarative/DesignContext.java b/server/src/com/vaadin/ui/declarative/DesignContext.java
index 4556e3a8f5..17439d0f8a 100644
--- a/server/src/com/vaadin/ui/declarative/DesignContext.java
+++ b/server/src/com/vaadin/ui/declarative/DesignContext.java
@@ -17,13 +17,27 @@ package com.vaadin.ui.declarative;
import java.util.Collections;
import java.util.HashMap;
+import java.util.Locale;
import java.util.Map;
+import org.jsoup.nodes.Attribute;
+import org.jsoup.nodes.Attributes;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.jsoup.nodes.Node;
+import org.jsoup.nodes.TextNode;
+
+import com.vaadin.ui.Component;
+import com.vaadin.ui.DesignSynchronizable;
+
/**
- * This class contains contextual information that is collected when a component
- * tree is constructed based on HTML design template
+ * DesignContext can create a component corresponding to a given html tree node
+ * or an html tree node corresponding to a given component. DesignContext also
+ * keeps track of id values found in the current html tree and can detect
+ * non-uniqueness of these values. Non-id attributes are handled by the
+ * component classes instead of DesignContext.
*
- * @since 7.4
+ * @since
* @author Vaadin Ltd
*/
public class DesignContext {
@@ -32,6 +46,353 @@ public class DesignContext {
private static Map<Class<?>, Object> instanceCache = Collections
.synchronizedMap(new HashMap<Class<?>, Object>());
+ public static final String ID_ATTRIBUTE = "id";
+ public static final String CAPTION_ATTRIBUTE = "caption";
+ public static final String LOCAL_ID_PREFIX = "_";
+ private Map<String, DesignSynchronizable> globalIds = new HashMap<String, DesignSynchronizable>();
+ private Map<String, DesignSynchronizable> localIds = new HashMap<String, DesignSynchronizable>();
+ private Map<String, DesignSynchronizable> captions = new HashMap<String, DesignSynchronizable>();
+ private Document doc; // used for accessing
+ // Document.createElement(String)
+ // namespace mappings
+ private Map<String, String> packageToPrefix = new HashMap<String, String>();
+ private Map<String, String> prefixToPackage = new HashMap<String, String>();
+ // prefix names for which no package-mapping element will be created in the
+ // html tree
+ private Map<String, String> defaultPrefixes = new HashMap<String, String>();
+
+ public DesignContext() {
+ doc = new Document("");
+ // Initialize the mapping between prefixes and package names. First add
+ // any default mappings (v -> com.vaadin.ui). The default mappings are
+ // the prefixes for which
+ // no meta tags will be created when writing a design to html.
+ defaultPrefixes.put("v", "com.vaadin.ui");
+ for (String prefix : defaultPrefixes.keySet()) {
+ String packageName = defaultPrefixes.get(prefix);
+ prefixToPackage.put(prefix, packageName);
+ packageToPrefix.put(packageName, prefix);
+ }
+ }
+
+ /**
+ * Get the mappings from prefixes to package names from meta tags located
+ * under <head> in the html document.
+ *
+ * @since
+ */
+ public void getPrefixes(Document doc) {
+ // TODO this method has not been tested in any way.
+ Element head = doc.head();
+ if (head == null) {
+ return;
+ }
+ for (Node child : head.childNodes()) {
+ if (child instanceof Element) {
+ Element childElement = (Element) child;
+ if ("meta".equals(childElement.tagName())) {
+ Attributes attributes = childElement.attributes();
+ if (attributes.hasKey("name")
+ && attributes.hasKey("content")
+ && "package-mapping".equals(attributes.get("name"))) {
+ String contentString = attributes.get("content");
+ String[] parts = contentString.split(":");
+ if (parts.length != 2) {
+ throw new RuntimeException("The meta tag '"
+ + child.toString() + "' cannot be parsed.");
+ }
+ String prefixName = parts[0];
+ String packageName = parts[1];
+ prefixToPackage.put(prefixName, packageName);
+ packageToPrefix.put(packageName, prefixName);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates an html tree node corresponding to the given element. Note that
+ * this method does not set the attribute values. That can be done by
+ * calling childComponent.synchronizeToDesign(result, designContext), where
+ * result is the node returned by this method and designContext is this
+ * context.
+ *
+ * @since
+ * @param childComponent
+ * A component implementing the DesignSynchronizable interface.
+ * @return An html tree node corresponding to the given component, with no
+ * attributes set. The tag name of the created node is derived from
+ * the class name of childComponent.
+ */
+ public Node createNode(DesignSynchronizable childComponent) {
+ // TODO handle namespaces and id's.
+ Class<?> componentClass = childComponent.getClass();
+ String packageName = componentClass.getPackage().getName();
+ String prefix = packageToPrefix.get(packageName);
+ if (prefix == null) {
+ prefix = packageName.replace('.', '_');
+ prefixToPackage.put(prefix, packageName);
+ packageToPrefix.put(packageName, prefix);
+ }
+ prefix = prefix + "-";
+ String className = classNameToElementName(componentClass
+ .getSimpleName());
+ Element newElement = doc.createElement(prefix + className);
+ return newElement;
+ }
+
+ /**
+ * Creates the name of the html tag corresponding to the given class name.
+ * The name is derived by converting each uppercase letter to lowercase and
+ * inserting a dash before the letter. No dash is inserted before the first
+ * letter of the class name.
+ *
+ * @since
+ * @param className
+ * the name of the class without a package name
+ * @return the html tag name corresponding to className
+ */
+ private String classNameToElementName(String className) {
+ StringBuilder result = new StringBuilder();
+ for (int i = 0; i < className.length(); i++) {
+ Character c = className.charAt(i);
+ if (Character.isUpperCase(c)) {
+ if (i > 0) {
+ result.append("-");
+ }
+ result.append(Character.toLowerCase(c));
+ } else {
+ result.append(c);
+ }
+ }
+ return result.toString();
+ }
+
+ /**
+ * Creates a DesignSynchronizable object corresponding to the given html
+ * node. Note that the attributes of the node are not taken into account by
+ * this method, except IDs. To get the attributes, call
+ * result.synchronizeFromDesign(componentDesign, designContext), where
+ * result is the node returned by this method and designContext is this
+ * context.
+ *
+ * @since
+ * @param componentDesign
+ * The html tree node containing the description of the component
+ * to be created.
+ * @return a DesignSynchronizable object corresponding to componentDesign,
+ * with no attributes set.
+ */
+ public DesignSynchronizable createChild(Node componentDesign) {
+ // Create the component.
+ DesignSynchronizable component = instantiateComponent(componentDesign);
+ // Get the IDs and the caption of the component and store them in the
+ // maps of this design context.
+ org.jsoup.nodes.Attributes attributes = componentDesign.attributes();
+
+ // Global id
+ String id = attributes.get(ID_ATTRIBUTE);
+ if (id != null && id.length() > 0) {
+ Component oldComponent = globalIds.put(camelCase(id), component);
+ if (oldComponent != null) {
+ throw new RuntimeException("Duplicate ids: " + id);
+ }
+ }
+
+ // Local id
+ String localId = null;
+ for (Attribute attribute : attributes.asList()) {
+ if (attribute.getKey().startsWith(LOCAL_ID_PREFIX)) {
+ if (localId != null) {
+ throw new RuntimeException(
+ "Duplicate local ids specified: " + localId
+ + " and " + attribute.getValue());
+ }
+ localId = attribute.getKey()
+ .substring(LOCAL_ID_PREFIX.length());
+ localIds.put(camelCase(localId), component);
+ }
+ }
+
+ // Caption
+ String caption = null;
+ if (componentDesign.nodeName().equals("v-button")) {
+ String buttonCaption = textContent(componentDesign);
+ if (buttonCaption != null && !(buttonCaption.equals(""))) {
+ caption = buttonCaption;
+ }
+ }
+ if (caption == null) {
+ String componentCaption = attributes.get(CAPTION_ATTRIBUTE);
+ if (componentCaption != null && !("".equals(componentCaption))) {
+ caption = componentCaption;
+ }
+ }
+ if (caption != null) {
+ Component oldComponent = captions
+ .put(camelCase(caption), component);
+ if (oldComponent != null) {
+ throw new RuntimeException("Duplicate captions: " + caption);
+ }
+ }
+ return component;
+ }
+
+ /**
+ * Returns the text content of an html tree node. Used for getting the
+ * caption of a button.
+ *
+ * @since
+ * @param node
+ * A node of an html tree
+ * @return the text content of node, obtained by concatenating the text
+ * contents of its children
+ */
+ private String textContent(Node node) {
+ String text = "";
+ for (Node child : node.childNodes()) {
+ if (child instanceof TextNode) {
+ text += ((TextNode) child).text();
+ }
+ }
+ return text;
+ }
+
+ /**
+ * Creates a DesignSynchronizable component corresponding to the given node.
+ *
+ * @since
+ * @param node
+ * a node of an html tree
+ * @return a DesignSynchronizable object corresponding to node
+ */
+ private DesignSynchronizable instantiateComponent(Node node) {
+ // Extract the package and class names.
+ String qualifiedClassName = tagNameToClassName(node);
+ return createComponent(qualifiedClassName);
+ }
+
+ private String tagNameToClassName(Node node) {
+ String tagName = node.nodeName();
+ if (tagName.equals("v-addon")) {
+ return node.attr("class");
+ } else if (tagName.toLowerCase(Locale.ENGLISH).equals("span")
+ || tagName.toLowerCase(Locale.ENGLISH).equals("div")) {
+ return "com.vaadin.ui.Label";
+ }
+ // Otherwise, get the package name from the prefixToPackage mapping.
+ String[] parts = tagName.split("-");
+ if (parts.length < 2) {
+ throw new RuntimeException("The tagname '" + tagName
+ + "' is invalid: missing prefix.");
+ }
+ String prefixName = parts[0];
+ String packageName = prefixToPackage.get(prefixName);
+ if (packageName == null) {
+ throw new RuntimeException("Unknown tag: " + tagName);
+ }
+ // v-vertical-layout -> com.vaadin.ui.VerticalLayout
+ int firstCharacterIndex = prefixName.length() + 1; // +1 is for '-'
+ tagName = tagName.substring(firstCharacterIndex,
+ firstCharacterIndex + 1).toUpperCase(Locale.ENGLISH)
+ + tagName.substring(firstCharacterIndex + 1);
+ int i;
+ while ((i = tagName.indexOf("-")) != -1) {
+ int length = tagName.length();
+ if (i != length - 1) {
+ tagName = tagName.substring(0, i)
+ + tagName.substring(i + 1, i + 2).toUpperCase(
+ Locale.ENGLISH) + tagName.substring(i + 2);
+
+ } else {
+ // Ends with "-", WTF?
+ System.out.println("ends with '-', really?");
+ }
+ }
+ return packageName + "." + tagName;
+ }
+
+ /**
+ * Returns a new component instance of given class name. If the component
+ * cannot be instantiated a ComponentInstantiationException is thrown.
+ *
+ * @param qualifiedClassName
+ * The full class name of the object to be created.
+ * @return a new DesignSynchronizable instance.
+ * @throws ComponentInstantiationException
+ */
+ public DesignSynchronizable createComponent(String qualifiedClassName) {
+ try {
+ Class<? extends DesignSynchronizable> componentClass = resolveComponentClass(qualifiedClassName);
+ DesignSynchronizable newComponent = componentClass.newInstance();
+ return newComponent;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ @SuppressWarnings("unchecked")
+ private Class<? extends DesignSynchronizable> resolveComponentClass(
+ String qualifiedClassName) throws ClassNotFoundException {
+ Class<?> componentClass = null;
+ componentClass = Class.forName(qualifiedClassName);
+
+ // Check that we're dealing with a DesignSynchronizable component.
+ if (isDesignSynchronizable(componentClass)) {
+ return (Class<? extends DesignSynchronizable>) componentClass;
+ } else {
+ throw new IllegalArgumentException(String.format(
+ "Resolved class %s is not a %s.", componentClass.getName(),
+ Component.class.getName()));
+ }
+ }
+
+ /**
+ * Returns {@code true} if the given {@link Class} implements the
+ * {@link Component} interface of Vaadin Framework otherwise {@code false}.
+ *
+ * @param componentClass
+ * {@link Class} to check against {@link Component} interface.
+ * @return {@code true} if the given {@link Class} is a {@link Component},
+ * {@code false} otherwise.
+ */
+ public static boolean isDesignSynchronizable(Class<?> componentClass) {
+ if (componentClass != null) {
+ return DesignSynchronizable.class.isAssignableFrom(componentClass);
+ } else {
+ return false;
+ }
+ }
+
+ private String camelCase(String localId) {
+ // TODO does this method do what it should (it was taken from another
+ // project without any modifications)
+
+ // Remove all but a-Z, 0-9 (used for names) and _- and space (used
+ // for separators)
+ // localId = localId.replaceAll("[^a-zA-Z0-9_- ]", "");
+ return localId.replaceAll("[^a-zA-Z0-9]", "").toLowerCase(
+ Locale.ENGLISH);
+ // String[] parts = localId.split("[ -_]+");
+ // String thisPart = parts[0];
+ // String camelCase =
+ // thisPart.substring(0,1).toLowerCase(Locale.ENGLISH);
+ // if (parts[0].length() > 1) {
+ // camelCase += thisPart.substring(1);
+ // }
+ //
+ // for (int i=1; i < parts.length; i++) {
+ // thisPart = parts[i];
+ // camelCase += thisPart.substring(0,1).toUpperCase(Locale.ENGLISH);
+ // if (thisPart.length() > 1) {
+ // camelCase += thisPart.substring(1);
+ // }
+ // }
+ // return camelCase;
+ }
+
/**
* Returns the default instance for the given class. The instance must not
* be modified by the caller.
@@ -54,4 +415,5 @@ public class DesignContext {
}
return instance;
}
+
}
diff --git a/server/tests/src/com/vaadin/tests/server/component/abstractorderedlayout/TestSynchronizeFromDesign.java b/server/tests/src/com/vaadin/tests/server/component/abstractorderedlayout/TestSynchronizeFromDesign.java
new file mode 100644
index 0000000000..7b9a40c8df
--- /dev/null
+++ b/server/tests/src/com/vaadin/tests/server/component/abstractorderedlayout/TestSynchronizeFromDesign.java
@@ -0,0 +1,109 @@
+/*
+ * 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.abstractorderedlayout;
+
+import junit.framework.TestCase;
+
+import org.jsoup.nodes.Attributes;
+import org.jsoup.nodes.Element;
+import org.jsoup.nodes.Node;
+import org.jsoup.parser.Tag;
+
+import com.vaadin.ui.Alignment;
+import com.vaadin.ui.DesignSynchronizable;
+import com.vaadin.ui.VerticalLayout;
+import com.vaadin.ui.declarative.DesignContext;
+
+/**
+ * Test case from reading AbstractOrdered layouts from design
+ *
+ * @since
+ * @author Vaadin Ltd
+ */
+public class TestSynchronizeFromDesign extends TestCase {
+
+ public void testChildCount() {
+ VerticalLayout root = createLayout(0f);
+ assertEquals(2, root.getComponentCount());
+ }
+
+ public void testAttributes() {
+ VerticalLayout root = createLayout(0f);
+ assertEquals("test-layout", root.getCaption());
+ assertEquals("test-label", root.getComponent(0).getCaption());
+ assertEquals("test-button", root.getComponent(1).getCaption());
+ }
+
+ public void testExpandRatio() {
+ VerticalLayout root = createLayout(1f);
+ assertEquals(1f, root.getExpandRatio(root.getComponent(0)));
+ assertEquals(1f, root.getExpandRatio(root.getComponent(1)));
+
+ root = createLayout(0f);
+ assertEquals(0f, root.getExpandRatio(root.getComponent(0)));
+ assertEquals(0f, root.getExpandRatio(root.getComponent(1)));
+ }
+
+ public void testAlignment() {
+ VerticalLayout root = createLayout(0f, ":top", ":left");
+ assertEquals(Alignment.TOP_LEFT,
+ root.getComponentAlignment(root.getComponent(0)));
+ root = createLayout(0f, ":middle", ":center");
+ assertEquals(Alignment.MIDDLE_CENTER,
+ root.getComponentAlignment(root.getComponent(0)));
+ root = createLayout(0f, ":bottom", ":right");
+ assertEquals(Alignment.BOTTOM_RIGHT,
+ root.getComponentAlignment(root.getComponent(0)));
+
+ }
+
+ private VerticalLayout createLayout(float expandRatio, String... alignments) {
+ DesignContext ctx = new DesignContext();
+ Node design = createDesign(expandRatio, alignments);
+ DesignSynchronizable child = ctx.createChild(design);
+ child.synchronizeFromDesign(design, ctx);
+ return (VerticalLayout) child;
+ }
+
+ private Node createDesign(float expandRatio, String... alignments) {
+
+ Attributes rootAttributes = new Attributes();
+ rootAttributes.put("caption", "test-layout");
+ Element node = new Element(Tag.valueOf("v-vertical-layout"), "",
+ rootAttributes);
+
+ Attributes firstChildAttributes = new Attributes();
+ firstChildAttributes.put("caption", "test-label");
+ firstChildAttributes.put(":expand", String.valueOf(expandRatio));
+ for (String alignment : alignments) {
+ firstChildAttributes.put(alignment, "");
+ }
+ Element firstChild = new Element(Tag.valueOf("v-label"), "",
+ firstChildAttributes);
+ node.appendChild(firstChild);
+
+ Attributes secondChildAttributes = new Attributes();
+ secondChildAttributes.put("caption", "test-button");
+ secondChildAttributes.put(":expand", String.valueOf(expandRatio));
+ for (String alignment : alignments) {
+ secondChildAttributes.put(alignment, "");
+ }
+ Element secondChild = new Element(Tag.valueOf("v-button"), "",
+ secondChildAttributes);
+ node.appendChild(secondChild);
+ return node;
+ }
+}
diff --git a/server/tests/src/com/vaadin/tests/server/component/abstractorderedlayout/TestSynchronizeToDesign.java b/server/tests/src/com/vaadin/tests/server/component/abstractorderedlayout/TestSynchronizeToDesign.java
new file mode 100644
index 0000000000..4c0397f182
--- /dev/null
+++ b/server/tests/src/com/vaadin/tests/server/component/abstractorderedlayout/TestSynchronizeToDesign.java
@@ -0,0 +1,139 @@
+/*
+ * 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.abstractorderedlayout;
+
+import junit.framework.TestCase;
+
+import org.jsoup.nodes.Attributes;
+import org.jsoup.nodes.Element;
+import org.jsoup.nodes.Node;
+import org.jsoup.parser.Tag;
+
+import com.vaadin.ui.Alignment;
+import com.vaadin.ui.Label;
+import com.vaadin.ui.VerticalLayout;
+import com.vaadin.ui.declarative.DesignContext;
+
+/**
+ * Test case for writing abstract ordered layout to design
+ *
+ * @since
+ * @author Vaadin Ltd
+ */
+public class TestSynchronizeToDesign extends TestCase {
+
+ public void testSynchronizeEmptyLayout() {
+ VerticalLayout layout = new VerticalLayout();
+ layout.setCaption("changed-caption");
+ Node design = createDesign();
+ layout.synchronizeToDesign(design, createDesignContext());
+ assertEquals(0, design.childNodes().size());
+ assertEquals("changed-caption", design.attr("caption"));
+ }
+
+ public void testSynchronizeLayoutWithChildren() {
+ VerticalLayout layout = new VerticalLayout();
+ layout.addComponent(new Label("test-label"));
+ layout.getComponent(0).setCaption("test-caption");
+ layout.addComponent(new Label("test-label-2"));
+ Node design = createDesign();
+ layout.synchronizeToDesign(design, createDesignContext());
+ assertEquals(2, design.childNodes().size());
+ assertEquals("v-label", ((Element) design.childNode(0)).tagName());
+ assertEquals("test-caption", design.childNode(0).attr("caption"));
+ }
+
+ public void testSynchronizeUnitExpandRatio() {
+ VerticalLayout layout = new VerticalLayout();
+ layout.addComponent(new Label("test-label"));
+ layout.setExpandRatio(layout.getComponent(0), 1.0f);
+ Node design = createDesign();
+ layout.synchronizeToDesign(design, createDesignContext());
+ assertTrue(design.childNode(0).hasAttr(":expand"));
+ assertEquals("", design.childNode(0).attr(":expand"));
+ }
+
+ public void testSynchronizeArbitraryExpandRatio() {
+ VerticalLayout layout = new VerticalLayout();
+ layout.addComponent(new Label("test-label"));
+ layout.setExpandRatio(layout.getComponent(0), 2.40f);
+ Node design = createDesign();
+ layout.synchronizeToDesign(design, createDesignContext());
+ assertTrue(design.childNode(0).hasAttr(":expand"));
+ assertEquals("2.4", design.childNode(0).attr(":expand"));
+ }
+
+ public void testSynchronizeDefaultAlignment() {
+ Node design = createDesign();
+ VerticalLayout layout = createLayoutWithAlignment(design, null);
+ layout.synchronizeToDesign(design, createDesignContext());
+ assertFalse(design.childNode(0).hasAttr(":top"));
+ assertFalse(design.childNode(0).hasAttr(":left"));
+ }
+
+ public void testSynchronizeMiddleCenter() {
+ Node design = createDesign();
+ VerticalLayout layout = createLayoutWithAlignment(design,
+ Alignment.MIDDLE_CENTER);
+ layout.synchronizeToDesign(design, createDesignContext());
+ assertTrue(design.childNode(0).hasAttr(":middle"));
+ assertTrue(design.childNode(0).hasAttr(":center"));
+ }
+
+ public void testSynchronizeBottomRight() {
+ Node design = createDesign();
+ VerticalLayout layout = createLayoutWithAlignment(design,
+ Alignment.BOTTOM_RIGHT);
+ layout.synchronizeToDesign(design, createDesignContext());
+ assertTrue(design.childNode(0).hasAttr(":bottom"));
+ assertTrue(design.childNode(0).hasAttr(":right"));
+ }
+
+ private VerticalLayout createLayoutWithAlignment(Node design,
+ Alignment alignment) {
+ VerticalLayout layout = new VerticalLayout();
+ layout.addComponent(new Label("test-label"));
+ if (alignment != null) {
+ layout.setComponentAlignment(layout.getComponent(0), alignment);
+ }
+ layout.synchronizeToDesign(design, createDesignContext());
+ return layout;
+ }
+
+ private Node createDesign() {
+ // make sure that the design node has old content that should be removed
+ Attributes rootAttributes = new Attributes();
+ rootAttributes.put("caption", "test-layout");
+ Element node = new Element(Tag.valueOf("v-vertical-layout"), "",
+ rootAttributes);
+ Attributes firstChildAttributes = new Attributes();
+ firstChildAttributes.put("caption", "test-label");
+ Element firstChild = new Element(Tag.valueOf("v-label"), "",
+ firstChildAttributes);
+ node.appendChild(firstChild);
+
+ Attributes secondChildAttributes = new Attributes();
+ secondChildAttributes.put("caption", "test-button");
+ Element secondChild = new Element(Tag.valueOf("v-button"), "",
+ secondChildAttributes);
+ node.appendChild(secondChild);
+ return node;
+ }
+
+ private DesignContext createDesignContext() {
+ return new DesignContext();
+ }
+}