com.extjs.gxt.ui.client.widget.form.Field.java Source code

Java tutorial

Introduction

Here is the source code for com.extjs.gxt.ui.client.widget.form.Field.java

Source

/*
 * Sencha GXT 2.3.1 - Sencha for GWT
 * Copyright(c) 2007-2013, Sencha, Inc.
 * licensing@sencha.com
 * 
 * http://www.sencha.com/products/gxt/license/
 */
package com.extjs.gxt.ui.client.widget.form;

import com.extjs.gxt.ui.client.GXT;
import com.extjs.gxt.ui.client.Style.HideMode;
import com.extjs.gxt.ui.client.core.El;
import com.extjs.gxt.ui.client.core.XDOM;
import com.extjs.gxt.ui.client.event.BaseEvent;
import com.extjs.gxt.ui.client.event.ComponentEvent;
import com.extjs.gxt.ui.client.event.Events;
import com.extjs.gxt.ui.client.event.FieldEvent;
import com.extjs.gxt.ui.client.event.KeyListener;
import com.extjs.gxt.ui.client.event.Listener;
import com.extjs.gxt.ui.client.util.DelayedTask;
import com.extjs.gxt.ui.client.util.Format;
import com.extjs.gxt.ui.client.util.KeyNav;
import com.extjs.gxt.ui.client.util.Util;
import com.extjs.gxt.ui.client.widget.BoxComponent;
import com.extjs.gxt.ui.client.widget.ComponentHelper;
import com.extjs.gxt.ui.client.widget.WidgetComponent;
import com.extjs.gxt.ui.client.widget.layout.FormLayout;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.InputElement;
import com.google.gwt.safehtml.client.SafeHtmlTemplates;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.AbstractImagePrototype;

/**
 * Base class for form fields that provides default event handling, value
 * handling and other functionality.
 * 
 * <dl>
 * <dt><b>Events:</b></dt>
 * <dd><b>Focus</b> : FieldEvent(field)<br>
 * <div>Fires when this field receives input focus.</div>
 * <ul>
 * <li>field : this</li>
 * </ul>
 * </dd>
 * <dd><b>Blur</b> : FieldEvent(field)<br>
 * <div>Fires when this field loses input focus.</div>
 * <ul>
 * <li>field : this</li>
 * </ul>
 * </dd>
 * <dd><b>Change</b> : FieldEvent(field, value, oldValue)<br>
 * <div>Fires after the field's value is changed.</div>
 * <ul>
 * <li>field : this</li>
 * <li>value : the new value</li>
 * <li>oldValue : the old value</li>
 * </ul>
 * </dd>
 * <dd><b>Invalid</b> : FieldEvent(field, message)<br>
 * <div>Fires after the field has been marked as invalid.</div>
 * <ul>
 * <li>field : this</li>
 * <li>message : the validation message</li>
 * </ul>
 * </dd>
 * <dd><b>Valid</b> : FieldEvent(field)<br>
 * <div>Fires after the field has been validated with no errors.</div>
 * <ul>
 * <li>field : this</li>
 * </ul>
 * </dd>
 * <dd><b>KeyPress</b> : FieldEvent(field)<br>
 * <div>Fires after a key is pressed.</div>
 * <ul>
 * <li>field : this</li>
 * </ul>
 * </dd>
 * <dd><b>SpecialKey</b> : FieldEvent(field)<br>
 * <div>Fires when any key related to navigation (arrows, tab, enter, esc, etc.)
 * is pressed.</div>
 * <ul>
 * <li>field : this</li>
 * </ul>
 * </dd>
 * </dl>
 * 
 * <dl>
 * <dt>Inherited Events:</dt>
 * <dd>BoxComponent Move</dd>
 * <dd>BoxComponent Resize</dd>
 * <dd>Component Enable</dd>
 * <dd>Component Disable</dd>
 * <dd>Component BeforeHide</dd>
 * <dd>Component Hide</dd>
 * <dd>Component BeforeShow</dd>
 * <dd>Component Show</dd>
 * <dd>Component Attach</dd>
 * <dd>Component Detach</dd>
 * <dd>Component BeforeRender</dd>
 * <dd>Component Render</dd>
 * <dd>Component BrowserEvent</dd>
 * <dd>Component BeforeStateRestore</dd>
 * <dd>Component StateRestore</dd>
 * <dd>Component BeforeStateSave</dd>
 * <dd>Component SaveState</dd>
 * </dl>
 * 
 * @param <D> the data type of the field
 */
@SuppressWarnings("deprecation")
public abstract class Field<D> extends BoxComponent {

    public class FieldImages {
        private AbstractImagePrototype invalid = GXT.IMAGES.field_invalid();

        public AbstractImagePrototype getInvalid() {
            return invalid;
        }

        public void setInvalid(AbstractImagePrototype invalid) {
            this.invalid = invalid;
        }

    }

    /**
     * The field messages.
     */
    public class FieldMessages {

        private String invalidText = GXT.MESSAGES.field_invalidText();

        /**
         * Returns the invalid text.
         * 
         * @return the text
         */
        public String getInvalidText() {
            return invalidText;
        }

        /**
         * Sets the error text to use when marking a field invalid and no message is
         * provided (defaults to "The value in this field is invalid").
         * 
         * @param invalidText the invalid text
         */
        public void setInvalidText(String invalidText) {
            this.invalidText = invalidText;
        }

    }

    interface Templates extends SafeHtmlTemplates {

        @Template("{0}{1}")
        SafeHtml fieldLabel(String fieldName, String separator);
    }

    private static final Templates TEMPLATES = GWT.create(Templates.class);

    protected boolean autoValidate;
    protected String emptyText;
    protected WidgetComponent errorIcon;
    protected String fieldStyle = "x-form-field";
    protected String focusStyle = "x-form-focus";
    protected Object focusValue;
    protected String forceInvalidText;
    protected boolean hasFocus;
    protected FieldImages images;
    protected String invalidStyle = "x-form-invalid";
    protected FieldMessages messages;
    protected String name;
    protected D originalValue;
    protected boolean preventMark;
    protected PropertyEditor<D> propertyEditor;
    protected boolean readOnly;
    protected String readOnlyFieldStyle = "x-form-readonly";
    protected int validationDelay = 250;
    protected DelayedTask validationTask;

    protected D value;
    private String activeErrorMessage;
    private String fieldLabel = "";
    private boolean fireChangeEventOnSetValue;
    private boolean hideLabel;
    private boolean inEditor;
    private String inputStyle;
    private String inputStyles = "";
    private String labelSeparator;
    private String labelStyle = "";

    private String messageTarget = "side";
    private int tabIndex = 0;

    private boolean validateOnBlur = true;

    /**
     * Creates a new field.
     */
    @SuppressWarnings("unchecked")
    protected Field() {
        propertyEditor = (PropertyEditor<D>) PropertyEditor.DEFAULT;
        messages = new FieldMessages();
    }

    /**
     * Adds a CSS style name to the input element of this field.
     * 
     * @param style the style name
     */
    public void addInputStyleName(String style) {
        if (rendered) {
            El inputEl = getInputEl();
            if (inputEl != null) {
                inputEl.addStyleName(style);
            }
        } else {
            inputStyle = inputStyle == null ? style : inputStyle + " " + style;
        }
    }

    /**
     * Adds a key listener.
     * 
     * @param listener the key listener
     */
    public void addKeyListener(KeyListener listener) {
        addListener(Events.KeyPress, listener);
        addListener(Events.KeyUp, listener);
        addListener(Events.KeyDown, listener);
    }

    /**
     * Clears the value from the field.
     */
    public void clear() {
        boolean restore = preventMark;
        preventMark = true;
        setValue(null);
        preventMark = restore;
        clearInvalid();
    }

    /**
     * Clear any invalid styles / messages for this field.
     */
    public void clearInvalid() {
        if (!rendered) {
            return;
        }
        getInputEl().removeStyleName(invalidStyle);

        if (forceInvalidText != null) {
            forceInvalidText = null;
        }

        if ("side".equals(messageTarget)) {
            if (errorIcon != null && errorIcon.isAttached()) {
                ComponentHelper.doDetach(errorIcon);
                errorIcon.setVisible(false);
                setAriaState("aria-describedby", "");
            }
        } else if ("title".equals(messageTarget)) {
            setTitle("");
        } else if ("tooltip".equals(messageTarget)) {
            hideToolTip();
            if (toolTip != null) {
                toolTip.disable();
            }
        } else {
            Element elem = XDOM.getElementById(messageTarget);
            if (elem != null) {
                elem.setInnerHTML("");
            }
        }
        if (GXT.isAriaEnabled()) {
            getAriaSupport().setState("aria-invalid", "false");
        }
        fireEvent(Events.Valid, new FieldEvent(this));
    }

    @Override
    public void focus() {
        super.focus();
        if (rendered) {
            onFocus(new FieldEvent(this));
        }
    }

    /**
     * Forces the field to be invalid using the given error message. When using
     * this feature, {@link #clearInvalid()} must be called to clear the error.
     * Also, no other validation logic will execute.
     * 
     * @param msg the error text
     */
    public void forceInvalid(String msg) {
        forceInvalidText = msg;
        markInvalid(msg);
    }

    /**
     * Returns true if the field value is validated on each key press.
     * 
     * @return the auto validate state
     */
    public boolean getAutoValidate() {
        return autoValidate;
    }

    /**
     * Returns the field's empty text.
     * 
     * @return the empty text
     */
    public String getEmptyText() {
        return emptyText;
    }

    /**
     * Returns the active error message as string
     * 
     * @return the active error message
     */

    public String getErrorMessage() {
        return activeErrorMessage;
    }

    /**
     * Returns the field's label.
     * 
     * @return the label
     */
    public String getFieldLabel() {
        return fieldLabel;
    }

    public FieldImages getImages() {
        if (images == null) {
            images = new FieldImages();
        }
        return images;
    }

    /**
     * Returns the field's label separator.
     * 
     * @return the label separator
     */
    public String getLabelSeparator() {
        return labelSeparator;
    }

    /**
     * Returns the field's label style.
     * 
     * @return the label style
     */
    public String getLabelStyle() {
        return labelStyle;
    }

    /**
     * Returns the field's messages.
     * 
     * @return the messages
     */
    public FieldMessages getMessages() {
        return messages;
    }

    /**
     * Returns the field's message target.
     * 
     * @return the message target
     */
    public String getMessageTarget() {
        return messageTarget;
    }

    /**
     * Returns the name attribute of the field if available.
     * 
     * @return the field name
     */
    public String getName() {
        if (rendered) {
            String n = getInputEl().dom.getAttribute("name");
            if (!n.equals("")) {
                return n;
            }
        }
        return name;
    }

    /**
     * Returns the original value of the field, which is the value of the field
     * when it is first rendered.
     * 
     * @return the original value
     */
    public D getOriginalValue() {
        return originalValue;
    }

    /**
     * Returns the field's property editor.
     * 
     * @return the property editor
     */
    public PropertyEditor<D> getPropertyEditor() {
        return propertyEditor;
    }

    /**
     * Returns the raw data value which may or may not be a valid, defined value.
     * To return a normalized value see {@link #getValue}.
     * 
     * @return the raw value
     */
    public String getRawValue() {
        String v = rendered ? getInputEl().getValue() : "";
        if (v == null || v.equals(emptyText)) {
            return "";
        }
        return v;
    }

    /**
     * Returns true if the value is validate on blur.
     * 
     * @return the validate on blur state
     */
    public boolean getValidateOnBlur() {
        return validateOnBlur;
    }

    /**
     * Returns the field's validation delay in milliseconds.
     * 
     * @return the delay in milliseconds
     */
    public int getValidationDelay() {
        return validationDelay;
    }

    /**
     * Returns the typed value of the field.
     * 
     * @return the fields value
     */
    public D getValue() {
        if (!rendered) {
            return value;
        }
        String v = getRawValue();
        if (emptyText != null && v.equals(emptyText)) {
            return null;
        }
        if (v == null || v.equals("")) {
            return null;
        }
        try {
            return propertyEditor.convertStringValue(v);
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * Returns <code>true</code> if this field is dirty. A field is dirty, if the
     * current value is different than it's original value. The original value is
     * the value of the field when the field is rendered. Disabled and
     * pre-rendered fields are never dirty.
     * 
     * @return the dirty state
     */
    public boolean isDirty() {
        if (disabled || !rendered) {
            return false;
        }
        return !Util.equalWithNull(getValue(), originalValue);
    }

    /**
     * Returns true if a change event is fired when {@link #setValue(Object)} is
     * called.
     * 
     * @return true if the change event is fired
     */
    public boolean isFireChangeEventOnSetValue() {
        return fireChangeEventOnSetValue;
    }

    /**
     * Returns true if the label is hidden.
     * 
     * @return the hide label state
     */
    public boolean isHideLabel() {
        return hideLabel;
    }

    /**
     * Returns true if the field is inside an editor.
     * 
     * @return true if inside an editor
     */
    public boolean isInEditor() {
        return inEditor;
    }

    /**
     * Returns the read only state.
     * 
     * @return <code>true</code> if read only, otherwise <code>false</code>
     */
    public boolean isReadOnly() {
        return readOnly;
    }

    public boolean isValid() {
        return isValid(false);
    }

    /**
     * Returns whether or not the field value is currently valid.
     * 
     * @param preventMark true for silent validation (no invalid event and field
     *          is not marked invalid)
     * 
     * @return <code>true</code> if the value is valid, otherwise
     *         <code>false</code>
     */
    public boolean isValid(boolean preventMark) {
        if (disabled) {
            return true;
        }
        boolean restore = this.preventMark;
        this.preventMark = preventMark;
        boolean result = validateValue(getRawValue());
        if (result) {
            activeErrorMessage = null;
        }
        this.preventMark = restore;
        return result;
    }

    /**
     * Marks this field as invalid. Validation will still run if called again, and
     * the error message will be changed or cleared based on validation. To set a
     * error message that will not be cleared until manually cleared see
     * {@link #forceInvalid(String)}
     * 
     * @param msg the validation message treated as HTML
     */
    public void markInvalid(String msg) {
        msg = Format.htmlEncode(msg == null ? getMessages().getInvalidText() : msg);
        activeErrorMessage = msg;
        if (!rendered || preventMark) {
            return;
        }
        getInputEl().addStyleName(invalidStyle);

        if ("side".equals(messageTarget)) {
            if (errorIcon == null) {
                errorIcon = new WidgetComponent(getImages().getInvalid().createImage());
                Element p = el().getParent().dom;
                errorIcon.render(p);
                errorIcon.setHideMode(HideMode.VISIBILITY);
                errorIcon.hide();
                errorIcon.setStyleAttribute("display", "block");
                errorIcon.el().makePositionable(true);
                errorIcon.getAriaSupport().setRole("alert");
                if (GXT.isAriaEnabled()) {
                    setAriaState("aria-describedby", errorIcon.getId());
                    errorIcon.setTitle(getErrorMessage());
                }

            } else if (!errorIcon.el().isConnected()) {
                Element p = el().getParent().dom;
                p.appendChild(errorIcon.getElement());
            }
            if (!errorIcon.isAttached()) {
                ComponentHelper.doAttach(errorIcon);
            }

            alignErrorIcon();
            if (GXT.isIE || GXT.isOpera) {
                alignErrorIcon();
            }
            // needed to prevent flickering
            DeferredCommand.addCommand(new Command() {
                public void execute() {
                    if (errorIcon.isAttached()) {
                        errorIcon.show();
                    }
                }
            });
            errorIcon.setToolTip(msg);
            errorIcon.getToolTip().addStyleName("x-form-invalid-tip");
            el().repaint();
        } else if ("title".equals(messageTarget)) {
            setTitle(msg);
        } else if ("tooltip".equals(messageTarget)) {
            setToolTip(msg);
            getToolTip().addStyleName("x-form-invalid-tip");
            getToolTip().enable();
        } else if ("none".equals(messageTarget)) {
            // do nothing
        } else {
            Element elem = XDOM.getElementById(messageTarget);
            if (elem != null) {
                elem.setInnerHTML(msg);
            }
        }

        if (GXT.isAriaEnabled()) {
            setAriaState("aria-invalid", "true");
        }

        FieldEvent fe = new FieldEvent(this);
        fe.setMessage(msg);
        fireEvent(Events.Invalid, fe);
    }

    public void onComponentEvent(ComponentEvent ce) {
        super.onComponentEvent(ce);
        FieldEvent fe = new FieldEvent(this);
        fe.setEvent(ce.getEvent());
        switch (ce.getEventTypeInt()) {
        case Event.ONFOCUS:
            onFocus(ce);
            break;
        case Event.ONBLUR:
            if (inEditor && GXT.isWindows && GXT.isGecko) {
                final ComponentEvent e = ce;
                DeferredCommand.addCommand(new Command() {
                    public void execute() {
                        onBlur(e);
                    }
                });
            } else {
                onBlur(ce);
            }
            break;
        case Event.ONCLICK:
            // in some cases, focus event is not fired when event preview was
            // active when the click is fired
            if (!hasFocus) {
                focus();
            }
            onClick(ce);
            break;
        case Event.ONKEYUP:
            onKeyUp(fe);
            break;
        case Event.ONKEYDOWN:
            onKeyDown(fe);
            if (KeyNav.getKeyEvent().getEventCode() == Event.ONKEYDOWN) {
                fireKey(fe);
            }
            break;
        case Event.ONKEYPRESS:
            onKeyPress(fe);
            if (KeyNav.getKeyEvent().getEventCode() == Event.ONKEYPRESS) {
                fireKey(fe);
            }
            break;
        }
    }

    /**
     * Removes a CSS style name from the input element of this field.
     * 
     * @param style the style name
     */
    public void removeInputStyleName(String style) {
        if (rendered) {
            El inputEl = getInputEl();
            if (inputEl != null) {
                inputEl.removeStyleName(style);
            }
        } else if (inputStyle != null && style != null) {
            String[] s = inputStyle.split(" ");
            inputStyle = "";
            for (int i = 0; i < s.length; i++) {
                if (!s[i].equals(style)) {
                    inputStyle += " " + s[i];
                }
            }
        }
    }

    /**
     * Removes the key listener.
     * 
     * @param listener the key listener
     */
    public void removeKeyListener(KeyListener listener) {
        removeListener(Events.KeyPress, listener);
        removeListener(Events.KeyUp, listener);
        removeListener(Events.KeyDown, listener);
    }

    /**
     * Resets the current field value to the originally loaded value and clears
     * any validation messages.
     */
    public void reset() {
        boolean restore = preventMark;
        preventMark = true;
        setValue(originalValue);
        preventMark = restore;
        clearInvalid();
    }

    /**
     * Sets whether the value is validated on each key press (defaults to false).
     * 
     * @param autoValidate true to validate on each key press
     */
    public void setAutoValidate(boolean autoValidate) {
        if (!this.autoValidate && autoValidate) {
            validationTask = new DelayedTask(new Listener<BaseEvent>() {
                public void handleEvent(BaseEvent be) {
                    validate();
                }
            });
        } else if (!autoValidate && validationTask != null) {
            validationTask.cancel();
            validationTask = null;
        }
        this.autoValidate = autoValidate;
    }

    /**
     * Sets the default text to display in an empty field.
     * 
     * @param emptyText the empty text
     */
    public void setEmptyText(String emptyText) {
        this.emptyText = emptyText;
    }

    /**
     * Sets the field's label.
     * 
     * @param fieldLabel the label treated as text
     */
    public void setFieldLabel(String fieldLabel) {
        this.fieldLabel = fieldLabel;
        if (rendered) {
            El elem = findLabelElement();
            if (elem != null) {
                elem.setInnerHtml(TEMPLATES.fieldLabel(fieldLabel, labelSeparator));
            }
        }
    }

    /**
     * True to fire a change event when {@link #setValue(Object)} is called
     * (defaults to false).
     * 
     * @param fireChangeEventOnSetValue true to fire a change event
     */
    public void setFireChangeEventOnSetValue(boolean fireChangeEventOnSetValue) {
        this.fireChangeEventOnSetValue = fireChangeEventOnSetValue;
    }

    /**
     * True to completely hide the label element (defaults to false, pre-render).
     * 
     * @param hideLabel true to hide the label
     */
    public void setHideLabel(boolean hideLabel) {
        this.hideLabel = hideLabel;
    }

    public void setImages(FieldImages images) {
        this.images = images;
    }

    /**
     * True to mark this field being in an editor.
     * 
     * @param inEditor true mark this field being in an editor
     */
    public void setInEditor(boolean inEditor) {
        this.inEditor = inEditor;
    }

    /**
     * Sets a style attribute on the input element.
     * 
     * @param attr the attribute
     * @param value the attribute value
     */
    public void setInputStyleAttribute(String attr, String value) {
        if (rendered) {
            getInputEl().setStyleAttribute(attr, value);
        } else {
            inputStyles += attr + ":" + value + ";";
        }
    }

    /**
     * The standard separator to display after the text of each form label
     * (defaults to the value of {@link FormLayout#setLabelSeparator(String)},
     * which is a colon ':' by default).
     * 
     * @param labelSeparator the label separator or "" for none
     */
    public void setLabelSeparator(String labelSeparator) {
        this.labelSeparator = labelSeparator;
    }

    /**
     * A CSS style specification to apply directly to this field's label. For
     * example, <code>labelStyle: 'font-weight:bold;'</code>
     * 
     * @param labelStyle the label style
     */
    public void setLabelStyle(String labelStyle) {
        assertPreRender();
        this.labelStyle = labelStyle;
    }

    /**
     * Sets the field's messages.
     * 
     * @param messages the messages
     */
    public void setMessages(FieldMessages messages) {
        this.messages = messages;
    }

    /**
     * The location where error text should display. Should be one of the
     * following values (defaults to 'side'): <code><pre>
     * Value         Description
     * -----------   ----------------------------------------------------------------------
     * tooltip       Display a tool tip when the user hovers over the field
     * title         Display a default browser title attribute popup
     * side          Add an error icon to the right of the field with a popup on hover
     * none          Do not display an error message
     * [element id]  Add the error text directly to the innerHTML of the specified element
     * </pre></code>
     * 
     * @param messageTarget the message target
     */
    public void setMessageTarget(String messageTarget) {
        this.messageTarget = messageTarget;
    }

    /**
     * Sets the field's HTML name attribute.
     * 
     * @param name the name
     */
    public void setName(String name) {
        this.name = name;
        if (rendered) {
            // we need to remove the old one first, else ie6 goes crazy.
            getInputEl().dom.removeAttribute("name");
            if (name != null) {
                ((InputElement) getInputEl().dom.cast()).setName(name);
            }
        }
    }

    /**
     * Updates the original value of the field. The originalValue is the value of
     * the field when it is rendered. This method is useful when a form / field is
     * being reused and the originaValue needs to be reset.
     * 
     * @param originalValue the original value
     */
    public void setOriginalValue(D originalValue) {
        this.originalValue = originalValue;
    }

    /**
     * Sets the field's property editor which is used to translate typed values to
     * string, and string values back to typed values.
     * 
     * @param propertyEditor the property editor
     */
    public void setPropertyEditor(PropertyEditor<D> propertyEditor) {
        this.propertyEditor = propertyEditor;
    }

    /**
     * Sets the underlying DOM field's value directly, bypassing validation. To
     * set the value with validation see {@link #setValue}.
     * 
     * @param value the raw value
     */
    public void setRawValue(String value) {
        if (rendered) {
            getInputEl().setValue(value == null ? "" : value);
        }
    }

    /**
     * Sets the field's read only state.
     * 
     * @param readOnly the read only state
     */
    public void setReadOnly(boolean readOnly) {
        this.readOnly = readOnly;
        if (rendered) {
            el().setStyleName(readOnlyFieldStyle, readOnly);
            getInputEl().dom.setPropertyBoolean("readOnly", readOnly);
        }
    }

    /**
     * Sets the tab index.
     * 
     * @param index the tab index value
     */
    public void setTabIndex(int index) {
        this.tabIndex = index;
        if (rendered) {
            getInputEl().dom.setPropertyInt("tabIndex", index);
        }
    }

    /**
     * Sets whether the field should validate when it loses focus (defaults to
     * true).
     * 
     * @param validateOnBlur true to validate on blur, otherwise false
     */
    public void setValidateOnBlur(boolean validateOnBlur) {
        this.validateOnBlur = validateOnBlur;
    }

    /**
     * Sets length of time in milliseconds after user input begins until
     * validation is initiated (defaults to 250).
     * 
     * @param validationDelay the delay in milliseconds
     */
    public void setValidationDelay(int validationDelay) {
        this.validationDelay = validationDelay;
    }

    /**
     * Sets a data value into the field and validates it. If the field is
     * rendered, To set the value directly without validation see
     * {@link #setRawValue}.
     * 
     * @param value the value to set
     */
    public void setValue(D value) {
        D oldValue = this.value;
        this.value = value;
        if (rendered) {
            String v = value == null ? "" : propertyEditor.getStringValue(value);
            setRawValue(v);
            validate(preventMark);
        }
        if (fireChangeEventOnSetValue) {
            fireChangeEvent(oldValue, value);
        }
    }

    /**
     * Updates the original value of the field. This method is useful when a form
     * / field is being reused and the originaValue needs to be reset.
     * 
     * @param value the new original value
     */
    public void updateOriginalValue(D value) {
        originalValue = value;
    }

    /**
     * Validates the field value.
     * 
     * @return <code>true</code> if valid, otherwise <code>false</code>
     */
    public boolean validate() {
        return validate(false);
    }

    /**
     * Validates the field value.
     * 
     * @param preventMark true to not mark the field valid and fire invalid event
     *          when invalid
     * @return <code>true</code> if valid, otherwise <code>false</code>
     */
    public boolean validate(boolean preventMark) {
        if (disabled) {
            clearInvalid();
            return true;
        }
        boolean restore = this.preventMark;
        this.preventMark = preventMark;
        boolean result = validateValue(getRawValue());
        this.preventMark = restore;
        if (result) {
            clearInvalid();
            activeErrorMessage = null;

        }
        return result;
    }

    @Override
    protected void afterRender() {
        super.afterRender();
        initValue();
    }

    protected void alignErrorIcon() {
        DeferredCommand.addCommand(new Command() {
            public void execute() {
                errorIcon.el().alignTo(getElement(), "tl-tr", new int[] { 2, 3 });
            }
        });
    }

    @Override
    protected ComponentEvent createComponentEvent(Event event) {
        return new FieldEvent(this);
    }

    @Override
    protected void doDetachChildren() {
        super.doDetachChildren();
        if (errorIcon != null && errorIcon.isAttached()) {
            errorIcon.setVisible(false);
            ComponentHelper.doDetach(errorIcon);
        }
    }

    protected El findLabelElement() {
        if (rendered) {
            El elem = el().findParent(".x-form-item", 5);
            if (elem != null) {
                return elem.firstChild();
            }
        }
        return null;
    }

    protected void fireChangeEvent(Object oldValue, Object value) {
        if (!Util.equalWithNull(oldValue, value)) {
            FieldEvent e = new FieldEvent(this);
            e.setOldValue(oldValue);
            e.setValue(value);
            fireEvent(Events.Change, e);
        }
    }

    protected void fireKey(FieldEvent fe) {
        if (fe.isSpecialKey()) {
            fireEvent(Events.SpecialKey, fe);
        }
    }

    /**
     * Provides support for wrapping the actual input element.
     * 
     * @return the input element
     */
    protected El getInputEl() {
        return el();
    }

    protected El getStyleEl() {
        return el();
    }

    protected void initValue() {
        if (value != null) {
            setValue(value);
        }
    }

    protected void onBlur(ComponentEvent be) {
        if (hasFocus) {
            if (getInputEl() != null) {
                getInputEl().removeStyleName(focusStyle);
            }
            hasFocus = false;
            if (validateOnBlur) {
                validate();
            }

            D v = getValue();
            value = v;
            fireChangeEvent(focusValue, v);
            fireEvent(Events.Blur, new FieldEvent(this));
        }
    }

    protected void onClick(ComponentEvent ce) {

    }

    @Override
    protected void onDetach() {
        if (validationTask != null) {
            validationTask.cancel();
        }
        super.onDetach();
    }

    @Override
    protected void onDisable() {
        super.onDisable();
        getInputEl().disable();
    }

    @Override
    protected void onEnable() {
        super.onEnable();
        getInputEl().enable();
    }

    protected void onFocus(ComponentEvent ce) {
        if (!hasFocus) {
            if (getInputEl() != null) {
                getInputEl().addStyleName(focusStyle);
            }
            hasFocus = true;
            focusValue = getValue();
            fireEvent(Events.Focus, new FieldEvent(this));
        }
    }

    @Override
    protected void onHide() {
        super.onHide();
        if (errorIcon != null && errorIcon.isAttached()) {
            errorIcon.hide();
        }
    }

    protected void onKeyDown(FieldEvent fe) {
        fireEvent(Events.KeyDown, new FieldEvent(this, fe.getEvent()));
    }

    protected void onKeyPress(FieldEvent fe) {
        fireEvent(Events.KeyPress, new FieldEvent(this, fe.getEvent()));
    }

    protected void onKeyUp(FieldEvent fe) {
        if (autoValidate && validationTask != null) {
            validationTask.delay(validationDelay);
        }
        fireEvent(Events.KeyUp, new FieldEvent(this, fe.getEvent()));
    }

    @Override
    protected void onRender(Element parent, int index) {
        super.onRender(parent, index);

        addStyleName(fieldStyle);

        String type = getInputEl().dom.getAttribute("type");
        if (type.equals("password")) {
            type = "text";
        }
        if (!type.equals("")) {
            getInputEl().addStyleName("x-form-" + type);
        }

        setName(name);

        if (readOnly) {
            setReadOnly(true);
        }

        setTabIndex(tabIndex);

        if (inputStyle != null) {
            addInputStyleName(inputStyle);
            inputStyle = null;
        }

        if (inputStyles != null && !inputStyles.equals("")) {
            getInputEl().applyStyles(inputStyles);
            inputStyles = null;
        }

        originalValue = value;
        getInputEl().addEventsSunk(Event.FOCUSEVENTS);
        sinkEvents(Event.ONCLICK | Event.MOUSEEVENTS | Event.FOCUSEVENTS | Event.KEYEVENTS);
    }

    @Override
    protected void onResize(int width, int height) {
        super.onResize(width, height);
        if (errorIcon != null && errorIcon.isAttached()) {
            alignErrorIcon();
        }
    }

    @Override
    protected void onShow() {
        super.onShow();
        if (errorIcon != null && errorIcon.isAttached()) {
            errorIcon.show();
        }
    }

    /**
     * Subclasses should provide the validation implementation by overriding this.
     * 
     * @param value the value to validate
     * 
     * @return <code>true</code> for valid
     */
    protected boolean validateValue(String value) {
        if (forceInvalidText != null) {
            markInvalid(forceInvalidText);
            return false;
        }
        return true;
    }

}