com.vaadin.v7.ui.AbstractField Maven / Gradle / Ivy
Show all versions of vaadin-compatibility-server Show documentation
/*
* Copyright (C) 2000-2024 Vaadin Ltd
*
* This program is available under Vaadin Commercial License and Service Terms.
*
* See for the full
* license.
*/
package com.vaadin.v7.ui;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import org.jsoup.nodes.Attributes;
import org.jsoup.nodes.Element;
import com.vaadin.event.Action;
import com.vaadin.event.ShortcutAction;
import com.vaadin.event.ShortcutListener;
import com.vaadin.server.AbstractErrorMessage;
import com.vaadin.server.CompositeErrorMessage;
import com.vaadin.server.ErrorMessage;
import com.vaadin.shared.util.SharedUtil;
import com.vaadin.ui.Component;
import com.vaadin.ui.declarative.DesignAttributeHandler;
import com.vaadin.ui.declarative.DesignContext;
import com.vaadin.v7.data.Buffered;
import com.vaadin.v7.data.BufferedValidatable;
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.util.converter.Converter;
import com.vaadin.v7.data.util.converter.Converter.ConversionException;
import com.vaadin.v7.data.util.converter.ConverterUtil;
import com.vaadin.v7.shared.AbstractFieldState;
/**
*
* 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 Property} interface. AbstractField
* implements that interface itself, too, so accessing the Property value
* represented by it is straightforward.
*
*
*
* AbstractField also provides the {@link Buffered} interface for buffering the
* data source value. By default the Field is in write through-mode and
* {@link Buffered#setBuffered(boolean)} should be called to enable buffering.
*
*
*
* The class also supports {@link Validator validators} to make sure the value
* contained in the field is valid.
*
*
* @author Vaadin Ltd.
* @since 3.0
*
* @deprecated This class is, apart from the rename, identical to the Vaadin 7
* {@code com.vaadin.ui.AbstractField}. It is provided for
* compatibility and migration purposes. As of 8.0, new field
* implementations should extend the new
* {@link com.vaadin.ui.AbstractField} instead.
*/
@Deprecated
public abstract class AbstractField extends AbstractLegacyComponent
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 converter = null;
/**
* Connected data-source.
*/
private Property> dataSource = null;
/**
* The list of validators.
*/
private LinkedList validators = null;
/**
* True if field is in buffered mode, false otherwise
*/
private boolean buffered;
/**
* 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 error message for the exception that is thrown when the field is
* required but empty.
*/
private String requiredError = "";
/**
* The error message that is shown when the field value cannot be converted.
*/
private String conversionError = "Could not convert value to {0}";
/**
* Is automatic validation enabled.
*/
private boolean validationVisible = true;
private boolean valueWasModifiedByDataSourceDuringCommit;
/**
* Whether this field is currently registered as listening to events from
* its data source.
*
* @see #setPropertyDataSource(Property)
* @see #addPropertyListeners()
* @see #removePropertyListeners()
*/
private boolean isListeningToPropertyEvents = false;
/**
* The locale used when setting the value.
*/
private Locale valueLocale = null;
/* Component basics */
/*
* Paints the field. Don't add a JavaDoc comment here, we use the default
* documentation from the implemented interface.
*/
/**
* 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() {
// getErrorMessage() can still return something else than null based on
// validation etc.
return isRequired() && isEmpty() && getComponentError() == 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
*/
@Override
public abstract Class extends T> 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 AbstractLegacyComponent#setReadOnly(boolean)
*/
@Override
public void setReadOnly(boolean readOnly) {
super.setReadOnly(readOnly);
fireReadOnlyStatusChange();
}
/**
* Tests if the invalid data is committed to datasource.
*
* @see BufferedValidatable#isInvalidCommitted()
*/
@Override
public boolean isInvalidCommitted() {
return invalidCommitted;
}
/**
* Sets if the invalid data should be committed to datasource.
*
* @see BufferedValidatable#setInvalidCommitted(boolean)
*/
@Override
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.
*/
@Override
public void commit()
throws Buffered.SourceException, InvalidValueException {
if (dataSource != null && !dataSource.isReadOnly()) {
if (isInvalidCommitted() || isValid()) {
try {
// Commits the value to datasource.
valueWasModifiedByDataSourceDuringCommit = false;
committingValueToDataSource = true;
getPropertyDataSource().setValue(getConvertedValue());
} catch (final Throwable e) {
// Sets the buffering state.
SourceException sourceException = new Buffered.SourceException(
this, e);
setCurrentBufferedSourceException(sourceException);
// Throws the source exception.
throw sourceException;
} finally {
committingValueToDataSource = false;
}
} else {
/*
* An invalid value and we don't allow them, throw the exception
*/
validate();
}
}
// The abstract field is not modified anymore
if (isModified()) {
setModified(false);
}
// If successful, remove set the buffering state to be ok
if (getCurrentBufferedSourceException() != null) {
setCurrentBufferedSourceException(null);
}
if (valueWasModifiedByDataSourceDuringCommit) {
valueWasModifiedByDataSourceDuringCommit = false;
fireValueChange(false);
}
}
/*
* Updates the value from the data source. Don't add a JavaDoc comment here,
* we use the default documentation from the implemented interface.
*/
@Override
public void discard() throws Buffered.SourceException {
updateValueFromDataSource();
}
/**
* Gets the value from the data source. This is only here because of clarity
* in the code that handles both the data model value and the field value.
*
* @return The value of the property data source
*/
private Object getDataSourceValue() {
return dataSource.getValue();
}
/**
* Returns the field value. This is always identical to {@link #getValue()}
* and only here because of clarity in the code that handles both the data
* model value and the field value.
*
* @return The value of the field
*/
private T getFieldValue() {
// Give the value from abstract buffers if the field if possible
if (dataSource == null || isBuffered() || isModified()) {
return getInternalValue();
}
// There is no buffered value so use whatever the data model provides
return convertFromModel(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.
*/
@Override
public boolean isModified() {
return getState(false).modified;
}
private void setModified(boolean modified) {
getState().modified = modified;
}
/**
* Sets the buffered mode of this Field.
*
* When the field is in buffered mode, changes will not be committed to the
* property data source until {@link #commit()} is called.
*
*
* Setting buffered mode from true to false will commit any pending changes.
*
*
*
*
*
* @since 7.0.0
* @param buffered
* true if buffered mode should be turned on, false otherwise
*/
@Override
public void setBuffered(boolean buffered) {
if (this.buffered == buffered) {
return;
}
this.buffered = buffered;
if (!buffered) {
commit();
}
}
/**
* Checks the buffered mode of this Field.
*
* @return true if buffered mode is on, false otherwise
*/
@Override
public boolean isBuffered() {
return buffered;
}
// LegacyPropertyHelper has been removed in Vaadin 8
/* Property interface implementation */
/**
* 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 converted value,
* use {@link #getConvertedValue()} and to access the value of the property
* data source, use {@link Property#getValue()} for the property data
* source.
*
*
*
* Since Vaadin 7.0, no implicit conversions between other data types and
* String are performed, but a converter is used if set.
*
*
* @return the current value of the field.
*/
@Override
public T getValue() {
return getFieldValue();
}
/**
* Sets the value of the field.
*
* @param newFieldValue
* the New value of the field.
* @throws Property.ReadOnlyException
*/
@Override
public void setValue(T newFieldValue)
throws Property.ReadOnlyException, Converter.ConversionException {
setValue(newFieldValue, false);
}
/**
* Sets the value of the field.
*
* @param newFieldValue
* the New value of the field.
* @param repaintIsNotNeeded
* True if caller is sure that repaint is not needed.
* @throws Property.ReadOnlyException
* @throws Converter.ConversionException
* @throws InvalidValueException
*/
protected void setValue(T newFieldValue, boolean repaintIsNotNeeded) {
setValue(newFieldValue, repaintIsNotNeeded, false);
}
/**
* Sets the value of the field.
*
* @since 7.5.7
* @param newFieldValue
* the New value of the field.
* @param repaintIsNotNeeded
* True if caller is sure that repaint is not needed.
* @param ignoreReadOnly
* True if the read-only check should be ignored
* @throws Property.ReadOnlyException
* @throws Converter.ConversionException
* @throws InvalidValueException
*/
protected void setValue(T newFieldValue, boolean repaintIsNotNeeded,
boolean ignoreReadOnly) throws Property.ReadOnlyException,
Converter.ConversionException, InvalidValueException {
if (!SharedUtil.equals(newFieldValue, getInternalValue())) {
// Read only fields can not be changed
if (!ignoreReadOnly && isReadOnly()) {
throw new Property.ReadOnlyException();
}
try {
T doubleConvertedFieldValue = convertFromModel(
convertToModel(newFieldValue));
if (!SharedUtil.equals(newFieldValue,
doubleConvertedFieldValue)) {
newFieldValue = doubleConvertedFieldValue;
repaintIsNotNeeded = false;
}
} catch (Throwable t) {
// Ignore exceptions in the conversion at this stage. Any
// conversion error will be handled later by validate().
}
// Repaint is needed even when the client thinks that it knows the
// new state if validity of the component may change
if (repaintIsNotNeeded && (isRequired() || hasValidators()
|| getConverter() != null)) {
repaintIsNotNeeded = false;
}
if (!isInvalidAllowed()) {
/*
* If invalid values are not allowed the value must be validated
* before it is set. If validation fails, the
* InvalidValueException is thrown and the internal value is not
* updated.
*/
validate(newFieldValue);
}
// Changes the value
setInternalValue(newFieldValue);
setModified(dataSource != null);
valueWasModifiedByDataSourceDuringCommit = false;
// In not buffering, try to commit
if (!isBuffered() && dataSource != null
&& (isInvalidCommitted() || isValid())) {
try {
// Commits the value to datasource
committingValueToDataSource = true;
getPropertyDataSource()
.setValue(convertToModel(newFieldValue));
// The buffer is now unmodified
setModified(false);
} catch (final Throwable e) {
// Sets the buffering state
currentBufferedSourceException = new Buffered.SourceException(
this, e);
markAsDirty();
// Throws the source exception
throw currentBufferedSourceException;
} finally {
committingValueToDataSource = false;
}
}
// If successful, remove set the buffering state to be ok
if (getCurrentBufferedSourceException() != null) {
setCurrentBufferedSourceException(null);
}
if (valueWasModifiedByDataSourceDuringCommit) {
/*
* Value was modified by datasource. Force repaint even if
* repaint was not requested.
*/
valueWasModifiedByDataSourceDuringCommit = repaintIsNotNeeded = false;
}
// Fires the value change
fireValueChange(repaintIsNotNeeded);
}
}
@Deprecated
static boolean equals(Object value1, Object value2) {
return SharedUtil.equals(value1, 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.
*/
@Override
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.
*
*
*
* If the data source implements {@link Property.ValueChangeNotifier} and/or
* {@link Property.ReadOnlyStatusChangeNotifier}, the field registers itself
* as a listener and updates itself according to the events it receives. To
* avoid memory leaks caused by references to a field no longer in use, the
* listener registrations are removed on {@link AbstractField#detach()
* detach} and re-added on {@link AbstractField#attach() attach}.
*
*
*
* 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.
*/
@Override
public void setPropertyDataSource(Property newDataSource) {
// Saves the old value
final Object oldValue = getInternalValue();
// Stop listening to the old data source
removePropertyListeners();
// Sets the new data source
dataSource = newDataSource;
getState().propertyReadOnly = dataSource == null ? false
: dataSource.isReadOnly();
// Check if the current converter is compatible.
if (newDataSource != null
&& !ConverterUtil.canConverterPossiblyHandle(getConverter(),
getType(), newDataSource.getType())) {
// There is no converter set or there is no way the current
// converter can be compatible.
setConverter(newDataSource.getType());
}
// Gets the value from source. This requires that a valid converter has
// been set.
try {
if (dataSource != null) {
T fieldValue = convertFromModel(getDataSourceValue());
setInternalValue(fieldValue);
}
setModified(false);
if (getCurrentBufferedSourceException() != null) {
setCurrentBufferedSourceException(null);
}
} catch (final Throwable e) {
setCurrentBufferedSourceException(
new Buffered.SourceException(this, e));
setModified(true);
throw getCurrentBufferedSourceException();
}
// Listen to new data source if possible
addPropertyListeners();
// Copy the validators from the data source
if (dataSource instanceof Validatable) {
final Collection validators = ((Validatable) dataSource)
.getValidators();
if (validators != null) {
for (final Validator v : validators) {
addValidator(v);
}
}
}
// Fires value change if the value has changed
T value = getInternalValue();
if (value != oldValue && (value != null && !value.equals(oldValue)
|| value == null)) {
fireValueChange(false);
}
}
/**
* Retrieves a converter for the field from the converter factory defined
* for the application. Clears the converter if no application reference is
* available or if the factory returns null.
*
* @param datamodelType
* The type of the data model that we want to be able to convert
* from
*/
public void setConverter(Class> datamodelType) {
Converter c = (Converter) ConverterUtil
.getConverter(getType(), datamodelType, getSession());
setConverter(c);
}
/**
* Convert the given value from the data source type to the UI type.
*
* @param newValue
* The data source value to convert.
* @return The converted value that is compatible with the UI type or the
* original value if its type is compatible and no converter is set.
* @throws Converter.ConversionException
* if there is no converter and the type is not compatible with
* the data source type.
*/
private T convertFromModel(Object newValue) {
return convertFromModel(newValue, getLocale());
}
/**
* Convert the given value from the data source type to the UI type.
*
* @param newValue
* The data source value to convert.
* @return The converted value that is compatible with the UI type or the
* original value if its type is compatible and no converter is set.
* @throws Converter.ConversionException
* if there is no converter and the type is not compatible with
* the data source type.
*/
private T convertFromModel(Object newValue, Locale locale) {
return ConverterUtil.convertFromModel(newValue, getType(),
getConverter(), locale);
}
/**
* Convert the given value from the UI type to the data source type.
*
* @param fieldValue
* The value to convert. Typically returned by
* {@link #getFieldValue()}
* @return The converted value that is compatible with the data source type.
* @throws Converter.ConversionException
* if there is no converter and the type is not compatible with
* the data source type.
*/
private Object convertToModel(T fieldValue)
throws Converter.ConversionException {
return convertToModel(fieldValue, getLocale());
}
/**
* Convert the given value from the UI type to the data source type.
*
* @param fieldValue
* The value to convert. Typically returned by
* {@link #getFieldValue()}
* @param locale
* The locale to use for the conversion
* @return The converted value that is compatible with the data source type.
* @throws Converter.ConversionException
* if there is no converter and the type is not compatible with
* the data source type.
*/
private Object convertToModel(T fieldValue, Locale locale)
throws Converter.ConversionException {
Class> modelType = getModelType();
try {
return ConverterUtil.convertToModel(fieldValue,
(Class