From cd86b2c97ccdd452bda4157578ffcd29cab3d645 Mon Sep 17 00:00:00 2001 From: Henri Sara Date: Thu, 8 Dec 2011 10:38:53 +0200 Subject: [PATCH] Add CustomField component in Vaadin core (#3718) CustomField component, related changes in AbstractField and some tests/examples added to Vaadin core. --- src/com/vaadin/ui/AbstractField.java | 19 +- src/com/vaadin/ui/CustomField.java | 242 ++++++++++++++++++ .../AbstractNestedFormExample.java | 75 ++++++ .../components/customfield/AddressField.java | 98 +++++++ .../customfield/AddressFormExample.java | 45 ++++ .../components/customfield/BooleanField.java | 45 ++++ .../customfield/BooleanFieldExample.html | 47 ++++ .../customfield/BooleanFieldExample.java | 89 +++++++ .../components/customfield/EmbeddedForm.java | 67 +++++ .../customfield/EmbeddedFormExample.html | 82 ++++++ .../customfield/EmbeddedFormExample.java | 17 ++ .../customfield/NestedFormExample.html | 82 ++++++ .../customfield/NestedFormExample.java | 15 ++ .../customfield/NestedPersonForm.java | 94 +++++++ 14 files changed, 1012 insertions(+), 5 deletions(-) create mode 100644 src/com/vaadin/ui/CustomField.java create mode 100644 tests/testbench/com/vaadin/tests/components/customfield/AbstractNestedFormExample.java create mode 100644 tests/testbench/com/vaadin/tests/components/customfield/AddressField.java create mode 100644 tests/testbench/com/vaadin/tests/components/customfield/AddressFormExample.java create mode 100644 tests/testbench/com/vaadin/tests/components/customfield/BooleanField.java create mode 100644 tests/testbench/com/vaadin/tests/components/customfield/BooleanFieldExample.html create mode 100644 tests/testbench/com/vaadin/tests/components/customfield/BooleanFieldExample.java create mode 100644 tests/testbench/com/vaadin/tests/components/customfield/EmbeddedForm.java create mode 100644 tests/testbench/com/vaadin/tests/components/customfield/EmbeddedFormExample.html create mode 100644 tests/testbench/com/vaadin/tests/components/customfield/EmbeddedFormExample.java create mode 100644 tests/testbench/com/vaadin/tests/components/customfield/NestedFormExample.html create mode 100644 tests/testbench/com/vaadin/tests/components/customfield/NestedFormExample.java create mode 100644 tests/testbench/com/vaadin/tests/components/customfield/NestedPersonForm.java diff --git a/src/com/vaadin/ui/AbstractField.java b/src/com/vaadin/ui/AbstractField.java index 73760d9247..44fb71ac84 100644 --- a/src/com/vaadin/ui/AbstractField.java +++ b/src/com/vaadin/ui/AbstractField.java @@ -328,7 +328,7 @@ public abstract class AbstractField extends AbstractComponent implements 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) { @@ -355,7 +355,7 @@ public abstract class AbstractField extends AbstractComponent implements 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 @@ -521,7 +521,7 @@ public abstract class AbstractField extends AbstractComponent implements 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()) { @@ -646,7 +646,7 @@ public abstract class AbstractField extends AbstractComponent implements 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 @@ -705,6 +705,7 @@ public abstract class AbstractField extends AbstractComponent implements } // Fires value change if the value has changed + T value = getInternalValue(); if ((value != oldValue) && ((value != null && !value.equals(oldValue)) || value == null)) { fireValueChange(false); @@ -1143,7 +1144,7 @@ public abstract class AbstractField extends AbstractComponent implements if (isReadThrough()) { if (committingValueToDataSource) { boolean propertyNotifiesOfTheBufferedValue = equals(event - .getProperty().getValue(), value); + .getProperty().getValue(), getInternalValue()); if (!propertyNotifiesOfTheBufferedValue) { /* * Property (or chained property like PropertyFormatter) now @@ -1201,6 +1202,14 @@ public abstract class AbstractField extends AbstractComponent implements 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. diff --git a/src/com/vaadin/ui/CustomField.java b/src/com/vaadin/ui/CustomField.java new file mode 100644 index 0000000000..eb4cdc3ef6 --- /dev/null +++ b/src/com/vaadin/ui/CustomField.java @@ -0,0 +1,242 @@ +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 extends AbstractField implements + ComponentContainer { + + /** + * The root component implementing the custom component. + */ + private Component root = null; + + /** + * Constructs a new custom field. + * + *

+ * 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. + *

+ */ + 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, + 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 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 + } + +} diff --git a/tests/testbench/com/vaadin/tests/components/customfield/AbstractNestedFormExample.java b/tests/testbench/com/vaadin/tests/components/customfield/AbstractNestedFormExample.java new file mode 100644 index 0000000000..d8c962f1e3 --- /dev/null +++ b/tests/testbench/com/vaadin/tests/components/customfield/AbstractNestedFormExample.java @@ -0,0 +1,75 @@ +package com.vaadin.tests.components.customfield; + +import com.vaadin.data.Item; +import com.vaadin.data.Property; +import com.vaadin.data.Property.ValueChangeEvent; +import com.vaadin.tests.components.TestBase; +import com.vaadin.tests.util.Person; +import com.vaadin.ui.Table; + +/** + * Demonstrate the use of a form as a custom field within another form. + */ +public abstract class AbstractNestedFormExample extends TestBase { + private NestedPersonForm personForm; + private boolean embeddedAddress; + + public void setup(boolean embeddedAddress) { + this.embeddedAddress = embeddedAddress; + + addComponent(getPersonTable()); + } + + /** + * Creates a table with two person objects + */ + public Table getPersonTable() { + Table table = new Table(); + table.setPageLength(5); + table.setSelectable(true); + table.setImmediate(true); + table.setNullSelectionAllowed(true); + table.addContainerProperty("Name", String.class, null); + table.addListener(getTableValueChangeListener()); + Person person = new Person("Teppo", "Testaaja", + "teppo.testaaja@example.com", "", "Ruukinkatu 2–4", 20540, + "Turku"); + Person person2 = new Person("Taina", "Testaaja", + "taina.testaaja@example.com", "", "Ruukinkatu 2–4", 20540, + "Turku"); + Item item = table.addItem(person); + item.getItemProperty("Name").setValue( + person.getFirstName() + " " + person.getLastName()); + item = table.addItem(person2); + item.getItemProperty("Name").setValue( + person2.getFirstName() + " " + person2.getLastName()); + return table; + } + + /** + * Creates value change listener for the table + */ + private Property.ValueChangeListener getTableValueChangeListener() { + return new Property.ValueChangeListener() { + + public void valueChange(ValueChangeEvent event) { + if (personForm != null) { + removeComponent(personForm); + } + if (event.getProperty().getValue() != null) { + personForm = new NestedPersonForm((Person) event + .getProperty().getValue(), embeddedAddress); + personForm.setWidth("350px"); + addComponent(personForm); + } + } + + }; + } + + @Override + protected Integer getTicketNumber() { + return null; + } + +} diff --git a/tests/testbench/com/vaadin/tests/components/customfield/AddressField.java b/tests/testbench/com/vaadin/tests/components/customfield/AddressField.java new file mode 100644 index 0000000000..a02b3235d8 --- /dev/null +++ b/tests/testbench/com/vaadin/tests/components/customfield/AddressField.java @@ -0,0 +1,98 @@ +package com.vaadin.tests.components.customfield; + +import java.util.Arrays; +import java.util.List; + +import com.vaadin.data.Buffered; +import com.vaadin.data.Validator.InvalidValueException; +import com.vaadin.data.util.BeanItem; +import com.vaadin.tests.util.Address; +import com.vaadin.ui.Component; +import com.vaadin.ui.CustomField; +import com.vaadin.ui.Form; + +/** + * Nested form for the Address object of the Person object + */ +public class AddressField extends CustomField
{ + private Form addressForm; + private final Form parentForm; + + public AddressField() { + this(null); + } + + public AddressField(Form parentForm) { + this.parentForm = parentForm; + } + + @Override + protected Component createContent() { + if (parentForm != null) { + addressForm = new EmbeddedForm(parentForm); + } else { + addressForm = new Form(); + } + addressForm.setCaption("Address"); + addressForm.setWriteThrough(false); + + // make sure field changes are sent early + addressForm.setImmediate(true); + + return addressForm; + } + + @Override + protected Form getContent() { + return (Form) super.getContent(); + } + + @Override + public void setInternalValue(Address address) throws ReadOnlyException, + ConversionException { + // create the address if not given + if (null == address) { + address = new Address(); + } + + super.setInternalValue(address); + + // set item data source and visible properties in a single operation to + // avoid creating fields multiple times + List visibleProperties = Arrays.asList("streetAddress", + "postalCode", "city"); + getContent().setItemDataSource(new BeanItem
(address), + visibleProperties); + } + + /** + * commit changes of the address form + */ + @Override + public void commit() throws Buffered.SourceException, InvalidValueException { + addressForm.commit(); + super.commit(); + } + + /** + * discard changes of the address form + */ + @Override + public void discard() throws Buffered.SourceException { + // Do not discard the top-level value + // super.discard(); + addressForm.discard(); + } + + @Override + public boolean isReadOnly() { + // In this application, the address is modified implicitly by + // addressForm.commit(), not by setting the Address object for a Person. + return false; + } + + @Override + public Class
getType() { + return Address.class; + } +} \ No newline at end of file diff --git a/tests/testbench/com/vaadin/tests/components/customfield/AddressFormExample.java b/tests/testbench/com/vaadin/tests/components/customfield/AddressFormExample.java new file mode 100644 index 0000000000..5386a404b4 --- /dev/null +++ b/tests/testbench/com/vaadin/tests/components/customfield/AddressFormExample.java @@ -0,0 +1,45 @@ +package com.vaadin.tests.components.customfield; + +import com.vaadin.tests.components.TestBase; +import com.vaadin.tests.util.Address; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; + +/** + * Demonstrate a custom field which is a form, and contains another custom field + * for the selection of a city. + */ +public class AddressFormExample extends TestBase { + + @Override + protected void setup() { + Address address = new Address("Ruukinkatu 2-4", 20540, "Turku"); + final AddressField field = new AddressField(); + field.setValue(address); + + addComponent(field); + + Button commitButton = new Button("Save", new Button.ClickListener() { + public void buttonClick(ClickEvent event) { + field.commit(); + Address address = field.getValue(); + field.getWindow().showNotification( + "Address saved: " + address.getStreetAddress() + ", " + + address.getPostalCode() + ", " + + address.getCity()); + } + }); + addComponent(commitButton); + } + + @Override + protected String getDescription() { + return "Custom field for editing an Address"; + } + + @Override + protected Integer getTicketNumber() { + return null; + } + +} diff --git a/tests/testbench/com/vaadin/tests/components/customfield/BooleanField.java b/tests/testbench/com/vaadin/tests/components/customfield/BooleanField.java new file mode 100644 index 0000000000..196a04eb5e --- /dev/null +++ b/tests/testbench/com/vaadin/tests/components/customfield/BooleanField.java @@ -0,0 +1,45 @@ +package com.vaadin.tests.components.customfield; + +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.Button.ClickListener; +import com.vaadin.ui.Component; +import com.vaadin.ui.CustomField; +import com.vaadin.ui.Label; +import com.vaadin.ui.VerticalLayout; + +/** + * An example of a custom field for editing a boolean value. The field is + * composed of multiple components, and could also edit a more complex data + * structures. Here, the commit etc. logic is not overridden. + */ +public class BooleanField extends CustomField { + + @Override + protected Component createContent() { + VerticalLayout layout = new VerticalLayout(); + + layout.addComponent(new Label("Please click the button")); + + final Button button = new Button("Click me"); + button.addListener(new ClickListener() { + public void buttonClick(ClickEvent event) { + Object value = getValue(); + boolean newValue = true; + if ((value instanceof Boolean) && ((Boolean) value)) { + newValue = false; + } + setValue(newValue); + button.setCaption(newValue ? "On" : "Off"); + } + }); + layout.addComponent(button); + + return layout; + } + + @Override + public Class getType() { + return Boolean.class; + } +} \ No newline at end of file diff --git a/tests/testbench/com/vaadin/tests/components/customfield/BooleanFieldExample.html b/tests/testbench/com/vaadin/tests/components/customfield/BooleanFieldExample.html new file mode 100644 index 0000000000..6b1b3e8a2d --- /dev/null +++ b/tests/testbench/com/vaadin/tests/components/customfield/BooleanFieldExample.html @@ -0,0 +1,47 @@ + + + + + + +BooleanFieldExample + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
BooleanFieldExample
open/run/com.vaadin.tests.components.customfield.BooleanFieldExample?restartApplication
screenCaptureinitial
clickvaadin=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]
screenCaptureon
clickvaadin=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]
screenCaptureoff
+ + diff --git a/tests/testbench/com/vaadin/tests/components/customfield/BooleanFieldExample.java b/tests/testbench/com/vaadin/tests/components/customfield/BooleanFieldExample.java new file mode 100644 index 0000000000..d53a38ed4e --- /dev/null +++ b/tests/testbench/com/vaadin/tests/components/customfield/BooleanFieldExample.java @@ -0,0 +1,89 @@ +package com.vaadin.tests.components.customfield; + +import com.vaadin.data.Item; +import com.vaadin.data.util.BeanItem; +import com.vaadin.tests.components.TestBase; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.Button.ClickListener; +import com.vaadin.ui.Component; +import com.vaadin.ui.DefaultFieldFactory; +import com.vaadin.ui.Field; +import com.vaadin.ui.Form; +import com.vaadin.ui.VerticalLayout; + +public class BooleanFieldExample extends TestBase { + + /** + * Data model class with two boolean fields. + */ + public static class TwoBooleans { + private boolean normal; + private boolean custom; + + public void setNormal(boolean normal) { + this.normal = normal; + } + + public boolean isNormal() { + return normal; + } + + public void setCustom(boolean custom) { + this.custom = custom; + } + + public boolean isCustom() { + return custom; + } + } + + @Override + protected void setup() { + final VerticalLayout layout = new VerticalLayout(); + layout.setMargin(true); + + final Form form = new Form(); + form.setFormFieldFactory(new DefaultFieldFactory() { + @Override + public Field createField(Item item, Object propertyId, + Component uiContext) { + if ("custom".equals(propertyId)) { + return new BooleanField(); + } + return super.createField(item, propertyId, uiContext); + } + }); + final TwoBooleans data = new TwoBooleans(); + form.setItemDataSource(new BeanItem(data)); + + layout.addComponent(form); + + Button submit = new Button("Submit", new ClickListener() { + public void buttonClick(ClickEvent event) { + form.commit(); + layout.getWindow() + .showNotification( + "The custom boolean field value is " + + data.isCustom() + + ".
" + + "The checkbox (default boolean field) value is " + + data.isNormal() + "."); + } + }); + layout.addComponent(submit); + + addComponent(layout); + } + + @Override + protected String getDescription() { + return "A customized field (a two-state button) for editing a boolean value."; + } + + @Override + protected Integer getTicketNumber() { + return null; + } + +} diff --git a/tests/testbench/com/vaadin/tests/components/customfield/EmbeddedForm.java b/tests/testbench/com/vaadin/tests/components/customfield/EmbeddedForm.java new file mode 100644 index 0000000000..ceaea1f53e --- /dev/null +++ b/tests/testbench/com/vaadin/tests/components/customfield/EmbeddedForm.java @@ -0,0 +1,67 @@ +package com.vaadin.tests.components.customfield; + +import java.util.HashMap; +import java.util.Map; + +import com.vaadin.ui.CustomLayout; +import com.vaadin.ui.Field; +import com.vaadin.ui.Form; +import com.vaadin.ui.Layout; + +/** + * Form that displays its fields in the layout of another form. + * + * The fields are still logically part of this form even though they are in the + * layout of the parent form. The embedded form itself is automatically hidden. + * + * TODO Known issue: any field factory creating an {@link EmbeddedForm} + * (directly or indirectly) should re-use the field once it has been created to + * avoid the creation of duplicate fields when e.g. setting the visible item + * properties. + */ +public class EmbeddedForm extends Form { + private Form parentForm; + private Map fields = new HashMap(); + + /** + * Create a form that places its fields in another {@link Form}. + * + * @param parentForm + * form to which to embed the fields, not null + */ + public EmbeddedForm(Form parentForm) { + this.parentForm = parentForm; + setVisible(false); + } + + @Override + protected void attachField(Object propertyId, Field field) { + if (propertyId == null || field == null) { + return; + } + + Layout layout = parentForm.getLayout(); + + Field oldField = fields.get(propertyId); + if (oldField != null) { + layout.removeComponent(oldField); + } + + fields.put(propertyId, field); + + if (layout instanceof CustomLayout) { + ((CustomLayout) layout).addComponent(field, propertyId.toString()); + } else { + layout.addComponent(field); + } + } + + @Override + public boolean removeItemProperty(Object id) { + // remove the field from the parent layout if already added there + parentForm.getLayout().removeComponent(fields.get(id)); + fields.remove(id); + + return super.removeItemProperty(id); + } +} \ No newline at end of file diff --git a/tests/testbench/com/vaadin/tests/components/customfield/EmbeddedFormExample.html b/tests/testbench/com/vaadin/tests/components/customfield/EmbeddedFormExample.html new file mode 100644 index 0000000000..2af441bc83 --- /dev/null +++ b/tests/testbench/com/vaadin/tests/components/customfield/EmbeddedFormExample.html @@ -0,0 +1,82 @@ + + + + + + +EmbeddedFormExample + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
EmbeddedFormExample
open/run/com.vaadin.tests.components.customfield.EmbeddedFormExample?restartApplication
mouseClickvaadin=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]66,10
screenCaptureinitial
enterCharactervaadin=runcomvaadintestscomponentscustomfieldEmbeddedFormExample::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[1]/VForm[0]/VFormLayout[0]/VFormLayout$VFormLayoutTable[0]/VTextField[4]Turkuaa
clickvaadin=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]
verifyValuevaadin=runcomvaadintestscomponentscustomfieldEmbeddedFormExample::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[1]/VForm[0]/VFormLayout[0]/VFormLayout$VFormLayoutTable[0]/VTextField[4]Turku
enterCharactervaadin=runcomvaadintestscomponentscustomfieldEmbeddedFormExample::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[1]/VForm[0]/VFormLayout[0]/VFormLayout$VFormLayoutTable[0]/VTextField[4]Helsinki
clickvaadin=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]
verifyValuevaadin=runcomvaadintestscomponentscustomfieldEmbeddedFormExample::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[1]/VForm[0]/VFormLayout[0]/VFormLayout$VFormLayoutTable[0]/VTextField[4]Helsinki
mouseClickvaadin=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]52,15
verifyValuevaadin=runcomvaadintestscomponentscustomfieldEmbeddedFormExample::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[1]/VForm[0]/VFormLayout[0]/VFormLayout$VFormLayoutTable[0]/VTextField[4]Turku
mouseClickvaadin=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]56,10
verifyValuevaadin=runcomvaadintestscomponentscustomfieldEmbeddedFormExample::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[1]/VForm[0]/VFormLayout[0]/VFormLayout$VFormLayoutTable[0]/VTextField[4]Helsinki
+ + diff --git a/tests/testbench/com/vaadin/tests/components/customfield/EmbeddedFormExample.java b/tests/testbench/com/vaadin/tests/components/customfield/EmbeddedFormExample.java new file mode 100644 index 0000000000..aeb1984937 --- /dev/null +++ b/tests/testbench/com/vaadin/tests/components/customfield/EmbeddedFormExample.java @@ -0,0 +1,17 @@ +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."; + } + +} diff --git a/tests/testbench/com/vaadin/tests/components/customfield/NestedFormExample.html b/tests/testbench/com/vaadin/tests/components/customfield/NestedFormExample.html new file mode 100644 index 0000000000..f9f5783c05 --- /dev/null +++ b/tests/testbench/com/vaadin/tests/components/customfield/NestedFormExample.html @@ -0,0 +1,82 @@ + + + + + + +NestedFormExample + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NestedFormExample
open/run/com.vaadin.tests.components.customfield.NestedFormExample?restartApplication
mouseClickvaadin=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]33,9
screenCapture
enterCharactervaadin=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]Turkuaa
clickvaadin=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]
verifyValuevaadin=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]Turku
enterCharactervaadin=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]Helsinki
clickvaadin=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]
verifyValuevaadin=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]Helsinki
mouseClickvaadin=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]56,14
verifyValuevaadin=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]Turku
mouseClickvaadin=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]30,9
verifyValuevaadin=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]Helsinki
+ + diff --git a/tests/testbench/com/vaadin/tests/components/customfield/NestedFormExample.java b/tests/testbench/com/vaadin/tests/components/customfield/NestedFormExample.java new file mode 100644 index 0000000000..91fb43f4e2 --- /dev/null +++ b/tests/testbench/com/vaadin/tests/components/customfield/NestedFormExample.java @@ -0,0 +1,15 @@ +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."; + } + +} diff --git a/tests/testbench/com/vaadin/tests/components/customfield/NestedPersonForm.java b/tests/testbench/com/vaadin/tests/components/customfield/NestedPersonForm.java new file mode 100644 index 0000000000..e0a3b08bc7 --- /dev/null +++ b/tests/testbench/com/vaadin/tests/components/customfield/NestedPersonForm.java @@ -0,0 +1,94 @@ +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 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); + 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; + } + } +} -- 2.39.5