All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.vaadin.ui.AbstractField Maven / Gradle / Ivy

Go to download

Vaadin is a web application framework for Rich Internet Applications (RIA). Vaadin enables easy development and maintenance of fast and secure rich web applications with a stunning look and feel and a wide browser support. It features a server-side architecture with the majority of the logic running on the server. Ajax technology is used at the browser-side to ensure a rich and interactive user experience.

There is a newer version: 24.4.10
Show newest version
/* 
 * Copyright 2011 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.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.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.event.Action;
import com.vaadin.event.ActionManager;
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 * 6.8.8 * @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 Object value; /** * 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; /** * Keeps track of the Actions added to this component; the actual * handling/notifying is delegated, usually to the containing window. */ private ActionManager actionManager; 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; /* 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; } /* * Gets the field type Don't add a JavaDoc comment here, we use the default * documentation from the implemented interface. */ 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 Object newValue = getValue(); try { // Commits the value to datasource. valueWasModifiedByDataSourceDuringCommit = false; committingValueToDataSource = true; dataSource.setValue(newValue); } 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 (modified) { modified = 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 { updateValueFromDataSource(); } /* * 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; } /* * 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 && dataSource != null) { setInternalValue(String.class == getType() ? dataSource.toString() : dataSource.getValue()); fireValueChange(false); } } /* Property interface implementation */ /** * Returns the value of the Property in human readable textual format. * * @see java.lang.Object#toString() */ @Override public String toString() { final Object value = getValue(); if (value == null) { return null; } return getValue().toString(); } /** * Gets the current value of the field. * *

* This is the visible, modified and possible invalid value the user have * entered to the field. In the read-through mode, the abstract buffer is * also updated and validation is performed. *

* *

* 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 datasources * native type, use getPropertyDatasource().getValue() instead. *

* *

* Note that when you extend AbstractField, you must reimplement this method * if datasource.getValue() is not assignable to class returned by getType() * AND getType() is not String. In case of Strings, getValue() calls * datasource.toString() instead of datasource.getValue(). *

* * @return the current value of the field. */ public Object getValue() { // Give the value from abstract buffers if the field if possible if (dataSource == null || !isReadThrough() || isModified()) { return value; } Object newValue = String.class == getType() ? dataSource.toString() : dataSource.getValue(); return newValue; } /** * Sets the value of the field. * * @param newValue * the New value of the field. * @throws Property.ReadOnlyException * @throws Property.ConversionException */ public void setValue(Object newValue) throws Property.ReadOnlyException, Property.ConversionException { setValue(newValue, false); } /** * Sets the value of the field. * * @param newValue * 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(Object newValue, boolean repaintIsNotNeeded) throws Property.ReadOnlyException, Property.ConversionException { if ((newValue == null && value != null) || (newValue != null && !newValue.equals(value))) { // 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(newValue); } } } // Changes the value setInternalValue(newValue); modified = 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; dataSource.setValue(newValue); // The buffer is now unmodified modified = 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); } } /* 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. *

* *

* If the data source implements * {@link com.vaadin.data.Property.ValueChangeNotifier} and/or * {@link com.vaadin.data.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. */ public void setPropertyDataSource(Property newDataSource) { // Saves the old value final Object oldValue = value; // Stop listening to the old data source removePropertyListeners(); // Sets the new data source dataSource = newDataSource; // Gets the value from source try { if (dataSource != null) { setInternalValue(String.class == getType() ? dataSource .toString() : dataSource.getValue()); } modified = false; } catch (final Throwable e) { currentBufferedSourceException = new Buffered.SourceException(this, e); modified = true; } // 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 Iterator i = validators.iterator(); i .hasNext();) { addValidator(i.next()); } } } // Fires value change if the value has changed if ((value != oldValue) && ((value != null && !value.equals(oldValue)) || value == null)) { fireValueChange(false); } } /* 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(); } /** * Removes all validators from the field. */ public void removeAllValidators() { if (validators != null) { validators.clear(); } 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. * * @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() { if (isEmpty()) { if (isRequired()) { return false; } else { return true; } } if (validators == null) { return true; } final Object value = getValue(); for (final Iterator i = validators.iterator(); i.hasNext();) { if (!(i.next()).isValid(value)) { return false; } } return true; } /** * 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 value = getValue(); // Gets all the validation errors for (final Iterator i = validators.iterator(); i.hasNext();) { try { (i.next()).validate(value); } 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 * 6.8.8 * @since 3.0 */ public 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 = event .getProperty().getValue() == value || (value != null && value.equals(event.getProperty() .getValue())); 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(event.getProperty().getValue()); } @Override public void changeVariables(Object source, Map variables) { super.changeVariables(source, variables); } /** * {@inheritDoc} */ @Override public void focus() { super.focus(); } /** * Creates abstract field by the type of the property. * *

* This returns most suitable field type for editing property of given type. *

* * @param propertyType * the Type of the property, that needs to be edited. * @deprecated use e.g. * {@link DefaultFieldFactory#createFieldByPropertyType(Class)} * instead */ @Deprecated public static AbstractField constructField(Class propertyType) { return (AbstractField) DefaultFieldFactory .createFieldByPropertyType(propertyType); } /* * (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(); } /** * 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(Object newValue) { value = newValue; if (validators != null && !validators.isEmpty()) { requestRepaint(); } } /** * Notifies the component that it is connected to an application. * * @see com.vaadin.ui.Component#attach() */ @Override public void attach() { super.attach(); if (actionManager != null) { actionManager.setViewer(getWindow()); } if (!isListeningToPropertyEvents) { addPropertyListeners(); if (!isModified() && isReadThrough()) { // The property value may have changed while the field was // detached and not listening updateValueFromDataSource(); } } } @Override public void detach() { super.detach(); if (actionManager != null) { actionManager.setViewer((Window) null); } // Stop listening to data source events on detach to avoid a potential // memory leak. See #6155. removePropertyListeners(); } /** * 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 (getValue() == 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(); } /* * Actions */ /** * Gets the {@link ActionManager} used to manage the * {@link ShortcutListener}s added to this {@link Field}. * * @return the ActionManager in use */ protected ActionManager getActionManager() { if (actionManager == null) { actionManager = new ActionManager(); if (getWindow() != null) { actionManager.setViewer(getWindow()); } } return actionManager; } public void addShortcutListener(ShortcutListener shortcut) { getActionManager().addAction(shortcut); } public void removeShortcutListener(ShortcutListener shortcut) { if (actionManager != null) { actionManager.removeAction(shortcut); } } /** * 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(); } } private void updateValueFromDataSource() { if (dataSource != null) { // Gets the correct value from datasource Object newValue; try { // Discards buffer by overwriting from datasource newValue = String.class == getType() ? dataSource.toString() : dataSource.getValue(); // If successful, remove set the buffering state to be ok if (currentBufferedSourceException != null) { currentBufferedSourceException = null; requestRepaint(); } } catch (final Throwable e) { // Sets the buffering state currentBufferedSourceException = new Buffered.SourceException( this, e); requestRepaint(); // Throws the source exception throw currentBufferedSourceException; } final boolean wasModified = isModified(); modified = false; // If the new value differs from the previous one if ((newValue == null && value != null) || (newValue != null && !newValue.equals(value))) { setInternalValue(newValue); fireValueChange(false); } // If the value did not change, but the modification status did else if (wasModified) { requestRepaint(); } } } /** * Registers this as an event listener for events sent by the data source * (if any). Does nothing if * isListeningToPropertyEvents == true. */ private void addPropertyListeners() { if (!isListeningToPropertyEvents) { if (dataSource instanceof Property.ValueChangeNotifier) { ((Property.ValueChangeNotifier) dataSource).addListener(this); } if (dataSource instanceof Property.ReadOnlyStatusChangeNotifier) { ((Property.ReadOnlyStatusChangeNotifier) dataSource) .addListener(this); } isListeningToPropertyEvents = true; } } /** * Stops listening to events sent by the data source (if any). Does nothing * if isListeningToPropertyEvents == false. */ private void removePropertyListeners() { if (isListeningToPropertyEvents) { if (dataSource instanceof Property.ValueChangeNotifier) { ((Property.ValueChangeNotifier) dataSource) .removeListener(this); } if (dataSource instanceof Property.ReadOnlyStatusChangeNotifier) { ((Property.ReadOnlyStatusChangeNotifier) dataSource) .removeListener(this); } isListeningToPropertyEvents = false; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy