@@ -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()); | |||
} | |||
} |
@@ -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,14 +309,28 @@ 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 |
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |