/* @VaadinApache2LicenseForJavaFiles@ */ package com.vaadin.ui; import java.io.Serializable; import java.lang.reflect.Method; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; import java.util.Map; import com.vaadin.Application; import com.vaadin.data.Buffered; import com.vaadin.data.Property; import com.vaadin.data.Validatable; import com.vaadin.data.Validator; import com.vaadin.data.Validator.InvalidValueException; import com.vaadin.data.util.converter.Converter; import com.vaadin.data.util.converter.ConverterFactory; import com.vaadin.event.Action; import com.vaadin.event.ShortcutAction; import com.vaadin.event.ShortcutListener; import com.vaadin.terminal.CompositeErrorMessage; import com.vaadin.terminal.ErrorMessage; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; /** *

* Abstract field component for implementing buffered property editors. The * field may hold an internal value, or it may be connected to any data source * that implements the {@link com.vaadin.data.Property}interface. * AbstractField implements that interface itself, too, so * accessing the Property value represented by it is straightforward. *

* *

* AbstractField also provides the {@link com.vaadin.data.Buffered} interface * for buffering the data source value. By default the Field is in write * through-mode and {@link #setWriteThrough(boolean)}should be called to enable * buffering. *

* *

* The class also supports {@link com.vaadin.data.Validator validators} to make * sure the value contained in the field is valid. *

* * @author Vaadin Ltd. * @version * @VERSION@ * @since 3.0 */ @SuppressWarnings("serial") public abstract class AbstractField extends AbstractComponent implements Field, Property.ReadOnlyStatusChangeListener, Property.ReadOnlyStatusChangeNotifier, Action.ShortcutNotifier { /* Private members */ /** * Value of the abstract field. */ private T value; /** * A converter used to convert from the data model type to the field type * and vice versa. */ private Converter valueConverter = null; /** * Connected data-source. */ private Property dataSource = null; /** * The list of validators. */ private LinkedList validators = null; /** * Auto commit mode. */ private boolean writeThroughMode = true; /** * Reads the value from data-source, when it is not modified. */ private boolean readThroughMode = true; /** * Is the field modified but not committed. */ private boolean modified = false; /** * Flag to indicate that the field is currently committing its value to the * datasource. */ private boolean committingValueToDataSource = false; /** * Current source exception. */ private Buffered.SourceException currentBufferedSourceException = null; /** * Are the invalid values allowed in fields ? */ private boolean invalidAllowed = true; /** * Are the invalid values committed ? */ private boolean invalidCommitted = false; /** * The tab order number of this field. */ private int tabIndex = 0; /** * Required field. */ private boolean required = false; /** * The error message for the exception that is thrown when the field is * required but empty. */ private String requiredError = ""; /** * Is automatic validation enabled. */ private boolean validationVisible = true; private boolean valueWasModifiedByDataSourceDuringCommit; /* Component basics */ /* * Paints the field. Don't add a JavaDoc comment here, we use the default * documentation from the implemented interface. */ @Override public void paintContent(PaintTarget target) throws PaintException { // The tab ordering number if (getTabIndex() != 0) { target.addAttribute("tabindex", getTabIndex()); } // If the field is modified, but not committed, set modified attribute if (isModified()) { target.addAttribute("modified", true); } // Adds the required attribute if (!isReadOnly() && isRequired()) { target.addAttribute("required", true); } // Hide the error indicator if needed if (shouldHideErrors()) { target.addAttribute("hideErrors", true); } } /** * Returns true if the error indicator be hidden when painting the component * even when there are errors. * * This is a mostly internal method, but can be overridden in subclasses * e.g. if the error indicator should also be shown for empty fields in some * cases. * * @return true to hide the error indicator, false to use the normal logic * to show it when there are errors */ protected boolean shouldHideErrors() { return isRequired() && isEmpty() && getComponentError() == null && getErrorMessage() != null; } /** * Returns the type of the Field. The methods getValue and * setValue must be compatible with this type: one must be able * to safely cast the value returned from getValue to the given * type and pass any variable assignable to this type as an argument to * setValue. * * @return the type of the Field */ public abstract Class getType(); /** * The abstract field is read only also if the data source is in read only * mode. */ @Override public boolean isReadOnly() { return super.isReadOnly() || (dataSource != null && dataSource.isReadOnly()); } /** * Changes the readonly state and throw read-only status change events. * * @see com.vaadin.ui.Component#setReadOnly(boolean) */ @Override public void setReadOnly(boolean readOnly) { super.setReadOnly(readOnly); fireReadOnlyStatusChange(); } /** * Tests if the invalid data is committed to datasource. * * @see com.vaadin.data.BufferedValidatable#isInvalidCommitted() */ public boolean isInvalidCommitted() { return invalidCommitted; } /** * Sets if the invalid data should be committed to datasource. * * @see com.vaadin.data.BufferedValidatable#setInvalidCommitted(boolean) */ public void setInvalidCommitted(boolean isCommitted) { invalidCommitted = isCommitted; } /* * Saves the current value to the data source Don't add a JavaDoc comment * here, we use the default documentation from the implemented interface. */ public void commit() throws Buffered.SourceException, InvalidValueException { if (dataSource != null && !dataSource.isReadOnly()) { if ((isInvalidCommitted() || isValid())) { final T fieldValue = getFieldValue(); try { // Commits the value to datasource. valueWasModifiedByDataSourceDuringCommit = false; committingValueToDataSource = true; getPropertyDataSource().setValue( convertToDataSource(fieldValue)); } catch (final Throwable e) { // Sets the buffering state. currentBufferedSourceException = new Buffered.SourceException( this, e); requestRepaint(); // Throws the source exception. throw currentBufferedSourceException; } finally { committingValueToDataSource = false; } } else { /* An invalid value and we don't allow them, throw the exception */ validate(); } } boolean repaintNeeded = false; // The abstract field is not modified anymore if (isModified()) { setModified(false); repaintNeeded = true; } // If successful, remove set the buffering state to be ok if (currentBufferedSourceException != null) { currentBufferedSourceException = null; repaintNeeded = true; } if (valueWasModifiedByDataSourceDuringCommit) { valueWasModifiedByDataSourceDuringCommit = false; fireValueChange(false); } else if (repaintNeeded) { requestRepaint(); } } /* * Updates the value from the data source. Don't add a JavaDoc comment here, * we use the default documentation from the implemented interface. */ public void discard() throws Buffered.SourceException { if (dataSource != null) { // Gets the correct value from datasource T newFieldValue; try { // Discards buffer by overwriting from datasource newFieldValue = convertFromDataSource(getDataSourceValue()); // If successful, remove set the buffering state to be ok if (currentBufferedSourceException != null) { currentBufferedSourceException = null; requestRepaint(); } } catch (final Throwable e) { // FIXME: What should really be done here if conversion fails? // Sets the buffering state currentBufferedSourceException = new Buffered.SourceException( this, e); requestRepaint(); // Throws the source exception throw currentBufferedSourceException; } final boolean wasModified = isModified(); setModified(false); // If the new value differs from the previous one if (!equals(newFieldValue, getInternalValue())) { setInternalValue(newFieldValue); fireValueChange(false); } else if (wasModified) { // If the value did not change, but the modification status did requestRepaint(); } } } private Object getDataSourceValue() { return dataSource.getValue(); } /** * Returns the value that is or should be displayed in the field. This is * always of type T. * * This method should return the same as * convertFromDataSource(getDataSourceValue()) if there are no buffered * changes in the field. * * @return The value of the field */ private T getFieldValue() { // Give the value from abstract buffers if the field if possible if (dataSource == null || !isReadThrough() || isModified()) { return getInternalValue(); } // There is no buffered value so use whatever the data model provides return convertFromDataSource(getDataSourceValue()); } /* * Has the field been modified since the last commit()? Don't add a JavaDoc * comment here, we use the default documentation from the implemented * interface. */ public boolean isModified() { return modified; } private void setModified(boolean modified) { this.modified = modified; } /* * Tests if the field is in write-through mode. Don't add a JavaDoc comment * here, we use the default documentation from the implemented interface. */ public boolean isWriteThrough() { return writeThroughMode; } /* * Sets the field's write-through mode to the specified status Don't add a * JavaDoc comment here, we use the default documentation from the * implemented interface. */ public void setWriteThrough(boolean writeThrough) throws Buffered.SourceException, InvalidValueException { if (writeThroughMode == writeThrough) { return; } writeThroughMode = writeThrough; if (writeThroughMode) { commit(); } } /* * Tests if the field is in read-through mode. Don't add a JavaDoc comment * here, we use the default documentation from the implemented interface. */ public boolean isReadThrough() { return readThroughMode; } /* * Sets the field's read-through mode to the specified status Don't add a * JavaDoc comment here, we use the default documentation from the * implemented interface. */ public void setReadThrough(boolean readThrough) throws Buffered.SourceException { if (readThroughMode == readThrough) { return; } readThroughMode = readThrough; if (!isModified() && readThroughMode && getPropertyDataSource() != null) { setInternalValue(convertFromDataSource(getDataSourceValue())); fireValueChange(false); } } /* Property interface implementation */ /** * Returns the value of the Property in human readable textual format. * * @see java.lang.Object#toString() * @deprecated get the string representation from the data source, or use * getStringValue() during migration */ @Deprecated @Override public String toString() { throw new UnsupportedOperationException( "Use Property.getValue() instead of " + getClass() + ".toString()"); } /** * Returns the (UI type) value of the field converted to a String. * * This method exists to help migration from the use of Property.toString() * to get the field value. For new applications, it is often better to * access getValue() directly. * * @return string representation of the field value or null if the value is * null * @since 7.0 */ public String getStringValue() { final Object value = getFieldValue(); if (value == null) { return null; } return value.toString(); } /** * Gets the current value of the field. * *

* This is the visible, modified and possible invalid value the user have * entered to the field. *

* *

* Note that the object returned is compatible with getType(). For example, * if the type is String, this returns Strings even when the underlying * datasource is of some other type. In order to access the native value of * the datasource, use getDataSourceValue() instead. *

* *

* Since Vaadin 7.0, no implicit conversions between other data types and * String are performed, but a value converter is used if set. *

* * @return the current value of the field. * @throws Property.ConversionException */ public T getValue() { return getFieldValue(); } /** * Sets the value of the field. * * @param newFieldValue * the New value of the field. * @throws Property.ReadOnlyException * @throws Property.ConversionException */ public void setValue(Object newFieldValue) throws Property.ReadOnlyException, Property.ConversionException { // This check is needed as long as setValue accepts Object instead of T if (newFieldValue != null) { if (!getType().isAssignableFrom(newFieldValue.getClass())) { throw new ConversionException("Value of type " + newFieldValue.getClass() + " cannot be assigned to " + getClass().getName()); } } setValue((T) newFieldValue, false); } /** * Sets the value of the field. * * @param newFieldValue * the New value of the field. * @param repaintIsNotNeeded * True iff caller is sure that repaint is not needed. * @throws Property.ReadOnlyException * @throws Property.ConversionException */ protected void setValue(T newFieldValue, boolean repaintIsNotNeeded) throws Property.ReadOnlyException, Property.ConversionException { if (!equals(newFieldValue, getInternalValue())) { // Read only fields can not be changed if (isReadOnly()) { throw new Property.ReadOnlyException(); } // Repaint is needed even when the client thinks that it knows the // new state if validity of the component may change if (repaintIsNotNeeded && (isRequired() || getValidators() != null)) { repaintIsNotNeeded = false; } // If invalid values are not allowed, the value must be checked if (!isInvalidAllowed()) { final Collection v = getValidators(); if (v != null) { for (final Iterator i = v.iterator(); i .hasNext();) { (i.next()).validate(newFieldValue); } } } // Changes the value setInternalValue(newFieldValue); setModified(dataSource != null); valueWasModifiedByDataSourceDuringCommit = false; // In write through mode , try to commit if (isWriteThrough() && dataSource != null && (isInvalidCommitted() || isValid())) { try { // Commits the value to datasource committingValueToDataSource = true; getPropertyDataSource().setValue( convertToDataSource(newFieldValue)); // The buffer is now unmodified setModified(false); } catch (final Throwable e) { // Sets the buffering state currentBufferedSourceException = new Buffered.SourceException( this, e); requestRepaint(); // Throws the source exception throw currentBufferedSourceException; } finally { committingValueToDataSource = false; } } // If successful, remove set the buffering state to be ok if (currentBufferedSourceException != null) { currentBufferedSourceException = null; requestRepaint(); } if (valueWasModifiedByDataSourceDuringCommit) { /* * Value was modified by datasource. Force repaint even if * repaint was not requested. */ valueWasModifiedByDataSourceDuringCommit = repaintIsNotNeeded = false; } // Fires the value change fireValueChange(repaintIsNotNeeded); } } private static boolean equals(Object value1, Object value2) { if (value1 == null) { return value2 == null; } return value1.equals(value2); } /* External data source */ /** * Gets the current data source of the field, if any. * * @return the current data source as a Property, or null if * none defined. */ public Property getPropertyDataSource() { return dataSource; } /** *

* Sets the specified Property as the data source for the field. All * uncommitted changes are replaced with a value from the new data source. *

* *

* If the datasource has any validators, the same validators are added to * the field. Because the default behavior of the field is to allow invalid * values, but not to allow committing them, this only adds visual error * messages to fields and do not allow committing them as long as the value * is invalid. After the value is valid, the error message is not shown and * the commit can be done normally. *

* *

* Note: before 6.5 we actually called discard() method in the beginning of * the method. This was removed to simplify implementation, avoid excess * calls to backing property and to avoid odd value change events that were * previously fired (developer expects 0-1 value change events if this * method is called). Some complex field implementations might now need to * override this method to do housekeeping similar to discard(). *

* * @param newDataSource * the new data source Property. */ public void setPropertyDataSource(Property newDataSource) { // Saves the old value final Object oldValue = getInternalValue(); // Stops listening the old data source changes if (dataSource != null && Property.ValueChangeNotifier.class .isAssignableFrom(dataSource.getClass())) { ((Property.ValueChangeNotifier) dataSource).removeListener(this); } if (dataSource != null && Property.ReadOnlyStatusChangeNotifier.class .isAssignableFrom(dataSource.getClass())) { ((Property.ReadOnlyStatusChangeNotifier) dataSource) .removeListener(this); } // Sets the new data source dataSource = newDataSource; // Check if the current converter is compatible. If not, get a new one if (newDataSource == null) { setValueConverter(null); } else if (!isValueConverterType(newDataSource.getType())) { setValueConverterFromFactory(newDataSource.getType()); } // Gets the value from source try { if (dataSource != null) { T fieldValue = convertFromDataSource(getDataSourceValue()); setInternalValue(fieldValue); } setModified(false); } catch (final Throwable e) { currentBufferedSourceException = new Buffered.SourceException(this, e); setModified(true); } // Listens the new data source if possible if (dataSource instanceof Property.ValueChangeNotifier) { ((Property.ValueChangeNotifier) dataSource).addListener(this); } if (dataSource instanceof Property.ReadOnlyStatusChangeNotifier) { ((Property.ReadOnlyStatusChangeNotifier) dataSource) .addListener(this); } // Copy the validators from the data source if (dataSource instanceof Validatable) { final Collection validators = ((Validatable) dataSource) .getValidators(); if (validators != null) { for (final Iterator i = validators.iterator(); i .hasNext();) { addValidator(i.next()); } } } // Fires value change if the value has changed T value = getInternalValue(); if ((value != oldValue) && ((value != null && !value.equals(oldValue)) || value == null)) { fireValueChange(false); } } private void setValueConverterFromFactory(Class datamodelType) { // FIXME Use thread local to get application ConverterFactory factory = Application.getConverterFactory(); Converter converter = (Converter) factory.createConverter( datamodelType, getType()); setValueConverter(converter); } private boolean isValueConverterType(Class type) { if (getValueConverter() == null) { return false; } return getValueConverter().getSourceType().isAssignableFrom(type); } @SuppressWarnings("unchecked") private T convertFromDataSource(Object newValue) { if (valueConverter != null) { return valueConverter.convertFromSourceToTarget(newValue, getLocale()); } if (newValue == null) { return null; } if (getType().isAssignableFrom(newValue.getClass())) { return (T) newValue; } else { throw new ConversionException( "Unable to convert value of type " + newValue.getClass().getName() + " to " + getType() + ". No value converter is set and the types are not compatible."); } } private Object convertToDataSource(T fieldValue) throws Converter.ConversionException { if (valueConverter != null) { /* * If there is a value converter, always use it. It must convert or * throw an exception. */ return valueConverter.convertFromTargetToSource(fieldValue, getLocale()); } if (fieldValue == null) { // Null should always be passed through the converter but if there // is no converter we can safely return null return null; } // check that the value class is compatible with the data source type // (if data source set) or field type Class type; if (getPropertyDataSource() != null) { type = getPropertyDataSource().getType(); } else { type = getType(); } if (type.isAssignableFrom(fieldValue.getClass())) { return fieldValue; } else { throw new Converter.ConversionException( "Unable to convert value of type " + fieldValue.getClass().getName() + " to " + type.getName() + ". No value converter is set and the types are not compatible."); } } /* Validation */ /** * Adds a new validator for the field's value. All validators added to a * field are checked each time the its value changes. * * @param validator * the new validator to be added. */ public void addValidator(Validator validator) { if (validators == null) { validators = new LinkedList(); } validators.add(validator); requestRepaint(); } /** * Gets the validators of the field. * * @return the Unmodifiable collection that holds all validators for the * field. */ public Collection getValidators() { if (validators == null || validators.isEmpty()) { return null; } return Collections.unmodifiableCollection(validators); } /** * Removes the validator from the field. * * @param validator * the validator to remove. */ public void removeValidator(Validator validator) { if (validators != null) { validators.remove(validator); } requestRepaint(); } /** * Tests the current value against registered validators if the field is not * empty. If the field is empty it is considered valid if it is not required * and invalid otherwise. Validators are never checked for empty fields. * * In most cases, {@link #validate()} should be used instead of * {@link #isValid()} to also get the error message. * * @return true if all registered validators claim that the * current value is valid or if the field is empty and not required, * false otherwise. */ public boolean isValid() { try { validate(); return true; } catch (InvalidValueException e) { return false; } } /** * Checks the validity of the Validatable by validating the field with all * attached validators except when the field is empty. An empty field is * invalid if it is required and valid otherwise. * * The "required" validation is a built-in validation feature. If the field * is required, but empty, validation will throw an EmptyValueException with * the error message set with setRequiredError(). * * @see com.vaadin.data.Validatable#validate() */ public void validate() throws Validator.InvalidValueException { if (isEmpty()) { if (isRequired()) { throw new Validator.EmptyValueException(requiredError); } else { return; } } // If there is no validator, there can not be any errors if (validators == null) { return; } // Initialize temps Validator.InvalidValueException firstError = null; LinkedList errors = null; final Object fieldValue = getFieldValue(); // Gets all the validation errors for (final Iterator i = validators.iterator(); i.hasNext();) { try { (i.next()).validate(fieldValue); } catch (final Validator.InvalidValueException e) { if (firstError == null) { firstError = e; } else { if (errors == null) { errors = new LinkedList(); errors.add(firstError); } errors.add(e); } } } // If there were no error if (firstError == null) { return; } // If only one error occurred, throw it forwards if (errors == null) { throw firstError; } // Creates composite validator final Validator.InvalidValueException[] exceptions = new Validator.InvalidValueException[errors .size()]; int index = 0; for (final Iterator i = errors.iterator(); i .hasNext();) { exceptions[index++] = i.next(); } throw new Validator.InvalidValueException(null, exceptions); } /** * Fields allow invalid values by default. In most cases this is wanted, * because the field otherwise visually forget the user input immediately. * * @return true iff the invalid values are allowed. * @see com.vaadin.data.Validatable#isInvalidAllowed() */ public boolean isInvalidAllowed() { return invalidAllowed; } /** * Fields allow invalid values by default. In most cases this is wanted, * because the field otherwise visually forget the user input immediately. *

* In common setting where the user wants to assure the correctness of the * datasource, but allow temporarily invalid contents in the field, the user * should add the validators to datasource, that should not allow invalid * values. The validators are automatically copied to the field when the * datasource is set. *

* * @see com.vaadin.data.Validatable#setInvalidAllowed(boolean) */ public void setInvalidAllowed(boolean invalidAllowed) throws UnsupportedOperationException { this.invalidAllowed = invalidAllowed; } /** * Error messages shown by the fields are composites of the error message * thrown by the superclasses (that is the component error message), * validation errors and buffered source errors. * * @see com.vaadin.ui.AbstractComponent#getErrorMessage() */ @Override public ErrorMessage getErrorMessage() { /* * Check validation errors only if automatic validation is enabled. * Empty, required fields will generate a validation error containing * the requiredError string. For these fields the exclamation mark will * be hidden but the error must still be sent to the client. */ ErrorMessage validationError = null; if (isValidationVisible()) { try { validate(); } catch (Validator.InvalidValueException e) { if (!e.isInvisible()) { validationError = e; } } } // Check if there are any systems errors final ErrorMessage superError = super.getErrorMessage(); // Return if there are no errors at all if (superError == null && validationError == null && currentBufferedSourceException == null) { return null; } // Throw combination of the error types return new CompositeErrorMessage(new ErrorMessage[] { superError, validationError, currentBufferedSourceException }); } /* Value change events */ private static final Method VALUE_CHANGE_METHOD; static { try { VALUE_CHANGE_METHOD = Property.ValueChangeListener.class .getDeclaredMethod("valueChange", new Class[] { Property.ValueChangeEvent.class }); } catch (final java.lang.NoSuchMethodException e) { // This should never happen throw new java.lang.RuntimeException( "Internal error finding methods in AbstractField"); } } /* * Adds a value change listener for the field. Don't add a JavaDoc comment * here, we use the default documentation from the implemented interface. */ public void addListener(Property.ValueChangeListener listener) { addListener(AbstractField.ValueChangeEvent.class, listener, VALUE_CHANGE_METHOD); } /* * Removes a value change listener from the field. Don't add a JavaDoc * comment here, we use the default documentation from the implemented * interface. */ public void removeListener(Property.ValueChangeListener listener) { removeListener(AbstractField.ValueChangeEvent.class, listener, VALUE_CHANGE_METHOD); } /** * Emits the value change event. The value contained in the field is * validated before the event is created. */ protected void fireValueChange(boolean repaintIsNotNeeded) { fireEvent(new AbstractField.ValueChangeEvent(this)); if (!repaintIsNotNeeded) { requestRepaint(); } } /* Read-only status change events */ private static final Method READ_ONLY_STATUS_CHANGE_METHOD; static { try { READ_ONLY_STATUS_CHANGE_METHOD = Property.ReadOnlyStatusChangeListener.class .getDeclaredMethod( "readOnlyStatusChange", new Class[] { Property.ReadOnlyStatusChangeEvent.class }); } catch (final java.lang.NoSuchMethodException e) { // This should never happen throw new java.lang.RuntimeException( "Internal error finding methods in AbstractField"); } } /** * React to read only status changes of the property by requesting a * repaint. * * @see Property.ReadOnlyStatusChangeListener */ public void readOnlyStatusChange(Property.ReadOnlyStatusChangeEvent event) { requestRepaint(); } /** * An Event object specifying the Property whose read-only * status has changed. * * @author Vaadin Ltd. * @version * @VERSION@ * @since 3.0 */ public static class ReadOnlyStatusChangeEvent extends Component.Event implements Property.ReadOnlyStatusChangeEvent, Serializable { /** * New instance of text change event. * * @param source * the Source of the event. */ public ReadOnlyStatusChangeEvent(AbstractField source) { super(source); } /** * Property where the event occurred. * * @return the Source of the event. */ public Property getProperty() { return (Property) getSource(); } } /* * Adds a read-only status change listener for the field. Don't add a * JavaDoc comment here, we use the default documentation from the * implemented interface. */ public void addListener(Property.ReadOnlyStatusChangeListener listener) { addListener(Property.ReadOnlyStatusChangeEvent.class, listener, READ_ONLY_STATUS_CHANGE_METHOD); } /* * Removes a read-only status change listener from the field. Don't add a * JavaDoc comment here, we use the default documentation from the * implemented interface. */ public void removeListener(Property.ReadOnlyStatusChangeListener listener) { removeListener(Property.ReadOnlyStatusChangeEvent.class, listener, READ_ONLY_STATUS_CHANGE_METHOD); } /** * Emits the read-only status change event. The value contained in the field * is validated before the event is created. */ protected void fireReadOnlyStatusChange() { fireEvent(new AbstractField.ReadOnlyStatusChangeEvent(this)); } /** * This method listens to data source value changes and passes the changes * forwards. * * Changes are not forwarded to the listeners of the field during internal * operations of the field to avoid duplicate notifications. * * @param event * the value change event telling the data source contents have * changed. */ public void valueChange(Property.ValueChangeEvent event) { if (isReadThrough()) { if (committingValueToDataSource) { boolean propertyNotifiesOfTheBufferedValue = equals(event .getProperty().getValue(), getInternalValue()); if (!propertyNotifiesOfTheBufferedValue) { /* * Property (or chained property like PropertyFormatter) now * reports different value than the one the field has just * committed to it. In this case we respect the property * value. * * Still, we don't fire value change yet, but instead * postpone it until "commit" is done. See setValue(Object, * boolean) and commit(). */ readValueFromProperty(event); valueWasModifiedByDataSourceDuringCommit = true; } } else if (!isModified()) { readValueFromProperty(event); fireValueChange(false); } } } private void readValueFromProperty(Property.ValueChangeEvent event) { setInternalValue(convertFromDataSource(event.getProperty().getValue())); } @Override public void changeVariables(Object source, Map variables) { super.changeVariables(source, variables); } /** * {@inheritDoc} */ @Override public void focus() { super.focus(); } /* * (non-Javadoc) * * @see com.vaadin.ui.Component.Focusable#getTabIndex() */ public int getTabIndex() { return tabIndex; } /* * (non-Javadoc) * * @see com.vaadin.ui.Component.Focusable#setTabIndex(int) */ public void setTabIndex(int tabIndex) { this.tabIndex = tabIndex; requestRepaint(); } /** * * @return */ protected T getInternalValue() { return value; } /** * Sets the internal field value. This is purely used by AbstractField to * change the internal Field value. It does not trigger valuechange events. * It can be overridden by the inheriting classes to update all dependent * variables. * * @param newValue * the new value to be set. */ protected void setInternalValue(T newValue) { value = newValue; if (validators != null && !validators.isEmpty()) { requestRepaint(); } } /** * Is this field required. Required fields must filled by the user. * * If the field is required, it is visually indicated in the user interface. * Furthermore, setting field to be required implicitly adds "non-empty" * validator and thus isValid() == false or any isEmpty() fields. In those * cases validation errors are not painted as it is obvious that the user * must fill in the required fields. * * On the other hand, for the non-required fields isValid() == true if the * field isEmpty() regardless of any attached validators. * * * @return true if the field is required .otherwise * false. */ public boolean isRequired() { return required; } /** * Sets the field required. Required fields must filled by the user. * * If the field is required, it is visually indicated in the user interface. * Furthermore, setting field to be required implicitly adds "non-empty" * validator and thus isValid() == false or any isEmpty() fields. In those * cases validation errors are not painted as it is obvious that the user * must fill in the required fields. * * On the other hand, for the non-required fields isValid() == true if the * field isEmpty() regardless of any attached validators. * * @param required * Is the field required. */ public void setRequired(boolean required) { this.required = required; requestRepaint(); } /** * Set the error that is show if this field is required, but empty. When * setting requiredMessage to be "" or null, no error pop-up or exclamation * mark is shown for a empty required field. This faults to "". Even in * those cases isValid() returns false for empty required fields. * * @param requiredMessage * Message to be shown when this field is required, but empty. */ public void setRequiredError(String requiredMessage) { requiredError = requiredMessage; requestRepaint(); } public String getRequiredError() { return requiredError; } /** * Is the field empty? * * In general, "empty" state is same as null. As an exception, TextField * also treats empty string as "empty". */ protected boolean isEmpty() { return (getFieldValue() == null); } /** * Is automatic, visible validation enabled? * * If automatic validation is enabled, any validators connected to this * component are evaluated while painting the component and potential error * messages are sent to client. If the automatic validation is turned off, * isValid() and validate() methods still work, but one must show the * validation in their own code. * * @return True, if automatic validation is enabled. */ public boolean isValidationVisible() { return validationVisible; } /** * Enable or disable automatic, visible validation. * * If automatic validation is enabled, any validators connected to this * component are evaluated while painting the component and potential error * messages are sent to client. If the automatic validation is turned off, * isValid() and validate() methods still work, but one must show the * validation in their own code. * * @param validateAutomatically * True, if automatic validation is enabled. */ public void setValidationVisible(boolean validateAutomatically) { if (validationVisible != validateAutomatically) { requestRepaint(); validationVisible = validateAutomatically; } } /** * Sets the current buffered source exception. * * @param currentBufferedSourceException */ public void setCurrentBufferedSourceException( Buffered.SourceException currentBufferedSourceException) { this.currentBufferedSourceException = currentBufferedSourceException; requestRepaint(); } /** * A ready-made {@link ShortcutListener} that focuses the given * {@link Focusable} (usually a {@link Field}) when the keyboard shortcut is * invoked. * */ public static class FocusShortcut extends ShortcutListener { protected Focusable focusable; /** * Creates a keyboard shortcut for focusing the given {@link Focusable} * using the shorthand notation defined in {@link ShortcutAction}. * * @param focusable * to focused when the shortcut is invoked * @param shorthandCaption * caption with keycode and modifiers indicated */ public FocusShortcut(Focusable focusable, String shorthandCaption) { super(shorthandCaption); this.focusable = focusable; } /** * Creates a keyboard shortcut for focusing the given {@link Focusable}. * * @param focusable * to focused when the shortcut is invoked * @param keyCode * keycode that invokes the shortcut * @param modifiers * modifiers required to invoke the shortcut */ public FocusShortcut(Focusable focusable, int keyCode, int... modifiers) { super(null, keyCode, modifiers); this.focusable = focusable; } /** * Creates a keyboard shortcut for focusing the given {@link Focusable}. * * @param focusable * to focused when the shortcut is invoked * @param keyCode * keycode that invokes the shortcut */ public FocusShortcut(Focusable focusable, int keyCode) { this(focusable, keyCode, null); } @Override public void handleAction(Object sender, Object target) { focusable.focus(); } } /** * Gets the converter used to convert the property data source value to the * field value. * * @return The converter or null if none is set. */ public Converter getValueConverter() { return valueConverter; } /** * Sets the converter used to convert the property data source value to the * field value. The converter must have a target type that matches the field * type. * * The source for the converter is the data model and the target is the * field. * * @param valueConverter * The new value converter to use. */ public void setValueConverter(Converter valueConverter) { // this.valueConverter = (Converter) valueConverter; requestRepaint(); } }