diff options
author | Matti Hosio <mhosio@vaadin.com> | 2014-12-02 15:55:41 +0200 |
---|---|---|
committer | Matti Hosio <mhosio@vaadin.com> | 2014-12-02 15:55:41 +0200 |
commit | d3af8be5a52365a51a0dd177617976ad566ead0e (patch) | |
tree | 25e200070b1ddf894816ac4edce5e9223dfee702 | |
parent | 070b7863cdbf121c74f7486e3e4e89cf57f0c427 (diff) | |
download | vaadin-framework-d3af8be5a52365a51a0dd177617976ad566ead0e.tar.gz vaadin-framework-d3af8be5a52365a51a0dd177617976ad566ead0e.zip |
Declarative support for AbstractOrderedLayout
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(); + } +} |