/* * 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.Validator.InvalidValueException; 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 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 Item} interface, most properties of any class * following bean pattern, can be accessed trough {@link 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, 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<Object>(); /** * 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<Object, Field<?>>(); /** * Form may act as an Item, its own properties are stored here. */ private final HashMap<Object, Property<?>> ownProperties = new HashMap<Object, Property<?>>(); /** * 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 FieldFactory 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, 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<SourceException>(); } 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<SourceException>(); } 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 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 AbstractLegacyComponent) { ((AbstractLegacyComponent) 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 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 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 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 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 Validatable#validate() */ @Override public void validate() throws 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 Validatable#isInvalidAllowed() */ @Override public boolean isInvalidAllowed() { return true; } /** * Should the validabtable object accept invalid values. * * @see 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 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 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 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<Object>(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<Object>(); for (int i = 0; i < visibleProperties.length; i++) { v.add(visibleProperties[i]); } setVisibleItemProperties(v); } /** * Focuses the first field in the form. * * @see 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 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 AbstractLegacyComponent) { ((AbstractLegacyComponent) 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; } @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; } }