setModified(false);
// If the new value differs from the previous one
- if (!equals(newFieldValue, value)) {
+ if (!equals(newFieldValue, getInternalValue())) {
setInternalValue(newFieldValue);
fireValueChange(false);
} else if (wasModified) {
private T getFieldValue() {
// Give the value from abstract buffers if the field if possible
if (dataSource == null || !isReadThrough() || isModified()) {
- return value;
+ return getInternalValue();
}
// There is no buffered value so use whatever the data model provides
protected void setValue(T newFieldValue, boolean repaintIsNotNeeded)
throws Property.ReadOnlyException, Property.ConversionException {
- if (!equals(newFieldValue, value)) {
+ if (!equals(newFieldValue, getInternalValue())) {
// Read only fields can not be changed
if (isReadOnly()) {
public void setPropertyDataSource(Property newDataSource) {
// Saves the old value
- final Object oldValue = value;
+ final Object oldValue = getInternalValue();
// Stops listening the old data source changes
if (dataSource != null
}
// Fires value change if the value has changed
+ T value = getInternalValue();
if ((value != oldValue)
&& ((value != null && !value.equals(oldValue)) || value == null)) {
fireValueChange(false);
if (isReadThrough()) {
if (committingValueToDataSource) {
boolean propertyNotifiesOfTheBufferedValue = equals(event
- .getProperty().getValue(), value);
+ .getProperty().getValue(), getInternalValue());
if (!propertyNotifiesOfTheBufferedValue) {
/*
* Property (or chained property like PropertyFormatter) now
requestRepaint();
}
+ /**
+ *
+ * @return
+ */
+ protected T getInternalValue() {
+ return value;
+ }
+
/**
* Sets the internal field value. This is purely used by AbstractField to
* change the internal Field value. It does not trigger valuechange events.
--- /dev/null
+package com.vaadin.ui;
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.Iterator;
+
+import com.vaadin.data.Property;
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+import com.vaadin.terminal.gwt.client.ui.VCustomComponent;
+
+/**
+ * A {@link Field} whose UI content can be constructed by the user, enabling the
+ * creation of e.g. form fields by composing Vaadin components. Customization of
+ * both the visual presentation and the logic of the field is possible.
+ *
+ * Subclasses must implement {@link #getType()} and {@link #createContent()}.
+ *
+ * Most custom fields can simply compose a user interface that calls the methods
+ * {@link #setInternalValue(Object)} and {@link #getInternalValue()} when
+ * necessary.
+ *
+ * It is also possible to override {@link #commit()},
+ * {@link #setPropertyDataSource(Property)} and other logic of the field.
+ *
+ * @since 7.0
+ */
+@ClientWidget(VCustomComponent.class)
+public abstract class CustomField<T> extends AbstractField<T> implements
+ ComponentContainer {
+
+ /**
+ * The root component implementing the custom component.
+ */
+ private Component root = null;
+
+ /**
+ * Constructs a new custom field.
+ *
+ * <p>
+ * The component is implemented by wrapping the methods of the composition
+ * root component given as parameter. The composition root must be set
+ * before the component can be used.
+ * </p>
+ */
+ public CustomField() {
+ // expand horizontally by default
+ setWidth(100, UNITS_PERCENTAGE);
+ }
+
+ /**
+ * Constructs the content and notifies it that the {@link CustomField} is
+ * attached to a window.
+ *
+ * @see com.vaadin.ui.Component#attach()
+ */
+ @Override
+ public void attach() {
+ root = getContent();
+ super.attach();
+ getContent().setParent(this);
+ getContent().attach();
+
+ fireComponentAttachEvent(getContent());
+ }
+
+ /**
+ * Notifies the content that the {@link CustomField} is detached from a
+ * window.
+ *
+ * @see com.vaadin.ui.Component#detach()
+ */
+ @Override
+ public void detach() {
+ super.detach();
+ getContent().detach();
+ }
+
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+ if (getContent() == null) {
+ throw new IllegalStateException(
+ "Content component or layout of the field must be set before the "
+ + getClass().getName() + " can be painted");
+ }
+
+ getContent().paint(target);
+ }
+
+ /**
+ * Returns the content of the
+ *
+ * @return
+ */
+ protected Component getContent() {
+ if (null == root) {
+ root = createContent();
+ }
+ return root;
+ }
+
+ /**
+ * Create the content component or layout for the field. Subclasses of
+ * {@link CustomField} should implement this method.
+ *
+ * Note that this method is called when the CustomField is attached to a
+ * layout or when {@link #getContent()} is called explicitly for the first
+ * time. It is only called once for a {@link CustomField}.
+ *
+ * @return
+ */
+ protected abstract Component createContent();
+
+ private void requestContentRepaint() {
+ if (getParent() == null) {
+ // skip repaint - not yet attached
+ return;
+ }
+ if (getContent() instanceof ComponentContainer) {
+ ((ComponentContainer) getContent()).requestRepaintAll();
+ } else {
+ getContent().requestRepaint();
+ }
+ }
+
+ // Size related methods
+ // TODO might not be necessary to override but following the pattern from
+ // AbstractComponentContainer
+
+ @Override
+ public void setHeight(float height, int unit) {
+ super.setHeight(height, unit);
+ requestContentRepaint();
+ }
+
+ @Override
+ public void setWidth(float height, int unit) {
+ super.setWidth(height, unit);
+ requestContentRepaint();
+ }
+
+ // ComponentContainer methods
+
+ private class ComponentIterator implements Iterator<Component>,
+ Serializable {
+ boolean first = getContent() != null;
+
+ public boolean hasNext() {
+ return first;
+ }
+
+ public Component next() {
+ first = false;
+ return getContent();
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ public Iterator<Component> getComponentIterator() {
+ return new ComponentIterator();
+ }
+
+ public int getComponentCount() {
+ return (null != getContent()) ? 1 : 0;
+ }
+
+ public void requestRepaintAll() {
+ requestRepaint();
+
+ requestContentRepaint();
+ }
+
+ /**
+ * Fires the component attached event. This should be called by the
+ * addComponent methods after the component have been added to this
+ * container.
+ *
+ * @param component
+ * the component that has been added to this container.
+ */
+ protected void fireComponentAttachEvent(Component component) {
+ fireEvent(new ComponentAttachEvent(this, component));
+ }
+
+ // TODO remove these methods when ComponentContainer interface is cleaned up
+
+ public void addComponent(Component c) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void removeComponent(Component c) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void removeAllComponents() {
+ throw new UnsupportedOperationException();
+ }
+
+ public void replaceComponent(Component oldComponent, Component newComponent) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void moveComponentsFrom(ComponentContainer source) {
+ throw new UnsupportedOperationException();
+ }
+
+ private static final Method COMPONENT_ATTACHED_METHOD;
+
+ static {
+ try {
+ COMPONENT_ATTACHED_METHOD = ComponentAttachListener.class
+ .getDeclaredMethod("componentAttachedToContainer",
+ new Class[] { ComponentAttachEvent.class });
+ } catch (final java.lang.NoSuchMethodException e) {
+ // This should never happen
+ throw new java.lang.RuntimeException(
+ "Internal error finding methods in CustomField");
+ }
+ }
+
+ public void addListener(ComponentAttachListener listener) {
+ addListener(ComponentContainer.ComponentAttachEvent.class, listener,
+ COMPONENT_ATTACHED_METHOD);
+ }
+
+ public void removeListener(ComponentAttachListener listener) {
+ removeListener(ComponentContainer.ComponentAttachEvent.class, listener,
+ COMPONENT_ATTACHED_METHOD);
+ }
+
+ public void addListener(ComponentDetachListener listener) {
+ // content never detached
+ }
+
+ public void removeListener(ComponentDetachListener listener) {
+ // content never detached
+ }
+
+}
--- /dev/null
+package com.vaadin.tests.components.customfield;\r
+\r
+import com.vaadin.data.Item;\r
+import com.vaadin.data.Property;\r
+import com.vaadin.data.Property.ValueChangeEvent;\r
+import com.vaadin.tests.components.TestBase;\r
+import com.vaadin.tests.util.Person;\r
+import com.vaadin.ui.Table;\r
+\r
+/**\r
+ * Demonstrate the use of a form as a custom field within another form.\r
+ */\r
+public abstract class AbstractNestedFormExample extends TestBase {\r
+ private NestedPersonForm personForm;\r
+ private boolean embeddedAddress;\r
+\r
+ public void setup(boolean embeddedAddress) {\r
+ this.embeddedAddress = embeddedAddress;\r
+\r
+ addComponent(getPersonTable());\r
+ }\r
+\r
+ /**\r
+ * Creates a table with two person objects\r
+ */\r
+ public Table getPersonTable() {\r
+ Table table = new Table();\r
+ table.setPageLength(5);\r
+ table.setSelectable(true);\r
+ table.setImmediate(true);\r
+ table.setNullSelectionAllowed(true);\r
+ table.addContainerProperty("Name", String.class, null);\r
+ table.addListener(getTableValueChangeListener());\r
+ Person person = new Person("Teppo", "Testaaja",\r
+ "teppo.testaaja@example.com", "", "Ruukinkatu 2–4", 20540,\r
+ "Turku");\r
+ Person person2 = new Person("Taina", "Testaaja",\r
+ "taina.testaaja@example.com", "", "Ruukinkatu 2–4", 20540,\r
+ "Turku");\r
+ Item item = table.addItem(person);\r
+ item.getItemProperty("Name").setValue(\r
+ person.getFirstName() + " " + person.getLastName());\r
+ item = table.addItem(person2);\r
+ item.getItemProperty("Name").setValue(\r
+ person2.getFirstName() + " " + person2.getLastName());\r
+ return table;\r
+ }\r
+\r
+ /**\r
+ * Creates value change listener for the table\r
+ */\r
+ private Property.ValueChangeListener getTableValueChangeListener() {\r
+ return new Property.ValueChangeListener() {\r
+\r
+ public void valueChange(ValueChangeEvent event) {\r
+ if (personForm != null) {\r
+ removeComponent(personForm);\r
+ }\r
+ if (event.getProperty().getValue() != null) {\r
+ personForm = new NestedPersonForm((Person) event\r
+ .getProperty().getValue(), embeddedAddress);\r
+ personForm.setWidth("350px");\r
+ addComponent(personForm);\r
+ }\r
+ }\r
+\r
+ };\r
+ }\r
+\r
+ @Override\r
+ protected Integer getTicketNumber() {\r
+ return null;\r
+ }\r
+\r
+}\r
--- /dev/null
+package com.vaadin.tests.components.customfield;\r
+\r
+import java.util.Arrays;\r
+import java.util.List;\r
+\r
+import com.vaadin.data.Buffered;\r
+import com.vaadin.data.Validator.InvalidValueException;\r
+import com.vaadin.data.util.BeanItem;\r
+import com.vaadin.tests.util.Address;\r
+import com.vaadin.ui.Component;\r
+import com.vaadin.ui.CustomField;\r
+import com.vaadin.ui.Form;\r
+\r
+/**\r
+ * Nested form for the Address object of the Person object\r
+ */\r
+public class AddressField extends CustomField<Address> {\r
+ private Form addressForm;\r
+ private final Form parentForm;\r
+\r
+ public AddressField() {\r
+ this(null);\r
+ }\r
+\r
+ public AddressField(Form parentForm) {\r
+ this.parentForm = parentForm;\r
+ }\r
+\r
+ @Override\r
+ protected Component createContent() {\r
+ if (parentForm != null) {\r
+ addressForm = new EmbeddedForm(parentForm);\r
+ } else {\r
+ addressForm = new Form();\r
+ }\r
+ addressForm.setCaption("Address");\r
+ addressForm.setWriteThrough(false);\r
+\r
+ // make sure field changes are sent early\r
+ addressForm.setImmediate(true);\r
+\r
+ return addressForm;\r
+ }\r
+\r
+ @Override\r
+ protected Form getContent() {\r
+ return (Form) super.getContent();\r
+ }\r
+\r
+ @Override\r
+ public void setInternalValue(Address address) throws ReadOnlyException,\r
+ ConversionException {\r
+ // create the address if not given\r
+ if (null == address) {\r
+ address = new Address();\r
+ }\r
+\r
+ super.setInternalValue(address);\r
+\r
+ // set item data source and visible properties in a single operation to\r
+ // avoid creating fields multiple times\r
+ List<String> visibleProperties = Arrays.asList("streetAddress",\r
+ "postalCode", "city");\r
+ getContent().setItemDataSource(new BeanItem<Address>(address),\r
+ visibleProperties);\r
+ }\r
+\r
+ /**\r
+ * commit changes of the address form\r
+ */\r
+ @Override\r
+ public void commit() throws Buffered.SourceException, InvalidValueException {\r
+ addressForm.commit();\r
+ super.commit();\r
+ }\r
+\r
+ /**\r
+ * discard changes of the address form\r
+ */\r
+ @Override\r
+ public void discard() throws Buffered.SourceException {\r
+ // Do not discard the top-level value\r
+ // super.discard();\r
+ addressForm.discard();\r
+ }\r
+\r
+ @Override\r
+ public boolean isReadOnly() {\r
+ // In this application, the address is modified implicitly by\r
+ // addressForm.commit(), not by setting the Address object for a Person.\r
+ return false;\r
+ }\r
+\r
+ @Override\r
+ public Class<Address> getType() {\r
+ return Address.class;\r
+ }\r
+}
\ No newline at end of file
--- /dev/null
+package com.vaadin.tests.components.customfield;\r
+\r
+import com.vaadin.tests.components.TestBase;\r
+import com.vaadin.tests.util.Address;\r
+import com.vaadin.ui.Button;\r
+import com.vaadin.ui.Button.ClickEvent;\r
+\r
+/**\r
+ * Demonstrate a custom field which is a form, and contains another custom field\r
+ * for the selection of a city.\r
+ */\r
+public class AddressFormExample extends TestBase {\r
+\r
+ @Override\r
+ protected void setup() {\r
+ Address address = new Address("Ruukinkatu 2-4", 20540, "Turku");\r
+ final AddressField field = new AddressField();\r
+ field.setValue(address);\r
+\r
+ addComponent(field);\r
+\r
+ Button commitButton = new Button("Save", new Button.ClickListener() {\r
+ public void buttonClick(ClickEvent event) {\r
+ field.commit();\r
+ Address address = field.getValue();\r
+ field.getWindow().showNotification(\r
+ "Address saved: " + address.getStreetAddress() + ", "\r
+ + address.getPostalCode() + ", "\r
+ + address.getCity());\r
+ }\r
+ });\r
+ addComponent(commitButton);\r
+ }\r
+\r
+ @Override\r
+ protected String getDescription() {\r
+ return "Custom field for editing an Address";\r
+ }\r
+\r
+ @Override\r
+ protected Integer getTicketNumber() {\r
+ return null;\r
+ }\r
+\r
+}\r
--- /dev/null
+package com.vaadin.tests.components.customfield;\r
+\r
+import com.vaadin.ui.Button;\r
+import com.vaadin.ui.Button.ClickEvent;\r
+import com.vaadin.ui.Button.ClickListener;\r
+import com.vaadin.ui.Component;\r
+import com.vaadin.ui.CustomField;\r
+import com.vaadin.ui.Label;\r
+import com.vaadin.ui.VerticalLayout;\r
+\r
+/**\r
+ * An example of a custom field for editing a boolean value. The field is\r
+ * composed of multiple components, and could also edit a more complex data\r
+ * structures. Here, the commit etc. logic is not overridden.\r
+ */\r
+public class BooleanField extends CustomField {\r
+\r
+ @Override\r
+ protected Component createContent() {\r
+ VerticalLayout layout = new VerticalLayout();\r
+\r
+ layout.addComponent(new Label("Please click the button"));\r
+\r
+ final Button button = new Button("Click me");\r
+ button.addListener(new ClickListener() {\r
+ public void buttonClick(ClickEvent event) {\r
+ Object value = getValue();\r
+ boolean newValue = true;\r
+ if ((value instanceof Boolean) && ((Boolean) value)) {\r
+ newValue = false;\r
+ }\r
+ setValue(newValue);\r
+ button.setCaption(newValue ? "On" : "Off");\r
+ }\r
+ });\r
+ layout.addComponent(button);\r
+\r
+ return layout;\r
+ }\r
+\r
+ @Override\r
+ public Class<?> getType() {\r
+ return Boolean.class;\r
+ }\r
+}
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="" />
+<title>BooleanFieldExample</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr><td rowspan="1" colspan="3">BooleanFieldExample</td></tr>
+</thead><tbody>
+<tr>
+ <td>open</td>
+ <td>/run/com.vaadin.tests.components.customfield.BooleanFieldExample?restartApplication</td>
+ <td></td>
+</tr>
+<tr>
+ <td>screenCapture</td>
+ <td></td>
+ <td>initial</td>
+</tr>
+<tr>
+ <td>click</td>
+ <td>vaadin=runcomvaadintestscomponentscustomfieldBooleanFieldExample::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VVerticalLayout[0]/ChildComponentContainer[0]/VForm[0]/VFormLayout[0]/VFormLayout$VFormLayoutTable[0]/VCustomComponent[0]/VVerticalLayout[0]/ChildComponentContainer[1]/VButton[0]/domChild[0]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>screenCapture</td>
+ <td></td>
+ <td>on</td>
+</tr>
+<tr>
+ <td>click</td>
+ <td>vaadin=runcomvaadintestscomponentscustomfieldBooleanFieldExample::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VVerticalLayout[0]/ChildComponentContainer[0]/VForm[0]/VFormLayout[0]/VFormLayout$VFormLayoutTable[0]/VCustomComponent[0]/VVerticalLayout[0]/ChildComponentContainer[1]/VButton[0]/domChild[0]/domChild[0]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>screenCapture</td>
+ <td></td>
+ <td>off</td>
+</tr>
+
+</tbody></table>
+</body>
+</html>
--- /dev/null
+package com.vaadin.tests.components.customfield;\r
+\r
+import com.vaadin.data.Item;\r
+import com.vaadin.data.util.BeanItem;\r
+import com.vaadin.tests.components.TestBase;\r
+import com.vaadin.ui.Button;\r
+import com.vaadin.ui.Button.ClickEvent;\r
+import com.vaadin.ui.Button.ClickListener;\r
+import com.vaadin.ui.Component;\r
+import com.vaadin.ui.DefaultFieldFactory;\r
+import com.vaadin.ui.Field;\r
+import com.vaadin.ui.Form;\r
+import com.vaadin.ui.VerticalLayout;\r
+\r
+public class BooleanFieldExample extends TestBase {\r
+\r
+ /**\r
+ * Data model class with two boolean fields.\r
+ */\r
+ public static class TwoBooleans {\r
+ private boolean normal;\r
+ private boolean custom;\r
+\r
+ public void setNormal(boolean normal) {\r
+ this.normal = normal;\r
+ }\r
+\r
+ public boolean isNormal() {\r
+ return normal;\r
+ }\r
+\r
+ public void setCustom(boolean custom) {\r
+ this.custom = custom;\r
+ }\r
+\r
+ public boolean isCustom() {\r
+ return custom;\r
+ }\r
+ }\r
+\r
+ @Override\r
+ protected void setup() {\r
+ final VerticalLayout layout = new VerticalLayout();\r
+ layout.setMargin(true);\r
+\r
+ final Form form = new Form();\r
+ form.setFormFieldFactory(new DefaultFieldFactory() {\r
+ @Override\r
+ public Field createField(Item item, Object propertyId,\r
+ Component uiContext) {\r
+ if ("custom".equals(propertyId)) {\r
+ return new BooleanField();\r
+ }\r
+ return super.createField(item, propertyId, uiContext);\r
+ }\r
+ });\r
+ final TwoBooleans data = new TwoBooleans();\r
+ form.setItemDataSource(new BeanItem<TwoBooleans>(data));\r
+\r
+ layout.addComponent(form);\r
+\r
+ Button submit = new Button("Submit", new ClickListener() {\r
+ public void buttonClick(ClickEvent event) {\r
+ form.commit();\r
+ layout.getWindow()\r
+ .showNotification(\r
+ "The custom boolean field value is "\r
+ + data.isCustom()\r
+ + ".<br>"\r
+ + "The checkbox (default boolean field) value is "\r
+ + data.isNormal() + ".");\r
+ }\r
+ });\r
+ layout.addComponent(submit);\r
+\r
+ addComponent(layout);\r
+ }\r
+\r
+ @Override\r
+ protected String getDescription() {\r
+ return "A customized field (a two-state button) for editing a boolean value.";\r
+ }\r
+\r
+ @Override\r
+ protected Integer getTicketNumber() {\r
+ return null;\r
+ }\r
+\r
+}\r
--- /dev/null
+package com.vaadin.tests.components.customfield;\r
+\r
+import java.util.HashMap;\r
+import java.util.Map;\r
+\r
+import com.vaadin.ui.CustomLayout;\r
+import com.vaadin.ui.Field;\r
+import com.vaadin.ui.Form;\r
+import com.vaadin.ui.Layout;\r
+\r
+/**\r
+ * Form that displays its fields in the layout of another form.\r
+ * \r
+ * The fields are still logically part of this form even though they are in the\r
+ * layout of the parent form. The embedded form itself is automatically hidden.\r
+ * \r
+ * TODO Known issue: any field factory creating an {@link EmbeddedForm}\r
+ * (directly or indirectly) should re-use the field once it has been created to\r
+ * avoid the creation of duplicate fields when e.g. setting the visible item\r
+ * properties.\r
+ */\r
+public class EmbeddedForm extends Form {\r
+ private Form parentForm;\r
+ private Map<Object, Field> fields = new HashMap<Object, Field>();\r
+\r
+ /**\r
+ * Create a form that places its fields in another {@link Form}.\r
+ * \r
+ * @param parentForm\r
+ * form to which to embed the fields, not null\r
+ */\r
+ public EmbeddedForm(Form parentForm) {\r
+ this.parentForm = parentForm;\r
+ setVisible(false);\r
+ }\r
+\r
+ @Override\r
+ protected void attachField(Object propertyId, Field field) {\r
+ if (propertyId == null || field == null) {\r
+ return;\r
+ }\r
+\r
+ Layout layout = parentForm.getLayout();\r
+\r
+ Field oldField = fields.get(propertyId);\r
+ if (oldField != null) {\r
+ layout.removeComponent(oldField);\r
+ }\r
+\r
+ fields.put(propertyId, field);\r
+\r
+ if (layout instanceof CustomLayout) {\r
+ ((CustomLayout) layout).addComponent(field, propertyId.toString());\r
+ } else {\r
+ layout.addComponent(field);\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public boolean removeItemProperty(Object id) {\r
+ // remove the field from the parent layout if already added there\r
+ parentForm.getLayout().removeComponent(fields.get(id));\r
+ fields.remove(id);\r
+\r
+ return super.removeItemProperty(id);\r
+ }\r
+}
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost:8888/" />
+<title>EmbeddedFormExample</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr><td rowspan="1" colspan="3">EmbeddedFormExample</td></tr>
+</thead><tbody>
+<tr>
+ <td>open</td>
+ <td>/run/com.vaadin.tests.components.customfield.EmbeddedFormExample?restartApplication</td>
+ <td></td>
+</tr>
+<tr>
+ <td>mouseClick</td>
+ <td>vaadin=runcomvaadintestscomponentscustomfieldEmbeddedFormExample::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VScrollTable[0]/domChild[1]/domChild[0]/domChild[1]/domChild[0]/domChild[0]/domChild[0]/domChild[0]</td>
+ <td>66,10</td>
+</tr>
+<tr>
+ <td>screenCapture</td>
+ <td></td>
+ <td>initial</td>
+</tr>
+<tr>
+ <td>enterCharacter</td>
+ <td>vaadin=runcomvaadintestscomponentscustomfieldEmbeddedFormExample::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[1]/VForm[0]/VFormLayout[0]/VFormLayout$VFormLayoutTable[0]/VTextField[4]</td>
+ <td>Turkuaa</td>
+</tr>
+<tr>
+ <td>click</td>
+ <td>vaadin=runcomvaadintestscomponentscustomfieldEmbeddedFormExample::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[1]/VForm[0]/VHorizontalLayout[0]/ChildComponentContainer[0]/VHorizontalLayout[0]/ChildComponentContainer[0]/VButton[0]/domChild[0]/domChild[0]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>verifyValue</td>
+ <td>vaadin=runcomvaadintestscomponentscustomfieldEmbeddedFormExample::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[1]/VForm[0]/VFormLayout[0]/VFormLayout$VFormLayoutTable[0]/VTextField[4]</td>
+ <td>Turku</td>
+</tr>
+<tr>
+ <td>enterCharacter</td>
+ <td>vaadin=runcomvaadintestscomponentscustomfieldEmbeddedFormExample::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[1]/VForm[0]/VFormLayout[0]/VFormLayout$VFormLayoutTable[0]/VTextField[4]</td>
+ <td>Helsinki</td>
+</tr>
+<tr>
+ <td>click</td>
+ <td>vaadin=runcomvaadintestscomponentscustomfieldEmbeddedFormExample::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[1]/VForm[0]/VHorizontalLayout[0]/ChildComponentContainer[0]/VHorizontalLayout[0]/ChildComponentContainer[1]/VButton[0]/domChild[0]/domChild[0]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>verifyValue</td>
+ <td>vaadin=runcomvaadintestscomponentscustomfieldEmbeddedFormExample::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[1]/VForm[0]/VFormLayout[0]/VFormLayout$VFormLayoutTable[0]/VTextField[4]</td>
+ <td>Helsinki</td>
+</tr>
+<tr>
+ <td>mouseClick</td>
+ <td>vaadin=runcomvaadintestscomponentscustomfieldEmbeddedFormExample::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VScrollTable[0]/domChild[1]/domChild[0]/domChild[1]/domChild[0]/domChild[1]/domChild[0]/domChild[0]</td>
+ <td>52,15</td>
+</tr>
+<tr>
+ <td>verifyValue</td>
+ <td>vaadin=runcomvaadintestscomponentscustomfieldEmbeddedFormExample::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[1]/VForm[0]/VFormLayout[0]/VFormLayout$VFormLayoutTable[0]/VTextField[4]</td>
+ <td>Turku</td>
+</tr>
+<tr>
+ <td>mouseClick</td>
+ <td>vaadin=runcomvaadintestscomponentscustomfieldEmbeddedFormExample::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VScrollTable[0]/domChild[1]/domChild[0]/domChild[1]/domChild[0]/domChild[0]/domChild[0]/domChild[0]</td>
+ <td>56,10</td>
+</tr>
+<tr>
+ <td>verifyValue</td>
+ <td>vaadin=runcomvaadintestscomponentscustomfieldEmbeddedFormExample::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[1]/VForm[0]/VFormLayout[0]/VFormLayout$VFormLayoutTable[0]/VTextField[4]</td>
+ <td>Helsinki</td>
+</tr>
+
+</tbody></table>
+</body>
+</html>
--- /dev/null
+package com.vaadin.tests.components.customfield;
+
+public class EmbeddedFormExample extends AbstractNestedFormExample {
+
+ @Override
+ protected void setup() {
+ super.setup(true);
+ }
+
+ @Override
+ protected String getDescription() {
+ return "An address form embedded in a person form.\n"
+ + "The address fields are placed in the layout of the parent (person) form.\n"
+ + "Note that in many cases the same result can be achieved with a property that maps subfields to the top level.";
+ }
+
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="" />
+<title>NestedFormExample</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr><td rowspan="1" colspan="3">NestedFormExample</td></tr>
+</thead><tbody>
+<tr>
+ <td>open</td>
+ <td>/run/com.vaadin.tests.components.customfield.NestedFormExample?restartApplication</td>
+ <td></td>
+</tr>
+<tr>
+ <td>mouseClick</td>
+ <td>vaadin=runcomvaadintestscomponentscustomfieldNestedFormExample::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VScrollTable[0]/domChild[1]/domChild[0]/domChild[1]/domChild[0]/domChild[0]/domChild[0]/domChild[0]</td>
+ <td>33,9</td>
+</tr>
+<tr>
+ <td>screenCapture</td>
+ <td></td>
+ <td></td>
+</tr>
+<tr>
+ <td>enterCharacter</td>
+ <td>vaadin=runcomvaadintestscomponentscustomfieldNestedFormExample::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[1]/VForm[0]/VFormLayout[0]/VFormLayout$VFormLayoutTable[0]/VCustomComponent[0]/VForm[0]/VFormLayout[0]/VFormLayout$VFormLayoutTable[0]/VTextField[2]</td>
+ <td>Turkuaa</td>
+</tr>
+<tr>
+ <td>click</td>
+ <td>vaadin=runcomvaadintestscomponentscustomfieldNestedFormExample::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[1]/VForm[0]/VHorizontalLayout[0]/ChildComponentContainer[0]/VHorizontalLayout[0]/ChildComponentContainer[0]/VButton[0]/domChild[0]/domChild[0]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>verifyValue</td>
+ <td>vaadin=runcomvaadintestscomponentscustomfieldNestedFormExample::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[1]/VForm[0]/VFormLayout[0]/VFormLayout$VFormLayoutTable[0]/VCustomComponent[0]/VForm[0]/VFormLayout[0]/VFormLayout$VFormLayoutTable[0]/VTextField[2]</td>
+ <td>Turku</td>
+</tr>
+<tr>
+ <td>enterCharacter</td>
+ <td>vaadin=runcomvaadintestscomponentscustomfieldNestedFormExample::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[1]/VForm[0]/VFormLayout[0]/VFormLayout$VFormLayoutTable[0]/VCustomComponent[0]/VForm[0]/VFormLayout[0]/VFormLayout$VFormLayoutTable[0]/VTextField[2]</td>
+ <td>Helsinki</td>
+</tr>
+<tr>
+ <td>click</td>
+ <td>vaadin=runcomvaadintestscomponentscustomfieldNestedFormExample::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[1]/VForm[0]/VHorizontalLayout[0]/ChildComponentContainer[0]/VHorizontalLayout[0]/ChildComponentContainer[1]/VButton[0]/domChild[0]/domChild[0]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>verifyValue</td>
+ <td>vaadin=runcomvaadintestscomponentscustomfieldNestedFormExample::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[1]/VForm[0]/VFormLayout[0]/VFormLayout$VFormLayoutTable[0]/VCustomComponent[0]/VForm[0]/VFormLayout[0]/VFormLayout$VFormLayoutTable[0]/VTextField[2]</td>
+ <td>Helsinki</td>
+</tr>
+<tr>
+ <td>mouseClick</td>
+ <td>vaadin=runcomvaadintestscomponentscustomfieldNestedFormExample::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VScrollTable[0]/domChild[1]/domChild[0]/domChild[1]/domChild[0]/domChild[1]/domChild[0]/domChild[0]</td>
+ <td>56,14</td>
+</tr>
+<tr>
+ <td>verifyValue</td>
+ <td>vaadin=runcomvaadintestscomponentscustomfieldNestedFormExample::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[1]/VForm[0]/VFormLayout[0]/VFormLayout$VFormLayoutTable[0]/VCustomComponent[0]/VForm[0]/VFormLayout[0]/VFormLayout$VFormLayoutTable[0]/VTextField[2]</td>
+ <td>Turku</td>
+</tr>
+<tr>
+ <td>mouseClick</td>
+ <td>vaadin=runcomvaadintestscomponentscustomfieldNestedFormExample::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VScrollTable[0]/domChild[1]/domChild[0]/domChild[1]/domChild[0]/domChild[0]/domChild[0]/domChild[0]</td>
+ <td>30,9</td>
+</tr>
+<tr>
+ <td>verifyValue</td>
+ <td>vaadin=runcomvaadintestscomponentscustomfieldNestedFormExample::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[1]/VForm[0]/VFormLayout[0]/VFormLayout$VFormLayoutTable[0]/VCustomComponent[0]/VForm[0]/VFormLayout[0]/VFormLayout$VFormLayoutTable[0]/VTextField[2]</td>
+ <td>Helsinki</td>
+</tr>
+
+</tbody></table>
+</body>
+</html>
--- /dev/null
+package com.vaadin.tests.components.customfield;
+
+public class NestedFormExample extends AbstractNestedFormExample {
+
+ @Override
+ protected void setup() {
+ super.setup(false);
+ }
+
+ @Override
+ protected String getDescription() {
+ return "An address form nested in a person form.";
+ }
+
+}
--- /dev/null
+package com.vaadin.tests.components.customfield;
+
+import java.util.Arrays;
+
+import com.vaadin.data.Item;
+import com.vaadin.data.util.BeanItem;
+import com.vaadin.tests.util.Person;
+import com.vaadin.ui.Alignment;
+import com.vaadin.ui.Button;
+import com.vaadin.ui.Button.ClickEvent;
+import com.vaadin.ui.Component;
+import com.vaadin.ui.DefaultFieldFactory;
+import com.vaadin.ui.Field;
+import com.vaadin.ui.Form;
+import com.vaadin.ui.HorizontalLayout;
+
+/**
+ * Example of nested forms
+ */
+public class NestedPersonForm extends Form {
+ private BeanItem<Person> beanItem;
+ private final boolean embeddedAddress;
+
+ /**
+ * Creates a person form which contains nested form for the persons address
+ */
+ public NestedPersonForm(Person person, boolean embeddedAddress) {
+ this.embeddedAddress = embeddedAddress;
+
+ beanItem = new BeanItem<Person>(person);
+ setCaption("Update person details");
+ setWriteThrough(false);
+ setFormFieldFactory(new PersonFieldFactory());
+ // set the data source and the visible fields
+ // Note that if the nested form is the first or last field in the parent
+ // form, styles from the parent (padding, ...) may leak to its contents.
+ setItemDataSource(beanItem, Arrays.asList("firstName", "lastName",
+ "address", "email", "phoneNumber"));
+ getFooter().addComponent(getButtonsLayout());
+ getFooter().setMargin(false, false, true, true);
+ }
+
+ /**
+ * Get apply and discard button in the layout
+ */
+ private Component getButtonsLayout() {
+ HorizontalLayout buttons = new HorizontalLayout();
+ buttons.setSpacing(true);
+ Button discardChanges = new Button("Discard changes",
+ new Button.ClickListener() {
+ public void buttonClick(ClickEvent event) {
+ NestedPersonForm.this.discard();
+ }
+ });
+ buttons.addComponent(discardChanges);
+ buttons.setComponentAlignment(discardChanges, Alignment.MIDDLE_LEFT);
+
+ Button apply = new Button("Apply", new Button.ClickListener() {
+ public void buttonClick(ClickEvent event) {
+ try {
+ NestedPersonForm.this.commit();
+ } catch (Exception e) {
+ // Ignored, we'll let the Form handle the errors
+ }
+ }
+ });
+ buttons.addComponent(apply);
+ return buttons;
+ }
+
+ /**
+ * Field factory for person form
+ */
+ private class PersonFieldFactory extends DefaultFieldFactory {
+ // reuse the address field - required by EmbeddedForm
+ private AddressField addressField;
+
+ @Override
+ public Field createField(Item item, Object propertyId,
+ Component uiContext) {
+ Field f = super.createField(item, propertyId, uiContext);
+ if ("address".equals(propertyId)) {
+ // create a custom field for the Address object
+ if (addressField == null) {
+ Form form = (embeddedAddress && uiContext instanceof Form) ? (Form) uiContext
+ : null;
+ addressField = new AddressField(form);
+ }
+ f = addressField;
+ }
+ return f;
+ }
+ }
+}