summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--compatibility-client/src/main/java/com/vaadin/v7/client/ui/VForm.java141
-rw-r--r--compatibility-client/src/main/java/com/vaadin/v7/client/ui/form/FormConnector.java236
-rw-r--r--compatibility-server/src/main/java/com/vaadin/v7/ui/DateField.java45
-rw-r--r--compatibility-server/src/main/java/com/vaadin/v7/ui/DefaultFieldFactory.java51
-rw-r--r--compatibility-server/src/main/java/com/vaadin/v7/ui/Form.java1421
-rw-r--r--compatibility-server/src/main/java/com/vaadin/v7/ui/FormFieldFactory.java56
-rw-r--r--compatibility-server/src/test/java/com/vaadin/v7/tests/server/SerializationTest.java22
-rw-r--r--compatibility-server/src/test/java/com/vaadin/v7/tests/server/component/form/FormTest.java68
-rw-r--r--compatibility-shared/src/main/java/com/vaadin/v7/shared/form/FormState.java27
-rw-r--r--server/src/main/java/com/vaadin/server/ComponentSizeValidator.java53
-rw-r--r--uitest/src/main/java/com/vaadin/v7/tests/components/form/FormTooltips.java58
-rw-r--r--uitest/src/main/java/com/vaadin/v7/tests/components/window/UndefinedHeightSubWindowAndContent.java60
-rw-r--r--uitest/src/test/java/com/vaadin/v7/tests/components/form/FormTooltipsTest.java60
-rw-r--r--uitest/src/test/java/com/vaadin/v7/tests/components/window/UndefinedHeightSubWindowAndContentTest.java34
14 files changed, 2308 insertions, 24 deletions
diff --git a/compatibility-client/src/main/java/com/vaadin/v7/client/ui/VForm.java b/compatibility-client/src/main/java/com/vaadin/v7/client/ui/VForm.java
new file mode 100644
index 0000000000..b5adc5cd8f
--- /dev/null
+++ b/compatibility-client/src/main/java/com/vaadin/v7/client/ui/VForm.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2000-2016 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.v7.client.ui;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.ComplexPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.VErrorMessage;
+import com.vaadin.client.ui.Icon;
+import com.vaadin.client.ui.ShortcutActionHandler;
+
+public class VForm extends ComplexPanel implements KeyDownHandler {
+
+ public static final String CLASSNAME = "v-form";
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public String id;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public Widget lo;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public Element legend = DOM.createLegend();
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public Element caption = DOM.createSpan();
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public Element desc = DOM.createDiv();
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public Icon icon;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public VErrorMessage errorMessage = new VErrorMessage();
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public Element fieldContainer = DOM.createDiv();
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public Element footerContainer = DOM.createDiv();
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public Element fieldSet = DOM.createFieldSet();
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public Widget footer;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public ApplicationConnection client;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public ShortcutActionHandler shortcutHandler;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public HandlerRegistration keyDownRegistration;
+
+ public VForm() {
+ setElement(DOM.createDiv());
+ getElement().appendChild(fieldSet);
+ setStyleName(CLASSNAME);
+ fieldSet.appendChild(legend);
+ legend.appendChild(caption);
+
+ fieldSet.appendChild(desc); // Adding description for initial padding
+ // measurements, removed later if no
+ // description is set
+
+ fieldSet.appendChild(fieldContainer);
+ errorMessage.setVisible(false);
+
+ fieldSet.appendChild(errorMessage.getElement());
+ fieldSet.appendChild(footerContainer);
+
+ errorMessage.setOwner(this);
+ }
+
+ @Override
+ public void setStyleName(String style) {
+ super.setStyleName(style);
+ updateStyleNames();
+ }
+
+ @Override
+ public void setStylePrimaryName(String style) {
+ super.setStylePrimaryName(style);
+ updateStyleNames();
+ }
+
+ protected void updateStyleNames() {
+ fieldContainer.setClassName(getStylePrimaryName() + "-content");
+ errorMessage.setStyleName(getStylePrimaryName() + "-errormessage");
+ desc.setClassName(getStylePrimaryName() + "-description");
+ footerContainer.setClassName(getStylePrimaryName() + "-footer");
+ }
+
+ @Override
+ public void onKeyDown(KeyDownEvent event) {
+ shortcutHandler.handleKeyboardEvent(Event.as(event.getNativeEvent()));
+ }
+
+ public void setFooterWidget(Widget footerWidget) {
+ if (footer != null) {
+ remove(footer);
+ }
+ if (footerWidget != null) {
+ super.add(footerWidget, footerContainer);
+ }
+ footer = footerWidget;
+ }
+
+ public void setLayoutWidget(Widget newLayoutWidget) {
+ if (lo != null) {
+ remove(lo);
+ }
+ if (newLayoutWidget != null) {
+ super.add(newLayoutWidget, fieldContainer);
+ }
+ lo = newLayoutWidget;
+ }
+}
diff --git a/compatibility-client/src/main/java/com/vaadin/v7/client/ui/form/FormConnector.java b/compatibility-client/src/main/java/com/vaadin/v7/client/ui/form/FormConnector.java
new file mode 100644
index 0000000000..e3f04aaa59
--- /dev/null
+++ b/compatibility-client/src/main/java/com/vaadin/v7/client/ui/form/FormConnector.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright 2000-2016 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.v7.client.ui.form;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.ComponentConnector;
+import com.vaadin.client.ConnectorHierarchyChangeEvent;
+import com.vaadin.client.LayoutManager;
+import com.vaadin.client.Paintable;
+import com.vaadin.client.TooltipInfo;
+import com.vaadin.client.UIDL;
+import com.vaadin.client.VCaption;
+import com.vaadin.client.ui.AbstractComponentContainerConnector;
+import com.vaadin.client.ui.ShortcutActionHandler;
+import com.vaadin.client.ui.layout.ElementResizeEvent;
+import com.vaadin.client.ui.layout.ElementResizeListener;
+import com.vaadin.client.ui.layout.MayScrollChildren;
+import com.vaadin.shared.ui.ComponentStateUtil;
+import com.vaadin.shared.ui.Connect;
+import com.vaadin.v7.client.ui.VForm;
+import com.vaadin.v7.shared.form.FormState;
+import com.vaadin.v7.ui.Form;
+
+@Connect(Form.class)
+public class FormConnector extends AbstractComponentContainerConnector
+ implements Paintable, MayScrollChildren {
+
+ private final ElementResizeListener footerResizeListener = new ElementResizeListener() {
+ @Override
+ public void onElementResize(ElementResizeEvent e) {
+ com.vaadin.v7.client.ui.VForm form = getWidget();
+
+ LayoutManager lm = getLayoutManager();
+ int footerHeight = 0;
+ if (form.footer != null) {
+ footerHeight += lm.getOuterHeight(form.footer.getElement());
+ }
+
+ if (form.errorMessage.isVisible()) {
+ footerHeight += lm
+ .getOuterHeight(form.errorMessage.getElement());
+ footerHeight -= lm.getMarginTop(form.errorMessage.getElement());
+ form.errorMessage.getElement().getStyle()
+ .setMarginTop(-footerHeight, Unit.PX);
+ form.footerContainer.getStyle().clearMarginTop();
+ } else {
+ form.footerContainer.getStyle().setMarginTop(-footerHeight,
+ Unit.PX);
+ }
+
+ form.fieldContainer.getStyle().setPaddingBottom(footerHeight,
+ Unit.PX);
+ }
+ };
+
+ @Override
+ protected void init() {
+ getLayoutManager().addElementResizeListener(
+ getWidget().errorMessage.getElement(), footerResizeListener);
+ }
+
+ @Override
+ public void onUnregister() {
+ VForm form = getWidget();
+ getLayoutManager().removeElementResizeListener(
+ form.errorMessage.getElement(), footerResizeListener);
+ if (form.footer != null) {
+ getLayoutManager().removeElementResizeListener(
+ form.footer.getElement(), footerResizeListener);
+ }
+ }
+
+ @Override
+ public boolean delegateCaptionHandling() {
+ return false;
+ }
+
+ @Override
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+ getWidget().client = client;
+ getWidget().id = uidl.getId();
+
+ if (!isRealUpdate(uidl)) {
+ return;
+ }
+
+ boolean legendEmpty = true;
+ if (getState().caption != null) {
+ VCaption.setCaptionText(getWidget().caption, getState());
+ legendEmpty = false;
+ } else {
+ getWidget().caption.setInnerText("");
+ }
+ if (getWidget().icon != null) {
+ getWidget().legend.removeChild(getWidget().icon.getElement());
+ }
+ if (getIconUri() != null) {
+ getWidget().icon = client.getIcon(getIconUri());
+ getWidget().legend.insertFirst(getWidget().icon.getElement());
+
+ legendEmpty = false;
+ }
+ if (legendEmpty) {
+ getWidget().addStyleDependentName("nocaption");
+ } else {
+ getWidget().removeStyleDependentName("nocaption");
+ }
+
+ if (null != getState().errorMessage) {
+ getWidget().errorMessage.updateMessage(getState().errorMessage);
+ getWidget().errorMessage.setVisible(true);
+ } else {
+ getWidget().errorMessage.setVisible(false);
+ }
+
+ if (ComponentStateUtil.hasDescription(getState())) {
+ getWidget().desc.setInnerHTML(getState().description);
+ if (getWidget().desc.getParentElement() == null) {
+ getWidget().fieldSet.insertAfter(getWidget().desc,
+ getWidget().legend);
+ }
+ } else {
+ getWidget().desc.setInnerHTML("");
+ if (getWidget().desc.getParentElement() != null) {
+ getWidget().fieldSet.removeChild(getWidget().desc);
+ }
+ }
+
+ // also recalculates size of the footer if undefined size form - see
+ // #3710
+ client.runDescendentsLayout(getWidget());
+
+ // We may have actions attached
+ if (uidl.getChildCount() >= 1) {
+ UIDL childUidl = uidl.getChildByTagName("actions");
+ if (childUidl != null) {
+ if (getWidget().shortcutHandler == null) {
+ getWidget().shortcutHandler = new ShortcutActionHandler(
+ getConnectorId(), client);
+ getWidget().keyDownRegistration = getWidget()
+ .addDomHandler(getWidget(), KeyDownEvent.getType());
+ }
+ getWidget().shortcutHandler.updateActionMap(childUidl);
+ }
+ } else if (getWidget().shortcutHandler != null) {
+ getWidget().keyDownRegistration.removeHandler();
+ getWidget().shortcutHandler = null;
+ getWidget().keyDownRegistration = null;
+ }
+ }
+
+ @Override
+ public void updateCaption(ComponentConnector component) {
+ // NOP form don't render caption for neither field layout nor footer
+ // layout
+ }
+
+ @Override
+ public VForm getWidget() {
+ return (VForm) super.getWidget();
+ }
+
+ @Override
+ public boolean isReadOnly() {
+ return super.isReadOnly() || getState().propertyReadOnly;
+ }
+
+ @Override
+ public FormState getState() {
+ return (FormState) super.getState();
+ }
+
+ private ComponentConnector getFooter() {
+ return (ComponentConnector) getState().footer;
+ }
+
+ private ComponentConnector getLayout() {
+ return (ComponentConnector) getState().layout;
+ }
+
+ @Override
+ public void onConnectorHierarchyChange(
+ ConnectorHierarchyChangeEvent connectorHierarchyChangeEvent) {
+ Widget newFooterWidget = null;
+ ComponentConnector footer = getFooter();
+
+ if (footer != null) {
+ newFooterWidget = footer.getWidget();
+ Widget currentFooter = getWidget().footer;
+ if (currentFooter != null) {
+ // Remove old listener
+ getLayoutManager().removeElementResizeListener(
+ currentFooter.getElement(), footerResizeListener);
+ }
+ getLayoutManager().addElementResizeListener(
+ newFooterWidget.getElement(), footerResizeListener);
+ }
+ getWidget().setFooterWidget(newFooterWidget);
+
+ Widget newLayoutWidget = null;
+ ComponentConnector newLayout = getLayout();
+ if (newLayout != null) {
+ newLayoutWidget = newLayout.getWidget();
+ }
+ getWidget().setLayoutWidget(newLayoutWidget);
+ }
+
+ @Override
+ public TooltipInfo getTooltipInfo(Element element) {
+ // Form shows its description and error message
+ // as a part of the actual layout
+ return null;
+ }
+
+ @Override
+ public boolean hasTooltip() {
+ return false;
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/v7/ui/DateField.java b/compatibility-server/src/main/java/com/vaadin/v7/ui/DateField.java
index b1e314d4de..14318cea8d 100644
--- a/compatibility-server/src/main/java/com/vaadin/v7/ui/DateField.java
+++ b/compatibility-server/src/main/java/com/vaadin/v7/ui/DateField.java
@@ -18,6 +18,7 @@ package com.vaadin.v7.ui;
import java.text.SimpleDateFormat;
import java.util.Calendar;
+import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
@@ -71,7 +72,7 @@ public class DateField extends AbstractField<Date> implements
/**
* Resolution identifier: seconds.
- *
+ *
* @deprecated As of 7.0, use {@link Resolution#SECOND}
*/
@Deprecated
@@ -600,6 +601,16 @@ public class DateField extends AbstractField<Date> implements
fireValueChange(false);
}
+ /*
+ * Because of our custom implementation of isValid(), that
+ * also checks the parsingSucceeded flag, we must also
+ * notify the form (if this is used in one) that the
+ * validity of this field has changed.
+ *
+ * Normally fields validity doesn't change without value
+ * change and form depends on this implementation detail.
+ */
+ notifyFormOfValidityChange();
markAsDirty();
}
} else if (newDate != oldDate
@@ -693,6 +704,38 @@ public class DateField extends AbstractField<Date> implements
super.setValue(newValue, repaintIsNotNeeded);
}
+ /**
+ * Detects if this field is used in a Form (logically) and if so, notifies
+ * it (by repainting it) that the validity of this field might have changed.
+ */
+ private void notifyFormOfValidityChange() {
+ Component parenOfDateField = getParent();
+ boolean formFound = false;
+ while (parenOfDateField != null || formFound) {
+ if (parenOfDateField instanceof Form) {
+ Form f = (Form) parenOfDateField;
+ Collection<?> visibleItemProperties = f.getItemPropertyIds();
+ for (Object fieldId : visibleItemProperties) {
+ Field<?> field = f.getField(fieldId);
+ if (equals(field)) {
+ /*
+ * this datefield is logically in a form. Do the same
+ * thing as form does in its value change listener that
+ * it registers to all fields.
+ */
+ f.markAsDirty();
+ formFound = true;
+ break;
+ }
+ }
+ }
+ if (formFound) {
+ break;
+ }
+ parenOfDateField = parenOfDateField.getParent();
+ }
+ }
+
@Override
protected void setInternalValue(Date newValue) {
// Also set the internal dateString
diff --git a/compatibility-server/src/main/java/com/vaadin/v7/ui/DefaultFieldFactory.java b/compatibility-server/src/main/java/com/vaadin/v7/ui/DefaultFieldFactory.java
index 53035ba087..da6441f48f 100644
--- a/compatibility-server/src/main/java/com/vaadin/v7/ui/DefaultFieldFactory.java
+++ b/compatibility-server/src/main/java/com/vaadin/v7/ui/DefaultFieldFactory.java
@@ -1,12 +1,12 @@
/*
* Copyright 2000-2016 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
@@ -15,31 +15,32 @@
*/
package com.vaadin.v7.ui;
-import java.text.Normalizer.Form;
import java.util.Date;
import com.vaadin.shared.util.SharedUtil;
import com.vaadin.ui.Component;
import com.vaadin.v7.data.Container;
+import com.vaadin.v7.data.Item;
import com.vaadin.v7.data.Property;
/**
- * This class contains a basic implementation for {@link TableFieldFactory}. The
- * class is singleton, use {@link #get()} method to get reference to the
- * instance.
- *
+ * This class contains a basic implementation for both {@link FormFieldFactory}
+ * and {@link TableFieldFactory}. The class is singleton, use {@link #get()}
+ * method to get reference to the instance.
+ *
* <p>
* There are also some static helper methods available for custom built field
* factories.
- *
+ *
*/
-public class DefaultFieldFactory implements TableFieldFactory {
+public class DefaultFieldFactory
+ implements FormFieldFactory, TableFieldFactory {
private static final DefaultFieldFactory instance = new DefaultFieldFactory();
/**
* Singleton method to get an instance of DefaultFieldFactory.
- *
+ *
* @return an instance of DefaultFieldFactory
*/
public static DefaultFieldFactory get() {
@@ -50,6 +51,15 @@ public class DefaultFieldFactory implements TableFieldFactory {
}
@Override
+ public Field<?> createField(Item item, Object propertyId,
+ Component uiContext) {
+ Class<?> type = item.getItemProperty(propertyId).getType();
+ Field<?> field = createFieldByPropertyType(type);
+ field.setCaption(createCaptionByPropertyId(propertyId));
+ return field;
+ }
+
+ @Override
public Field createField(Container container, Object itemId,
Object propertyId, Component uiContext) {
Property containerProperty = container.getContainerProperty(itemId,
@@ -63,7 +73,7 @@ public class DefaultFieldFactory implements TableFieldFactory {
/**
* If name follows method naming conventions, convert the name to spaced
* upper case text. For example, convert "firstName" to "First Name"
- *
+ *
* @param propertyId
* @return the formatted caption string
*/
@@ -74,18 +84,18 @@ public class DefaultFieldFactory implements TableFieldFactory {
/**
* Creates fields based on the property type.
* <p>
- * The default field type is {@link TextField}. Other field types generated
- * by this method:
+ * The default field type is {@link LegacyTextField}. Other field types
+ * generated by this method:
* <p>
* <b>Boolean</b>: {@link CheckBox}.<br/>
- * <b>Date</b>: {@link DateField}(resolution: day).<br/>
+ * <b>Date</b>: {@link LegacyDateField}(resolution: day).<br/>
* <b>Item</b>: {@link Form}. <br/>
- * <b>default field type</b>: {@link TextField}.
+ * <b>default field type</b>: {@link LegacyTextField}.
* <p>
- *
+ *
* @param type
* the type of the property
- * @return the most suitable generic {@link Field} for given type
+ * @return the most suitable generic {@link LegacyField} for given type
*/
public static Field<?> createFieldByPropertyType(Class<?> type) {
// Null typed properties can not be edited
@@ -93,6 +103,11 @@ public class DefaultFieldFactory implements TableFieldFactory {
return null;
}
+ // Item field
+ if (com.vaadin.v7.data.Item.class.isAssignableFrom(type)) {
+ return new Form();
+ }
+
// Date field
if (Date.class.isAssignableFrom(type)) {
final DateField df = new DateField();
diff --git a/compatibility-server/src/main/java/com/vaadin/v7/ui/Form.java b/compatibility-server/src/main/java/com/vaadin/v7/ui/Form.java
new file mode 100644
index 0000000000..e9bdf8771e
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/v7/ui/Form.java
@@ -0,0 +1,1421 @@
+/*
+ * Copyright 2000-2016 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.v7.ui;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Map;
+
+import com.vaadin.event.Action;
+import com.vaadin.event.Action.Handler;
+import com.vaadin.event.Action.ShortcutNotifier;
+import com.vaadin.event.ActionManager;
+import com.vaadin.server.AbstractErrorMessage;
+import com.vaadin.server.CompositeErrorMessage;
+import com.vaadin.server.ErrorMessage;
+import com.vaadin.server.PaintException;
+import com.vaadin.server.PaintTarget;
+import com.vaadin.server.UserError;
+import com.vaadin.ui.AbstractComponent;
+import com.vaadin.ui.Component;
+import com.vaadin.ui.ComponentContainer;
+import com.vaadin.ui.CustomLayout;
+import com.vaadin.ui.FormLayout;
+import com.vaadin.ui.GridLayout;
+import com.vaadin.ui.HasComponents;
+import com.vaadin.ui.HorizontalLayout;
+import com.vaadin.ui.Layout;
+import com.vaadin.ui.LegacyComponent;
+import com.vaadin.v7.data.Buffered;
+import com.vaadin.v7.data.Item;
+import com.vaadin.v7.data.Property;
+import com.vaadin.v7.data.Validatable;
+import com.vaadin.v7.data.Validator;
+import com.vaadin.v7.data.fieldgroup.FieldGroup;
+import com.vaadin.v7.data.util.BeanItem;
+import com.vaadin.v7.shared.form.FormState;
+
+/**
+ * Form component provides easy way of creating and managing sets fields.
+ *
+ * <p>
+ * <code>Form</code> is a container for fields implementing {@link Field}
+ * interface. It provides support for any layouts and provides buffering
+ * interface for easy connection of commit and discard buttons. All the form
+ * fields can be customized by adding validators, setting captions and icons,
+ * setting immediateness, etc. Also direct mechanism for replacing existing
+ * fields with selections is given.
+ * </p>
+ *
+ * <p>
+ * <code>Form</code> provides customizable editor for classes implementing
+ * {@link com.vaadin.data.Item} interface. Also the form itself implements this
+ * interface for easier connectivity to other items. To use the form as editor
+ * for an item, just connect the item to form with
+ * {@link Form#setItemDataSource(Item)}. If only a part of the item needs to be
+ * edited, {@link Form#setItemDataSource(Item,Collection)} can be used instead.
+ * After the item has been connected to the form, the automatically created
+ * fields can be customized and new fields can be added. If you need to connect
+ * a class that does not implement {@link com.vaadin.data.Item} interface, most
+ * properties of any class following bean pattern, can be accessed trough
+ * {@link com.vaadin.data.util.BeanItem}.
+ * </p>
+ *
+ * @author Vaadin Ltd.
+ * @since 3.0
+ * @deprecated As of 7.0, use {@link FieldGroup} instead of {@link Form} for
+ * more flexibility.
+ */
+@Deprecated
+public class Form extends AbstractField<Object>
+ implements Item.Editor, com.vaadin.v7.data.Buffered, Item, Validatable,
+ Action.Notifier, HasComponents, LegacyComponent {
+
+ private Object propertyValue;
+
+ /**
+ * Item connected to this form as datasource.
+ */
+ private Item itemDatasource;
+
+ /**
+ * Ordered list of property ids in this editor.
+ */
+ private final LinkedList<Object> propertyIds = new LinkedList<>();
+
+ /**
+ * Current buffered source exception.
+ */
+ private Buffered.SourceException currentBufferedSourceException = null;
+
+ /**
+ * Is the form in buffered mode.
+ */
+ private boolean buffered = false;
+
+ /**
+ * Mapping from propertyName to corresponding field.
+ */
+ private final HashMap<Object, Field<?>> fields = new HashMap<>();
+
+ /**
+ * Form may act as an Item, its own properties are stored here.
+ */
+ private final HashMap<Object, Property<?>> ownProperties = new HashMap<>();
+
+ /**
+ * Field factory for this form.
+ */
+ private FormFieldFactory fieldFactory;
+
+ /**
+ * Visible item properties.
+ */
+ private Collection<?> visibleItemProperties;
+
+ /**
+ * Form needs to repaint itself if child fields value changes due possible
+ * change in form validity.
+ *
+ * TODO introduce ValidityChangeEvent (#6239) and start using it instead.
+ * See e.g. DateField#notifyFormOfValidityChange().
+ */
+ private final ValueChangeListener fieldValueChangeListener = new ValueChangeListener() {
+ @Override
+ public void valueChange(Property.ValueChangeEvent event) {
+ markAsDirty();
+ }
+ };
+
+ /**
+ * If this is true, commit implicitly calls setValidationVisible(true).
+ */
+ private boolean validationVisibleOnCommit = true;
+
+ // special handling for gridlayout; remember initial cursor pos
+ private int gridlayoutCursorX = -1;
+ private int gridlayoutCursorY = -1;
+
+ /**
+ * Keeps track of the Actions added to this component, and manages the
+ * painting and handling as well. Note that the extended AbstractField is a
+ * {@link ShortcutNotifier} and has a actionManager that delegates actions
+ * to the containing window. This one does not delegate.
+ */
+ private ActionManager ownActionManager = new ActionManager(this);
+
+ /**
+ * Constructs a new form with default layout.
+ *
+ * <p>
+ * By default the form uses {@link FormLayout}.
+ * </p>
+ */
+ public Form() {
+ this(null);
+ setValidationVisible(false);
+ }
+
+ /**
+ * Constructs a new form with given {@link Layout}.
+ *
+ * @param formLayout
+ * the layout of the form.
+ */
+ public Form(Layout formLayout) {
+ this(formLayout, DefaultFieldFactory.get());
+ }
+
+ /**
+ * Constructs a new form with given {@link Layout} and
+ * {@link FormFieldFactory}.
+ *
+ * @param formLayout
+ * the layout of the form.
+ * @param fieldFactory
+ * the TableFieldFactory of the form.
+ */
+ public Form(Layout formLayout, FormFieldFactory fieldFactory) {
+ super();
+ setLayout(formLayout);
+ setFooter(new HorizontalLayout());
+ setFormFieldFactory(fieldFactory);
+ setValidationVisible(false);
+ setWidth(100, UNITS_PERCENTAGE);
+ }
+
+ @Override
+ protected FormState getState() {
+ return (FormState) super.getState();
+ }
+
+ @Override
+ protected FormState getState(boolean markAsDirty) {
+ return (FormState) super.getState(markAsDirty);
+ }
+
+ /* Documented in interface */
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+ if (ownActionManager != null) {
+ ownActionManager.paintActions(null, target);
+ }
+ }
+
+ @Override
+ public void changeVariables(Object source, Map<String, Object> variables) {
+ // Actions
+ if (ownActionManager != null) {
+ ownActionManager.handleActions(variables, this);
+ }
+ }
+
+ /**
+ * The error message of a Form is the error of the first field with a
+ * non-empty error.
+ *
+ * Empty error messages of the contained fields are skipped, because an
+ * empty error indicator would be confusing to the user, especially if there
+ * are errors that have something to display. This is also the reason why
+ * the calculation of the error message is separate from validation, because
+ * validation fails also on empty errors.
+ */
+ @Override
+ public ErrorMessage getErrorMessage() {
+
+ // Reimplement the checking of validation error by using
+ // getErrorMessage() recursively instead of validate().
+ ErrorMessage validationError = null;
+ if (isValidationVisible()) {
+ for (final Iterator<Object> i = propertyIds.iterator(); i
+ .hasNext();) {
+ Object f = fields.get(i.next());
+ if (f instanceof AbstractComponent) {
+ AbstractComponent field = (AbstractComponent) f;
+
+ validationError = field.getErrorMessage();
+ if (validationError != null) {
+ // Show caption as error for fields with empty errors
+ if ("".equals(validationError.toString())) {
+ validationError = new UserError(field.getCaption());
+ }
+ break;
+ } else if (f instanceof Field
+ && !((Field<?>) f).isValid()) {
+ // Something is wrong with the field, but no proper
+ // error is given. Generate one.
+ validationError = new UserError(field.getCaption());
+ break;
+ }
+ }
+ }
+ }
+
+ // Return if there are no errors at all
+ if (getComponentError() == null && validationError == null
+ && currentBufferedSourceException == null) {
+ return null;
+ }
+
+ // Throw combination of the error types
+ return new CompositeErrorMessage(
+ new ErrorMessage[] { getComponentError(), validationError,
+ AbstractErrorMessage.getErrorMessageForException(
+ currentBufferedSourceException) });
+ }
+
+ /**
+ * Controls the making validation visible implicitly on commit.
+ *
+ * Having commit() call setValidationVisible(true) implicitly is the default
+ * behaviour. You can disable the implicit setting by setting this property
+ * as false.
+ *
+ * It is useful, because you usually want to start with the form free of
+ * errors and only display them after the user clicks Ok. You can disable
+ * the implicit setting by setting this property as false.
+ *
+ * @param makeVisible
+ * If true (default), validation is made visible when commit() is
+ * called. If false, the visibility is left as it is.
+ */
+ public void setValidationVisibleOnCommit(boolean makeVisible) {
+ validationVisibleOnCommit = makeVisible;
+ }
+
+ /**
+ * Is validation made automatically visible on commit?
+ *
+ * See setValidationVisibleOnCommit().
+ *
+ * @return true if validation is made automatically visible on commit.
+ */
+ public boolean isValidationVisibleOnCommit() {
+ return validationVisibleOnCommit;
+ }
+
+ /*
+ * Commit changes to the data source Don't add a JavaDoc comment here, we
+ * use the default one from the interface.
+ */
+ @Override
+ public void commit()
+ throws Buffered.SourceException, Validator.InvalidValueException {
+
+ LinkedList<SourceException> problems = null;
+
+ // Only commit on valid state if so requested
+ if (!isInvalidCommitted() && !isValid()) {
+ /*
+ * The values are not ok and we are told not to commit invalid
+ * values
+ */
+ if (validationVisibleOnCommit) {
+ setValidationVisible(true);
+ }
+
+ // Find the first invalid value and throw the exception
+ validate();
+ }
+
+ // Try to commit all
+ for (final Iterator<Object> i = propertyIds.iterator(); i.hasNext();) {
+ try {
+ final Field<?> f = (fields.get(i.next()));
+ // Commit only non-readonly fields.
+ if (!f.isReadOnly()) {
+ f.commit();
+ }
+ } catch (final Buffered.SourceException e) {
+ if (problems == null) {
+ problems = new LinkedList<>();
+ }
+ problems.add(e);
+ }
+ }
+
+ // No problems occurred
+ if (problems == null) {
+ if (currentBufferedSourceException != null) {
+ currentBufferedSourceException = null;
+ markAsDirty();
+ }
+ return;
+ }
+
+ // Commit problems
+ final Throwable[] causes = new Throwable[problems.size()];
+ int index = 0;
+ for (final Iterator<SourceException> i = problems.iterator(); i
+ .hasNext();) {
+ causes[index++] = i.next();
+ }
+ final Buffered.SourceException e = new Buffered.SourceException(this,
+ causes);
+ currentBufferedSourceException = e;
+ markAsDirty();
+ throw e;
+ }
+
+ /*
+ * Discards local changes and refresh values from the data source Don't add
+ * a JavaDoc comment here, we use the default one from the interface.
+ */
+ @Override
+ public void discard() throws Buffered.SourceException {
+
+ LinkedList<SourceException> problems = null;
+
+ // Try to discard all changes
+ for (final Iterator<Object> i = propertyIds.iterator(); i.hasNext();) {
+ try {
+ (fields.get(i.next())).discard();
+ } catch (final Buffered.SourceException e) {
+ if (problems == null) {
+ problems = new LinkedList<>();
+ }
+ problems.add(e);
+ }
+ }
+
+ // No problems occurred
+ if (problems == null) {
+ if (currentBufferedSourceException != null) {
+ currentBufferedSourceException = null;
+ markAsDirty();
+ }
+ return;
+ }
+
+ // Discards problems occurred
+ final Throwable[] causes = new Throwable[problems.size()];
+ int index = 0;
+ for (final Iterator<SourceException> i = problems.iterator(); i
+ .hasNext();) {
+ causes[index++] = i.next();
+ }
+ final Buffered.SourceException e = new Buffered.SourceException(this,
+ causes);
+ currentBufferedSourceException = e;
+ markAsDirty();
+ throw e;
+ }
+
+ /*
+ * Is the object modified but not committed? Don't add a JavaDoc comment
+ * here, we use the default one from the interface.
+ */
+ @Override
+ public boolean isModified() {
+ for (final Iterator<Object> i = propertyIds.iterator(); i.hasNext();) {
+ final Field<?> f = fields.get(i.next());
+ if (f != null && f.isModified()) {
+ return true;
+ }
+
+ }
+ return false;
+ }
+
+ /*
+ * Sets the editor's buffered mode to the specified status. Don't add a
+ * JavaDoc comment here, we use the default one from the interface.
+ */
+ @Override
+ public void setBuffered(boolean buffered) {
+ if (buffered != this.buffered) {
+ this.buffered = buffered;
+ for (final Iterator<Object> i = propertyIds.iterator(); i
+ .hasNext();) {
+ (fields.get(i.next())).setBuffered(buffered);
+ }
+ }
+ }
+
+ /**
+ * Adds a new property to form and create corresponding field.
+ *
+ * @see com.vaadin.data.Item#addItemProperty(Object, Property)
+ */
+ @Override
+ public boolean addItemProperty(Object id, Property property) {
+
+ // Checks inputs
+ if (id == null || property == null) {
+ throw new NullPointerException("Id and property must be non-null");
+ }
+
+ // Checks that the property id is not reserved
+ if (propertyIds.contains(id)) {
+ return false;
+ }
+
+ propertyIds.add(id);
+ ownProperties.put(id, property);
+
+ // Gets suitable field
+ final Field<?> field = fieldFactory.createField(this, id, this);
+ if (field == null) {
+ return false;
+ }
+
+ // Configures the field
+ bindPropertyToField(id, property, field);
+
+ // Register and attach the created field
+ addField(id, field);
+
+ return true;
+ }
+
+ /**
+ * Registers the field with the form and adds the field to the form layout.
+ *
+ * <p>
+ * The property id must not be already used in the form.
+ * </p>
+ *
+ * <p>
+ * This field is added to the layout using the
+ * {@link #attachField(Object, Field)} method.
+ * </p>
+ *
+ * @param propertyId
+ * the Property id the the field.
+ * @param field
+ * the field which should be added to the form.
+ */
+ public void addField(Object propertyId, Field<?> field) {
+ registerField(propertyId, field);
+ attachField(propertyId, field);
+ markAsDirty();
+ }
+
+ /**
+ * Register the field with the form. All registered fields are validated
+ * when the form is validated and also committed when the form is committed.
+ *
+ * <p>
+ * The property id must not be already used in the form.
+ * </p>
+ *
+ *
+ * @param propertyId
+ * the Property id of the field.
+ * @param field
+ * the Field that should be registered
+ */
+ private void registerField(Object propertyId, Field<?> field) {
+ if (propertyId == null || field == null) {
+ return;
+ }
+
+ fields.put(propertyId, field);
+ field.addListener(fieldValueChangeListener);
+ if (!propertyIds.contains(propertyId)) {
+ // adding a field directly
+ propertyIds.addLast(propertyId);
+ }
+
+ // Update the buffered mode and immediate to match the
+ // form.
+ // Should this also include invalidCommitted (#3993)?
+ field.setBuffered(buffered);
+ if (isImmediate() && field instanceof AbstractComponent) {
+ ((AbstractComponent) field).setImmediate(true);
+ }
+ }
+
+ /**
+ * Adds the field to the form layout.
+ * <p>
+ * The field is added to the form layout in the default position (the
+ * position used by {@link Layout#addComponent(Component)}. If the
+ * underlying layout is a {@link CustomLayout} the field is added to the
+ * CustomLayout location given by the string representation of the property
+ * id using {@link CustomLayout#addComponent(Component, String)}.
+ * </p>
+ *
+ * <p>
+ * Override this method to control how the fields are added to the layout.
+ * </p>
+ *
+ * @param propertyId
+ * @param field
+ */
+ protected void attachField(Object propertyId, Field field) {
+ if (propertyId == null || field == null) {
+ return;
+ }
+
+ Layout layout = getLayout();
+ if (layout instanceof CustomLayout) {
+ ((CustomLayout) layout).addComponent(field, propertyId.toString());
+ } else {
+ layout.addComponent(field);
+ }
+
+ }
+
+ /**
+ * The property identified by the property id.
+ *
+ * <p>
+ * The property data source of the field specified with property id is
+ * returned. If there is a (with specified property id) having no data
+ * source, the field is returned instead of the data source.
+ * </p>
+ *
+ * @see com.vaadin.data.Item#getItemProperty(Object)
+ */
+ @Override
+ public Property getItemProperty(Object id) {
+ final Field<?> field = fields.get(id);
+ if (field == null) {
+ // field does not exist or it is not (yet) created for this property
+ return ownProperties.get(id);
+ }
+ final Property<?> property = field.getPropertyDataSource();
+
+ if (property != null) {
+ return property;
+ } else {
+ return field;
+ }
+ }
+
+ /**
+ * Gets the field identified by the propertyid.
+ *
+ * @param propertyId
+ * the id of the property.
+ */
+ public Field getField(Object propertyId) {
+ return fields.get(propertyId);
+ }
+
+ /* Documented in interface */
+ @Override
+ public Collection<?> getItemPropertyIds() {
+ return Collections.unmodifiableCollection(propertyIds);
+ }
+
+ /**
+ * Removes the property and corresponding field from the form.
+ *
+ * @see com.vaadin.data.Item#removeItemProperty(Object)
+ */
+ @Override
+ public boolean removeItemProperty(Object id) {
+ ownProperties.remove(id);
+
+ final Field<?> field = fields.get(id);
+
+ if (field != null) {
+ propertyIds.remove(id);
+ fields.remove(id);
+ detachField(field);
+ field.removeListener(fieldValueChangeListener);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Called when a form field is detached from a Form. Typically when a new
+ * Item is assigned to Form via {@link #setItemDataSource(Item)}.
+ * <p>
+ * Override this method to control how the fields are removed from the
+ * layout.
+ * </p>
+ *
+ * @param field
+ * the field to be detached from the forms layout.
+ */
+ protected void detachField(final Field field) {
+ Component p = field.getParent();
+ if (p instanceof ComponentContainer) {
+ ((ComponentContainer) p).removeComponent(field);
+ }
+ }
+
+ /**
+ * Removes all properties and fields from the form.
+ *
+ * @return the Success of the operation. Removal of all fields succeeded if
+ * (and only if) the return value is <code>true</code>.
+ */
+ public boolean removeAllProperties() {
+ final Object[] properties = propertyIds.toArray();
+ boolean success = true;
+
+ for (int i = 0; i < properties.length; i++) {
+ if (!removeItemProperty(properties[i])) {
+ success = false;
+ }
+ }
+
+ return success;
+ }
+
+ /* Documented in the interface */
+ @Override
+ public Item getItemDataSource() {
+ return itemDatasource;
+ }
+
+ /**
+ * Sets the item datasource for the form.
+ *
+ * <p>
+ * Setting item datasource clears any fields, the form might contain and
+ * adds all the properties as fields to the form.
+ * </p>
+ *
+ * @see com.vaadin.data.Item.Viewer#setItemDataSource(Item)
+ */
+ @Override
+ public void setItemDataSource(Item newDataSource) {
+ setItemDataSource(newDataSource, newDataSource != null
+ ? newDataSource.getItemPropertyIds() : null);
+ }
+
+ /**
+ * Set the item datasource for the form, but limit the form contents to
+ * specified properties of the item.
+ *
+ * <p>
+ * Setting item datasource clears any fields, the form might contain and
+ * adds the specified the properties as fields to the form, in the specified
+ * order.
+ * </p>
+ *
+ * @see com.vaadin.data.Item.Viewer#setItemDataSource(Item)
+ */
+ public void setItemDataSource(Item newDataSource,
+ Collection<?> propertyIds) {
+
+ if (getLayout() instanceof GridLayout) {
+ GridLayout gl = (GridLayout) getLayout();
+ if (gridlayoutCursorX == -1) {
+ // first setItemDataSource, remember initial cursor
+ gridlayoutCursorX = gl.getCursorX();
+ gridlayoutCursorY = gl.getCursorY();
+ } else {
+ // restore initial cursor
+ gl.setCursorX(gridlayoutCursorX);
+ gl.setCursorY(gridlayoutCursorY);
+ }
+ }
+
+ // Removes all fields first from the form
+ removeAllProperties();
+
+ // Sets the datasource
+ itemDatasource = newDataSource;
+
+ // If the new datasource is null, just set null datasource
+ if (itemDatasource == null) {
+ markAsDirty();
+ return;
+ }
+
+ // Adds all the properties to this form
+ for (final Iterator<?> i = propertyIds.iterator(); i.hasNext();) {
+ final Object id = i.next();
+ final Property<?> property = itemDatasource.getItemProperty(id);
+ if (id != null && property != null) {
+ final Field<?> f = fieldFactory.createField(itemDatasource, id,
+ this);
+ if (f != null) {
+ bindPropertyToField(id, property, f);
+ addField(id, f);
+ }
+ }
+ }
+ }
+
+ /**
+ * Binds an item property to a field. The default behavior is to bind
+ * property straight to Field. If Property.Viewer type property (e.g.
+ * PropertyFormatter) is already set for field, the property is bound to
+ * that Property.Viewer.
+ *
+ * @param propertyId
+ * @param property
+ * @param field
+ * @since 6.7.3
+ */
+ protected void bindPropertyToField(final Object propertyId,
+ final Property property, final Field field) {
+ // check if field has a property that is Viewer set. In that case we
+ // expect developer has e.g. PropertyFormatter that he wishes to use and
+ // assign the property to the Viewer instead.
+ boolean hasFilterProperty = field.getPropertyDataSource() != null
+ && (field.getPropertyDataSource() instanceof Property.Viewer);
+ if (hasFilterProperty) {
+ ((Property.Viewer) field.getPropertyDataSource())
+ .setPropertyDataSource(property);
+ } else {
+ field.setPropertyDataSource(property);
+ }
+ }
+
+ /**
+ * Gets the layout of the form.
+ *
+ * <p>
+ * By default form uses <code>OrderedLayout</code> with <code>form</code>
+ * -style.
+ * </p>
+ *
+ * @return the Layout of the form.
+ */
+ public Layout getLayout() {
+ return (Layout) getState(false).layout;
+ }
+
+ /**
+ * Sets the layout of the form.
+ *
+ * <p>
+ * If set to null then Form uses a FormLayout by default.
+ * </p>
+ *
+ * @param layout
+ * the layout of the form.
+ */
+ public void setLayout(Layout layout) {
+
+ // Use orderedlayout by default
+ if (layout == null) {
+ layout = new FormLayout();
+ }
+
+ // reset cursor memory
+ gridlayoutCursorX = -1;
+ gridlayoutCursorY = -1;
+
+ // Move fields from previous layout
+ if (getLayout() != null) {
+ final Object[] properties = propertyIds.toArray();
+ for (int i = 0; i < properties.length; i++) {
+ Field<?> f = getField(properties[i]);
+ detachField(f);
+ if (layout instanceof CustomLayout) {
+ ((CustomLayout) layout).addComponent(f,
+ properties[i].toString());
+ } else {
+ layout.addComponent(f);
+ }
+ }
+
+ getLayout().setParent(null);
+ }
+
+ // Replace the previous layout
+ layout.setParent(this);
+ getState().layout = layout;
+ }
+
+ /**
+ * Sets the form field to be selectable from static list of changes.
+ *
+ * <p>
+ * The list values and descriptions are given as array. The value-array must
+ * contain the current value of the field and the lengths of the arrays must
+ * match. Null values are not supported.
+ * </p>
+ *
+ * Note: since Vaadin 7.0, returns an {@link AbstractSelect} instead of a
+ * {@link Select}.
+ *
+ * @param propertyId
+ * the id of the property.
+ * @param values
+ * @param descriptions
+ * @return the select property generated
+ */
+ public AbstractSelect replaceWithSelect(Object propertyId, Object[] values,
+ Object[] descriptions) {
+
+ // Checks the parameters
+ if (propertyId == null || values == null || descriptions == null) {
+ throw new NullPointerException("All parameters must be non-null");
+ }
+ if (values.length != descriptions.length) {
+ throw new IllegalArgumentException(
+ "Value and description list are of different size");
+ }
+
+ // Gets the old field
+ final Field<?> oldField = fields.get(propertyId);
+ if (oldField == null) {
+ throw new IllegalArgumentException("Field with given propertyid '"
+ + propertyId.toString() + "' can not be found.");
+ }
+ final Object value = oldField.getPropertyDataSource() == null
+ ? oldField.getValue()
+ : oldField.getPropertyDataSource().getValue();
+
+ // Checks that the value exists and check if the select should
+ // be forced in multiselect mode
+ boolean found = false;
+ boolean isMultiselect = false;
+ for (int i = 0; i < values.length && !found; i++) {
+ if (values[i] == value
+ || (value != null && value.equals(values[i]))) {
+ found = true;
+ }
+ }
+ if (value != null && !found) {
+ if (value instanceof Collection) {
+ for (final Iterator<?> it = ((Collection<?>) value)
+ .iterator(); it.hasNext();) {
+ final Object val = it.next();
+ found = false;
+ for (int i = 0; i < values.length && !found; i++) {
+ if (values[i] == val
+ || (val != null && val.equals(values[i]))) {
+ found = true;
+ }
+ }
+ if (!found) {
+ throw new IllegalArgumentException(
+ "Currently selected value '" + val
+ + "' of property '"
+ + propertyId.toString()
+ + "' was not found");
+ }
+ }
+ isMultiselect = true;
+ } else {
+ throw new IllegalArgumentException(
+ "Current value '" + value + "' of property '"
+ + propertyId.toString() + "' was not found");
+ }
+ }
+
+ // Creates the new field matching to old field parameters
+ final AbstractSelect newField = isMultiselect ? new ListSelect()
+ : new Select();
+ newField.setCaption(oldField.getCaption());
+ newField.setReadOnly(oldField.isReadOnly());
+ newField.setBuffered(oldField.isBuffered());
+
+ // Creates the options list
+ newField.addContainerProperty("desc", String.class, "");
+ newField.setItemCaptionPropertyId("desc");
+ for (int i = 0; i < values.length; i++) {
+ Object id = values[i];
+ final Item item;
+ if (id == null) {
+ id = newField.addItem();
+ item = newField.getItem(id);
+ newField.setNullSelectionItemId(id);
+ } else {
+ item = newField.addItem(id);
+ }
+
+ if (item != null) {
+ item.getItemProperty("desc")
+ .setValue(descriptions[i].toString());
+ }
+ }
+
+ // Sets the property data source
+ final Property<?> property = oldField.getPropertyDataSource();
+ oldField.setPropertyDataSource(null);
+ newField.setPropertyDataSource(property);
+
+ // Replaces the old field with new one
+ getLayout().replaceComponent(oldField, newField);
+ fields.put(propertyId, newField);
+ newField.addListener(fieldValueChangeListener);
+ oldField.removeListener(fieldValueChangeListener);
+
+ return newField;
+ }
+
+ /**
+ * Checks the validity of the Form and all of its fields.
+ *
+ * @see com.vaadin.legacy.data.Validatable#validate()
+ */
+ @Override
+ public void validate() throws Validator.InvalidValueException {
+ super.validate();
+ for (final Iterator<Object> i = propertyIds.iterator(); i.hasNext();) {
+ (fields.get(i.next())).validate();
+ }
+ }
+
+ /**
+ * Checks the validabtable object accept invalid values.
+ *
+ * @see com.vaadin.legacy.data.Validatable#isInvalidAllowed()
+ */
+ @Override
+ public boolean isInvalidAllowed() {
+ return true;
+ }
+
+ /**
+ * Should the validabtable object accept invalid values.
+ *
+ * @see com.vaadin.legacy.data.Validatable#setInvalidAllowed(boolean)
+ */
+ @Override
+ public void setInvalidAllowed(boolean invalidValueAllowed)
+ throws UnsupportedOperationException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Sets the component's to read-only mode to the specified state.
+ *
+ * @see com.vaadin.ui.Component#setReadOnly(boolean)
+ */
+ @Override
+ public void setReadOnly(boolean readOnly) {
+ super.setReadOnly(readOnly);
+ for (final Iterator<?> i = propertyIds.iterator(); i.hasNext();) {
+ (fields.get(i.next())).setReadOnly(readOnly);
+ }
+ }
+
+ /**
+ * Sets the field factory used by this Form to genarate Fields for
+ * properties.
+ *
+ * {@link FormFieldFactory} is used to create fields for form properties.
+ * {@link DefaultFieldFactory} is used by default.
+ *
+ * @param fieldFactory
+ * the new factory used to create the fields.
+ * @see Field
+ * @see FormFieldFactory
+ */
+ public void setFormFieldFactory(FormFieldFactory fieldFactory) {
+ this.fieldFactory = fieldFactory;
+ }
+
+ /**
+ * Get the field factory of the form.
+ *
+ * @return the FormFieldFactory Factory used to create the fields.
+ */
+ public FormFieldFactory getFormFieldFactory() {
+ return fieldFactory;
+ }
+
+ /**
+ * Gets the field type.
+ *
+ * @see com.vaadin.legacy.ui.AbstractField#getType()
+ */
+ @Override
+ public Class<?> getType() {
+ if (getPropertyDataSource() != null) {
+ return getPropertyDataSource().getType();
+ }
+ return Object.class;
+ }
+
+ /**
+ * Sets the internal value.
+ *
+ * This is relevant when the Form is used as Field.
+ *
+ * @see com.vaadin.legacy.ui.AbstractField#setInternalValue(java.lang.Object)
+ */
+ @Override
+ protected void setInternalValue(Object newValue) {
+ // Stores the old value
+ final Object oldValue = propertyValue;
+
+ // Sets the current Value
+ super.setInternalValue(newValue);
+ propertyValue = newValue;
+
+ // Ignores form updating if data object has not changed.
+ if (oldValue != newValue) {
+ setFormDataSource(newValue, getVisibleItemProperties());
+ }
+ }
+
+ /**
+ * Gets the first focusable field in form. If there are enabled,
+ * non-read-only fields, the first one of them is returned. Otherwise, the
+ * field for the first property (or null if none) is returned.
+ *
+ * @return the Field.
+ */
+ private Field<?> getFirstFocusableField() {
+ Collection<?> itemPropertyIds = getItemPropertyIds();
+ if (itemPropertyIds != null && itemPropertyIds.size() > 0) {
+ for (Object id : itemPropertyIds) {
+ if (id != null) {
+ Field<?> field = getField(id);
+ if (field.isConnectorEnabled() && !field.isReadOnly()) {
+ return field;
+ }
+ }
+ }
+ // fallback: first field if none of the fields is enabled and
+ // writable
+ Object id = itemPropertyIds.iterator().next();
+ if (id != null) {
+ return getField(id);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Updates the internal form datasource.
+ *
+ * Method setFormDataSource.
+ *
+ * @param data
+ * @param properties
+ */
+ protected void setFormDataSource(Object data, Collection<?> properties) {
+
+ // If data is an item use it.
+ Item item = null;
+ if (data instanceof Item) {
+ item = (Item) data;
+ } else if (data != null) {
+ item = new BeanItem<>(data);
+ }
+
+ // Sets the datasource to form
+ if (item != null && properties != null) {
+ // Shows only given properties
+ this.setItemDataSource(item, properties);
+ } else {
+ // Shows all properties
+ this.setItemDataSource(item);
+ }
+ }
+
+ /**
+ * Returns the visibleProperties.
+ *
+ * @return the Collection of visible Item properites.
+ */
+ public Collection<?> getVisibleItemProperties() {
+ return visibleItemProperties;
+ }
+
+ /**
+ * Sets the visibleProperties.
+ *
+ * @param visibleProperties
+ * the visibleProperties to set.
+ */
+ public void setVisibleItemProperties(Collection<?> visibleProperties) {
+ visibleItemProperties = visibleProperties;
+ Object value = getValue();
+ if (value == null) {
+ value = itemDatasource;
+ }
+ setFormDataSource(value, getVisibleItemProperties());
+ }
+
+ /**
+ * Sets the visibleProperties.
+ *
+ * @param visibleProperties
+ * the visibleProperties to set.
+ */
+ public void setVisibleItemProperties(Object... visibleProperties) {
+ LinkedList<Object> v = new LinkedList<>();
+ for (int i = 0; i < visibleProperties.length; i++) {
+ v.add(visibleProperties[i]);
+ }
+ setVisibleItemProperties(v);
+ }
+
+ /**
+ * Focuses the first field in the form.
+ *
+ * @see com.vaadin.ui.Component.Focusable#focus()
+ */
+ @Override
+ public void focus() {
+ final Field<?> f = getFirstFocusableField();
+ if (f != null) {
+ f.focus();
+ }
+ }
+
+ /**
+ * Sets the Tabulator index of this Focusable component.
+ *
+ * @see com.vaadin.ui.Component.Focusable#setTabIndex(int)
+ */
+ @Override
+ public void setTabIndex(int tabIndex) {
+ super.setTabIndex(tabIndex);
+ for (final Iterator<?> i = getItemPropertyIds().iterator(); i
+ .hasNext();) {
+ (getField(i.next())).setTabIndex(tabIndex);
+ }
+ }
+
+ /**
+ * Setting the form to be immediate also sets all the fields of the form to
+ * the same state.
+ */
+ @Override
+ public void setImmediate(boolean immediate) {
+ super.setImmediate(immediate);
+ for (Iterator<Field<?>> i = fields.values().iterator(); i.hasNext();) {
+ Field<?> f = i.next();
+ if (f instanceof AbstractComponent) {
+ ((AbstractComponent) f).setImmediate(immediate);
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * A Form is empty if all of its fields are empty.
+ *
+ */
+ @Override
+ public boolean isEmpty() {
+
+ for (Iterator<Field<?>> i = fields.values().iterator(); i.hasNext();) {
+ Field<?> f = i.next();
+ if (f instanceof AbstractField) {
+ if (!((AbstractField<?>) f).isEmpty()) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.AbstractField#clear()
+ */
+ @Override
+ public void clear() {
+ for (Iterator<Field<?>> i = fields.values().iterator(); i.hasNext();) {
+ Field<?> f = i.next();
+ if (f instanceof AbstractField) {
+ ((AbstractField<?>) f).clear();
+ }
+ }
+ }
+
+ /**
+ * Adding validators directly to form is not supported.
+ *
+ * Add the validators to form fields instead.
+ */
+ @Override
+ public void addValidator(Validator validator) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Returns a layout that is rendered below normal form contents. This area
+ * can be used for example to include buttons related to form contents.
+ *
+ * @return layout rendered below normal form contents or null if no footer
+ * is used
+ */
+ public Layout getFooter() {
+ return (Layout) getState(false).footer;
+ }
+
+ /**
+ * Sets the layout that is rendered below normal form contents. No footer is
+ * rendered if this is set to null, .
+ *
+ * @param footer
+ * the new footer layout
+ */
+ public void setFooter(Layout footer) {
+ if (getFooter() != null) {
+ getFooter().setParent(null);
+ }
+ getState().footer = footer;
+ if (footer != null) {
+ footer.setParent(this);
+ }
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+ if (getParent() != null && !getParent().isEnabled()) {
+ // some ancestor still disabled, don't update children
+ return;
+ } else {
+ getLayout().markAsDirtyRecursive();
+ }
+ }
+
+ /*
+ * ACTIONS
+ */
+
+ /**
+ * Gets the {@link ActionManager} responsible for handling {@link Action}s
+ * added to this Form.<br/>
+ * Note that Form has another ActionManager inherited from
+ * {@link AbstractField}. The ownActionManager handles Actions attached to
+ * this Form specifically, while the ActionManager in AbstractField
+ * delegates to the containing Window (i.e global Actions).
+ *
+ * @return
+ */
+ protected ActionManager getOwnActionManager() {
+ if (ownActionManager == null) {
+ ownActionManager = new ActionManager(this);
+ }
+ return ownActionManager;
+ }
+
+ @Override
+ public void addActionHandler(Handler actionHandler) {
+ getOwnActionManager().addActionHandler(actionHandler);
+ }
+
+ @Override
+ public void removeActionHandler(Handler actionHandler) {
+ if (ownActionManager != null) {
+ ownActionManager.removeActionHandler(actionHandler);
+ }
+ }
+
+ /**
+ * Removes all action handlers
+ */
+ public void removeAllActionHandlers() {
+ if (ownActionManager != null) {
+ ownActionManager.removeAllActionHandlers();
+ }
+ }
+
+ @Override
+ public <T extends Action & com.vaadin.event.Action.Listener> void addAction(
+ T action) {
+ getOwnActionManager().addAction(action);
+ }
+
+ @Override
+ public <T extends Action & com.vaadin.event.Action.Listener> void removeAction(
+ T action) {
+ if (ownActionManager != null) {
+ ownActionManager.removeAction(action);
+ }
+ }
+
+ @Override
+ public Iterator<Component> iterator() {
+ return new ComponentIterator();
+ }
+
+ /**
+ * Modifiable and Serializable Iterator for the components, used by
+ * {@link Form#getComponentIterator()}.
+ */
+ private class ComponentIterator
+ implements Iterator<Component>, Serializable {
+
+ int i = 0;
+
+ @Override
+ public boolean hasNext() {
+ if (i < getComponentCount()) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public Component next() {
+ if (!hasNext()) {
+ return null;
+ }
+ i++;
+ if (i == 1) {
+ if (getLayout() != null) {
+ return getLayout();
+ }
+ if (getFooter() != null) {
+ return getFooter();
+ }
+ } else if (i == 2) {
+ if (getFooter() != null) {
+ return getFooter();
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void remove() {
+ if (i == 1) {
+ if (getLayout() != null) {
+ setLayout(null);
+ i = 0;
+ } else {
+ setFooter(null);
+ }
+ } else if (i == 2) {
+ setFooter(null);
+ }
+ }
+ }
+
+ /**
+ * @deprecated As of 7.0, use {@link #iterator()} instead.
+ */
+ @Deprecated
+ public Iterator<Component> getComponentIterator() {
+ return iterator();
+ }
+
+ public int getComponentCount() {
+ int count = 0;
+ if (getLayout() != null) {
+ count++;
+ }
+ if (getFooter() != null) {
+ count++;
+ }
+
+ return count;
+ }
+
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/v7/ui/FormFieldFactory.java b/compatibility-server/src/main/java/com/vaadin/v7/ui/FormFieldFactory.java
new file mode 100644
index 0000000000..a2c963c0a4
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/v7/ui/FormFieldFactory.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2000-2016 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.v7.ui;
+
+import java.io.Serializable;
+
+import com.vaadin.ui.Component;
+import com.vaadin.v7.data.Item;
+import com.vaadin.v7.data.fieldgroup.FieldGroup;
+
+/**
+ * Factory interface for creating new Field-instances based on {@link Item},
+ * property id and uiContext (the component responsible for displaying fields).
+ * Currently this interface is used by {@link Form}, but might later be used by
+ * some other components for {@link Field} generation.
+ *
+ * <p>
+ *
+ * @author Vaadin Ltd.
+ * @since 6.0
+ * @see TableFieldFactory
+ * @deprecated As of 7.0, use {@link FieldGroup} instead of {@link Form} for
+ * more flexibility.
+ */
+@Deprecated
+public interface FormFieldFactory extends Serializable {
+ /**
+ * Creates a field based on the item, property id and the component (most
+ * commonly {@link Form}) where the Field will be presented.
+ *
+ * @param item
+ * the item where the property belongs to.
+ * @param propertyId
+ * the Id of the property.
+ * @param uiContext
+ * the component where the field is presented, most commonly this
+ * is {@link Form}. uiContext will not necessary be the parent
+ * component of the field, but the one that is responsible for
+ * creating it.
+ * @return the field suitable for editing the specified data.
+ */
+ Field<?> createField(Item item, Object propertyId, Component uiContext);
+}
diff --git a/compatibility-server/src/test/java/com/vaadin/v7/tests/server/SerializationTest.java b/compatibility-server/src/test/java/com/vaadin/v7/tests/server/SerializationTest.java
index dd0148fdc9..23b3c4fc0d 100644
--- a/compatibility-server/src/test/java/com/vaadin/v7/tests/server/SerializationTest.java
+++ b/compatibility-server/src/test/java/com/vaadin/v7/tests/server/SerializationTest.java
@@ -17,6 +17,7 @@ import com.vaadin.v7.data.Property;
import com.vaadin.v7.data.util.IndexedContainer;
import com.vaadin.v7.data.util.MethodProperty;
import com.vaadin.v7.data.validator.RegexpValidator;
+import com.vaadin.v7.ui.Form;
public class SerializationTest {
@@ -29,6 +30,19 @@ public class SerializationTest {
}
@Test
+ public void testForm() throws Exception {
+ Form f = new Form();
+ String propertyId = "My property";
+ f.addItemProperty(propertyId,
+ new MethodProperty<>(new Data(), "dummyGetterAndSetter"));
+ f.replaceWithSelect(propertyId, new Object[] { "a", "b", null },
+ new String[] { "Item a", "ITem b", "Null item" });
+
+ serializeAndDeserialize(f);
+
+ }
+
+ @Test
public void testIndedexContainerItemIds() throws Exception {
IndexedContainer ic = new IndexedContainer();
ic.addContainerProperty("prop1", String.class, null);
@@ -43,22 +57,20 @@ public class SerializationTest {
@Test
public void testMethodPropertyGetter() throws Exception {
- MethodProperty<?> mp = new MethodProperty<Object>(new Data(),
- "dummyGetter");
+ MethodProperty<?> mp = new MethodProperty<>(new Data(), "dummyGetter");
serializeAndDeserialize(mp);
}
@Test
public void testMethodPropertyGetterAndSetter() throws Exception {
- MethodProperty<?> mp = new MethodProperty<Object>(new Data(),
+ MethodProperty<?> mp = new MethodProperty<>(new Data(),
"dummyGetterAndSetter");
serializeAndDeserialize(mp);
}
@Test
public void testMethodPropertyInt() throws Exception {
- MethodProperty<?> mp = new MethodProperty<Object>(new Data(),
- "dummyInt");
+ MethodProperty<?> mp = new MethodProperty<>(new Data(), "dummyInt");
serializeAndDeserialize(mp);
}
diff --git a/compatibility-server/src/test/java/com/vaadin/v7/tests/server/component/form/FormTest.java b/compatibility-server/src/test/java/com/vaadin/v7/tests/server/component/form/FormTest.java
new file mode 100644
index 0000000000..0b30afbe0c
--- /dev/null
+++ b/compatibility-server/src/test/java/com/vaadin/v7/tests/server/component/form/FormTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.v7.tests.server.component.form;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.vaadin.v7.ui.Form;
+import com.vaadin.v7.ui.TextField;
+
+/**
+ * Test for {@link Form}.
+ *
+ * @author Vaadin Ltd
+ */
+public class FormTest {
+
+ @Test
+ public void testFocus() {
+ Form form = new Form();
+ final boolean firstFieldIsFocused[] = new boolean[1];
+ TextField field1 = new TextField() {
+ @Override
+ public boolean isConnectorEnabled() {
+ return false;
+ }
+
+ @Override
+ public void focus() {
+ firstFieldIsFocused[0] = true;
+ }
+ };
+
+ final boolean secondFieldIsFocused[] = new boolean[1];
+ TextField field2 = new TextField() {
+ @Override
+ public boolean isConnectorEnabled() {
+ return true;
+ }
+
+ @Override
+ public void focus() {
+ secondFieldIsFocused[0] = true;
+ }
+ };
+ form.addField("a", field1);
+ form.addField("b", field2);
+ form.focus();
+
+ Assert.assertTrue("Field with enabled connector is not focused",
+ secondFieldIsFocused[0]);
+ Assert.assertFalse("Field with disabled connector is focused",
+ firstFieldIsFocused[0]);
+ }
+}
diff --git a/compatibility-shared/src/main/java/com/vaadin/v7/shared/form/FormState.java b/compatibility-shared/src/main/java/com/vaadin/v7/shared/form/FormState.java
new file mode 100644
index 0000000000..f351bbbe36
--- /dev/null
+++ b/compatibility-shared/src/main/java/com/vaadin/v7/shared/form/FormState.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2000-2016 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.v7.shared.form;
+
+import com.vaadin.shared.Connector;
+import com.vaadin.v7.shared.AbstractFieldState;
+
+public class FormState extends AbstractFieldState {
+ {
+ primaryStyleName = "v-form";
+ }
+ public Connector layout;
+ public Connector footer;
+}
diff --git a/server/src/main/java/com/vaadin/server/ComponentSizeValidator.java b/server/src/main/java/com/vaadin/server/ComponentSizeValidator.java
index 241aea2078..fa3b269fae 100644
--- a/server/src/main/java/com/vaadin/server/ComponentSizeValidator.java
+++ b/server/src/main/java/com/vaadin/server/ComponentSizeValidator.java
@@ -36,6 +36,7 @@ import com.vaadin.ui.ComponentContainer;
import com.vaadin.ui.CustomComponent;
import com.vaadin.ui.GridLayout;
import com.vaadin.ui.GridLayout.Area;
+import com.vaadin.ui.HasComponents;
import com.vaadin.ui.Panel;
import com.vaadin.ui.TabSheet;
import com.vaadin.ui.UI;
@@ -89,11 +90,39 @@ public class ComponentSizeValidator implements Serializable {
errors = validateComponentRelativeSizes(it.next(), errors,
parent);
}
+ } else if (isForm(component)) {
+ HasComponents form = (HasComponents) component;
+ for (Iterator<Component> iterator = form.iterator(); iterator
+ .hasNext();) {
+ Component child = iterator.next();
+ errors = validateComponentRelativeSizes(child, errors, parent);
+ }
}
return errors;
}
+ /**
+ * Comparability form component which is defined in the different jar.
+ *
+ * TODO : Normally this logic shouldn't be here. But it means that the whole
+ * this class has wrong design and impementation and should be refactored.
+ */
+ private static boolean isForm(Component component) {
+ if (!(component instanceof HasComponents)) {
+ return false;
+ }
+ Class<?> clazz = component.getClass();
+ while (clazz != null) {
+ if (component.getClass().getName()
+ .equals("com.vaadin.v7.ui.Form")) {
+ return true;
+ }
+ clazz = clazz.getSuperclass();
+ }
+ return false;
+ }
+
private static void printServerError(String msg,
Stack<ComponentInfo> attributes, boolean widthError,
PrintStream errorStream) {
@@ -448,6 +477,12 @@ public class ComponentSizeValidator implements Serializable {
// Other components define row height
return true;
}
+ } else if (isForm(parent)) {
+ /*
+ * If some other part of the form is not relative it determines
+ * the component width
+ */
+ return formHasNonRelativeWidthComponent(parent);
}
if (parent instanceof Panel || parent instanceof AbstractSplitPanel
@@ -477,6 +512,24 @@ public class ComponentSizeValidator implements Serializable {
}
}
+ /**
+ * Comparability form component which is defined in the different jar.
+ *
+ * TODO : Normally this logic shouldn't be here. But it means that the whole
+ * this class has wrong design and impementation and should be refactored.
+ */
+ private static boolean formHasNonRelativeWidthComponent(Component form) {
+ HasComponents parent = (HasComponents) form;
+ for (Iterator<Component> iterator = parent.iterator(); iterator
+ .hasNext();) {
+ if (!hasRelativeWidth(iterator.next())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
private static boolean hasRelativeHeight(Component component) {
return (component.getHeightUnits() == Unit.PERCENTAGE
&& component.getHeight() > 0);
diff --git a/uitest/src/main/java/com/vaadin/v7/tests/components/form/FormTooltips.java b/uitest/src/main/java/com/vaadin/v7/tests/components/form/FormTooltips.java
new file mode 100644
index 0000000000..75688dfdb9
--- /dev/null
+++ b/uitest/src/main/java/com/vaadin/v7/tests/components/form/FormTooltips.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2012 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.v7.tests.components.form;
+
+import java.util.Arrays;
+
+import com.vaadin.server.UserError;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.tests.components.AbstractTestUI;
+import com.vaadin.tests.data.bean.Person;
+import com.vaadin.tests.data.bean.Sex;
+import com.vaadin.v7.data.util.BeanItem;
+import com.vaadin.v7.ui.Form;
+import com.vaadin.v7.ui.TextField;
+
+public class FormTooltips extends AbstractTestUI {
+
+ @Override
+ protected void setup(VaadinRequest request) {
+ final Form form = new Form();
+ form.setId("tooltipForm");
+ form.setDescription("Some description");
+ form.setItemDataSource(
+ new BeanItem<>(
+ new Person("foo", "bar", "baz", 12, Sex.MALE, null)),
+ Arrays.asList(new String[] { "firstName", "lastName", "age" }));
+ ((TextField) form.getField("firstName"))
+ .setDescription("Fields own tooltip");
+
+ form.setComponentError(new UserError("Form error"));
+ addComponent(form);
+
+ }
+
+ @Override
+ protected String getTestDescription() {
+ return "The 'first name' should show its own tooltip, the other fields should show no tooltip";
+ }
+
+ @Override
+ protected Integer getTicketNumber() {
+ return 9173;
+ }
+
+}
diff --git a/uitest/src/main/java/com/vaadin/v7/tests/components/window/UndefinedHeightSubWindowAndContent.java b/uitest/src/main/java/com/vaadin/v7/tests/components/window/UndefinedHeightSubWindowAndContent.java
new file mode 100644
index 0000000000..4a747771ac
--- /dev/null
+++ b/uitest/src/main/java/com/vaadin/v7/tests/components/window/UndefinedHeightSubWindowAndContent.java
@@ -0,0 +1,60 @@
+package com.vaadin.v7.tests.components.window;
+
+import com.vaadin.tests.components.TestBase;
+import com.vaadin.ui.VerticalLayout;
+import com.vaadin.ui.Window;
+import com.vaadin.v7.data.Validator;
+import com.vaadin.v7.ui.Form;
+import com.vaadin.v7.ui.TextField;
+
+public class UndefinedHeightSubWindowAndContent extends TestBase {
+
+ @Override
+ protected void setup() {
+ Window subWindow = new Window("No scrollbars!");
+ subWindow.setWidth("300px");
+ subWindow.center();
+ subWindow.setModal(true);
+ VerticalLayout layout = new VerticalLayout();
+ layout.setWidth("100%");
+ subWindow.setContent(layout);
+
+ final Form form = new Form();
+ form.setFooter(null);
+ form.setImmediate(true);
+ form.setValidationVisible(true);
+ form.setCaption("This is a form");
+ form.setDescription("How do you do?");
+ final TextField field1 = new TextField("Write here");
+ field1.setImmediate(true);
+ field1.addValidator(new Validator() {
+
+ @Override
+ public void validate(Object value) throws InvalidValueException {
+ if (!isValid(value)) {
+ throw new InvalidValueException("FAIL!");
+ }
+ }
+
+ public boolean isValid(Object value) {
+ return field1.getValue().equals("valid");
+ }
+ });
+ form.addField("Field 1", field1);
+ layout.addComponent(form);
+
+ getMainWindow().addWindow(subWindow);
+ subWindow.bringToFront();
+ }
+
+ @Override
+ protected String getDescription() {
+ return "When both window and its content have undefined height, window must not reserve space for a scroll bar when it is not needed.";
+ }
+
+ @Override
+ protected Integer getTicketNumber() {
+ return 8852;
+ }
+
+}
diff --git a/uitest/src/test/java/com/vaadin/v7/tests/components/form/FormTooltipsTest.java b/uitest/src/test/java/com/vaadin/v7/tests/components/form/FormTooltipsTest.java
new file mode 100644
index 0000000000..710fd5a8c1
--- /dev/null
+++ b/uitest/src/test/java/com/vaadin/v7/tests/components/form/FormTooltipsTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2000-2013 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.v7.tests.components.form;
+
+import org.junit.Test;
+import org.openqa.selenium.WebElement;
+
+import com.vaadin.testbench.By;
+import com.vaadin.testbench.elements.FormElement;
+import com.vaadin.testbench.elements.TextFieldElement;
+import com.vaadin.tests.tb3.TooltipTest;
+
+public class FormTooltipsTest extends TooltipTest {
+
+ @Test
+ public void testTooltipConfiguration() throws Exception {
+ openTestURL();
+ // first name tooltip
+
+ WebElement fieldElement = $(FormElement.class).first()
+ .$(TextFieldElement.class).first();
+ checkTooltip(fieldElement, "Fields own tooltip");
+ clearTooltip();
+ checkTooltipNotPresent();
+
+ // first name caption tooltip
+ checkTooltip($(FormElement.class).first()
+ .findElement(By.className("v-caption")), "Fields own tooltip");
+
+ clearTooltip();
+ checkTooltipNotPresent();
+
+ // Form should not have a description tooltip
+ checkTooltip($(FormElement.class).first(), null);
+
+ // Form error message should not have a tooltip
+ checkTooltip(By.className("v-form-errormessage"), null);
+
+ // last name should have no tooltip
+ checkTooltip($(TextFieldElement.class).get(1), null);
+
+ // last name caption should have no tooltip
+ checkTooltip($(FormElement.class).first()
+ .findElements(By.className("v-caption")).get(1), null);
+ }
+
+}
diff --git a/uitest/src/test/java/com/vaadin/v7/tests/components/window/UndefinedHeightSubWindowAndContentTest.java b/uitest/src/test/java/com/vaadin/v7/tests/components/window/UndefinedHeightSubWindowAndContentTest.java
new file mode 100644
index 0000000000..09489d4566
--- /dev/null
+++ b/uitest/src/test/java/com/vaadin/v7/tests/components/window/UndefinedHeightSubWindowAndContentTest.java
@@ -0,0 +1,34 @@
+package com.vaadin.v7.tests.components.window;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.openqa.selenium.Keys;
+
+import com.vaadin.testbench.customelements.WindowElement;
+import com.vaadin.testbench.elements.TextFieldElement;
+import com.vaadin.tests.tb3.MultiBrowserTest;
+
+public class UndefinedHeightSubWindowAndContentTest extends MultiBrowserTest {
+
+ @Test
+ public void testUndefinedHeight() {
+ openTestURL();
+
+ TextFieldElement textField = $(TextFieldElement.class).first();
+
+ textField.click();
+ textField.sendKeys("invalid", Keys.ENTER);
+
+ WindowElement window = $(WindowElement.class).first();
+ int height = window.getSize().getHeight();
+ Assert.assertTrue("Window height with validation failure",
+ 161 <= height && height <= 164);
+
+ textField.setValue("valid");
+ textField.sendKeys(Keys.ENTER);
+ height = window.getSize().getHeight();
+ Assert.assertTrue("Window height with validation success",
+ 136 <= height && height <= 139);
+ }
+
+}