Package com.tll.client.ui.field

Source Code of com.tll.client.ui.field.AbstractField

/**
* The Logic Lab
* @author jpk Sep 14, 2007
*/
package com.tll.client.ui.field;

import java.util.List;

import com.allen_sauer.gwt.log.client.Log;
import com.google.gwt.event.dom.client.BlurEvent;
import com.google.gwt.event.dom.client.BlurHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Focusable;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.Widget;
import com.tll.client.convert.IConverter;
import com.tll.client.ui.BindableWidgetAdapter;
import com.tll.client.ui.IHasFormat;
import com.tll.client.util.Fmt;
import com.tll.client.util.GlobalFormat;
import com.tll.client.validate.BooleanValidator;
import com.tll.client.validate.CharacterValidator;
import com.tll.client.validate.CompositeValidator;
import com.tll.client.validate.DateValidator;
import com.tll.client.validate.DecimalValidator;
import com.tll.client.validate.Error;
import com.tll.client.validate.ErrorClassifier;
import com.tll.client.validate.ErrorDisplay;
import com.tll.client.validate.IErrorHandler;
import com.tll.client.validate.IValidator;
import com.tll.client.validate.IntegerValidator;
import com.tll.client.validate.NotEmptyValidator;
import com.tll.client.validate.ValidationException;
import com.tll.common.bind.IPropertyChangeListener;
import com.tll.common.model.PropertyPathException;
import com.tll.model.schema.IPropertyMetadataProvider;
import com.tll.model.schema.PropertyMetadata;
import com.tll.util.ObjectUtil;
import com.tll.util.StringUtil;

/**
* AbstractField - Base class for non-group {@link IField}s.
* @param <V> the value type
* @author jpk
*/
public abstract class AbstractField<V> extends Composite implements IFieldWidget<V>, ValueChangeHandler<V>, Focusable, BlurHandler {

  /**
   * Reflects the number of instantiated {@link AbstractField}s. This is
   * required to ensure a unique DOM id which is used by the associated
   * {@link FieldLabel}.
   * <p>
   * NOTE: The counter always increments and never decrements and is not
   * reflective of the actual number of fields referenced in the app.
   */
  private static int fieldCounter = 0;

  private static final String dfltReadOnlyEmptyValue = "-";

  private final BindableWidgetAdapter<V> adapter;

  /**
   * The unique DOM element id of this field.
   */
  private final String domId;

  /**
   * The unique field name.
   */
  private String name;

  /**
   * The full property path intended to uniquely identify this field relative to
   * a common root object.
   * <p>
   * <em>NOTE: This is distinct from the field's <code>name</code> property.
   */
  private String property;

  private boolean required = false;
  private boolean readOnly = false;
  private boolean enabled = true;

  /**
   * Text that appears when the mouse hovers over the field.
   */
  private String helpText;

  /**
   * The read-only field Widget.
   */
  private HTML rof;

  /**
   * The optional field label that is <em>not</em> a child of this Widget. This
   * class only *logically* owns the field label and <em>not</em> physically
   * (dom-wise).
   */
  private FieldLabel fldLbl;

  /**
   * The {@link Composite} widget containing the field's contents.
   */
  private final FlowPanel pnl = new FlowPanel();

  /**
   * The desired ancestor Widget reference for the field and the field label
   * necessary respectively ensuring certain styling rules are properly applied
   * since the field label is not necessarily a child of this Widget.
   */
  private Widget container, labelContainer;

  /**
   * The field validator(s).
   */
  private CompositeValidator validator;

  /**
   * The initial value and the current old value.
   */
  private V initialValue, oldValue;

  /**
   * Flag to indicate whether or not the initial value is set.
   */
  private boolean initialValueSet;

  private IErrorHandler errorHandler;

  /**
   * The incremental validation flag.
   */
  private boolean incrValFlag = true;

  /**
   * Constructor
   * @param name The field name which should be unique relative to any sibling
   *        fields under a common parent (field group).
   * @param propName The optional property name for binding purposes
   * @param labelText The optional field label text
   * @param helpText The options field help text that will appear when the mouse
   *        hovers.
   * @throws IllegalArgumentException When no field name is given
   */
  public AbstractField(String name, String propName, String labelText, String helpText) {
    setName(name);
    setPropertyName(propName);
    domId = 'f' + Integer.toString(++fieldCounter);

    // set the label
    setLabelText(labelText);

    // set the help text
    setHelpText(helpText);

    pnl.setStyleName(Styles.FIELD);

    initWidget(pnl);

    adapter = new BindableWidgetAdapter<V>(this);
  }

  @Override
  public final Object getProperty(String propPath) throws PropertyPathException {
    return adapter.getProperty(propPath);
  }

  @Override
  public final void setProperty(String propPath, Object value) throws PropertyPathException, IllegalArgumentException {
    adapter.setProperty(propPath, value);
  }

  @Override
  public final IConverter<V, Object> getConverter() {
    return adapter.getConverter();
  }

  @Override
  public final void setConverter(IConverter<V, Object> converter) {
    adapter.setConverter(converter);
  }

  @Override
  public final void addPropertyChangeListener(IPropertyChangeListener listener) {
    adapter.addPropertyChangeListener(listener);
  }

  @Override
  public final void addPropertyChangeListener(String propertyName, IPropertyChangeListener listener) {
    adapter.addPropertyChangeListener(propertyName, listener);
  }

  @Override
  public final void removePropertyChangeListener(IPropertyChangeListener listener) {
    adapter.removePropertyChangeListener(listener);
  }

  @Override
  public final void removePropertyChangeListener(String propertyName, IPropertyChangeListener listener) {
    adapter.removePropertyChangeListener(propertyName, listener);
  }

  @Override
  public final HandlerRegistration addValueChangeHandler(ValueChangeHandler<V> handler) {
    return addHandler(handler, ValueChangeEvent.getType());
  }

  /*
   * We need to honor the IField contract and this method is declated soley to make it publicly visible.
   */
  @Override
  public final Widget getWidget() {
    return this;
  }

  @Override
  public final String descriptor() {
    return getLabelText();
  }

  /**
   * Sets the Widget to be the field's containing Widget. This is necessary to
   * apply certain styling to the appropriate dom node.
   * @param fieldContainer The desired containing Widget
   */
  public final void setFieldContainer(Widget fieldContainer) {
    this.container = fieldContainer;
  }

  /**
   * Sets the Widget to be the field label's containing Widget. This is
   * necessary to apply certain styling to the appropriate dom node.
   * @param labelContainer The desired Widget containing the label
   */
  public final void setFieldLabelContainer(Widget labelContainer) {
    this.labelContainer = labelContainer;
  }

  /**
   * @return The {@link FieldLabel}. <br>
   *         <em>NOTE: </em>The field label is <em>NOT</em> a child of this
   *         composite Widget.
   */
  public final FieldLabel getFieldLabel() {
    return fldLbl;
  }

  public String getLabelText() {
    return fldLbl == null ? "" : fldLbl.getText();
  }

  /**
   * Set the field's associated label.
   * @param labelText The label text. If <code>null</code>, the label will be
   *        removed.
   */
  public void setLabelText(String labelText) {
    if(labelText != null) {
      if(fldLbl == null) {
        fldLbl = new FieldLabel();
        fldLbl.setFor(domId);
      }
      fldLbl.setText(labelText);
    }
  }

  /**
   * @return the domId
   */
  public final String getDomId() {
    return domId;
  }

  public final String getName() {
    return name;
  }

  public final void setName(String name) {
    if(StringUtil.isEmpty(name)) {
      throw new IllegalArgumentException("A field must have a name.");
    }
    this.name = name;
  }

  public final String getPropertyName() {
    return property;
  }

  public final void setPropertyName(String propName) {
    this.property = propName;
  }

  public final void clearValue() {
    setValue(null);
    // remove all msgs, edit and validation styling
    resolveError(ErrorDisplay.ALL_FLAGS);
    removeStyleName(Styles.DIRTY);
    this.initialValue = null;
    this.initialValueSet = false;
    this.oldValue = null;
  }

  public final boolean isReadOnly() {
    return readOnly;
  }

  public void setReadOnly(boolean readOnly) {
    // hide the field label required indicator if we are in read-only state
    if(fldLbl != null) fldLbl.setRequired(readOnly ? false : required);

    this.readOnly = readOnly;
    if(isAttached()) draw();
  }

  public final boolean isRequired() {
    return required;
  }

  public final void setRequired(boolean required) {
    // show/hide the field label required indicator
    if(fldLbl != null) {
      fldLbl.setRequired(readOnly ? false : required);
    }
    this.required = required;
    if(required) {
      addValidator(NotEmptyValidator.INSTANCE);
    }
    else {
      removeValidator(NotEmptyValidator.class);
    }
  }

  public final boolean isEnabled() {
    return enabled;
  }

  public void setEnabled(boolean enabled) {
    this.enabled = enabled;
    if(isAttached()) draw();
  }

  /**
   * We override to handle setting the visibility for the label as well as the
   * visibility to the field and label containers.
   */
  @Override
  public final void setVisible(boolean visible) {
    super.setVisible(visible);

    if(fldLbl != null) {
      fldLbl.setVisible(visible);
    }

    if(container != null) {
      container.setVisible(visible);
    }
    if(labelContainer != null) {
      labelContainer.setVisible(visible);
    }
  }

  /**
   * @return the helpText
   */
  public final String getHelpText() {
    return helpText;
  }

  /**
   * @param helpText the helpText to set
   */
  public final void setHelpText(String helpText) {
    if(!ObjectUtil.equals(this.helpText, helpText)) {
      this.helpText = helpText;
      if(isAttached()) draw();
    }
  }

  /**
   * Obtains the editable form control Widget.
   * @return The form control Widget
   */
  public abstract IEditable<V> getEditable();

  /**
   * Either marks or un-marks this field as dirty in the UI based on its current
   * value and the set initial value.
   */
  private void dirtyCheck() {
    if(initialValueSet) {
      if(!ObjectUtil.equals(initialValue, getValue())) {
        addStyleName(Styles.DIRTY);
      }
      else {
        removeStyleName(Styles.DIRTY);
      }
    }
  }

  public final void addValidator(IValidator vldtr) {
    if(vldtr != null) {
      if(this.validator == null) {
        this.validator = new CompositeValidator();
      }
      this.validator.remove(vldtr.getClass());
      this.validator.add(vldtr);
    }
  }

  public final void applyPropertyMetadata(IPropertyMetadataProvider provider, boolean isNewModelData) {
    // Log.debug("AbstractField.applyPropertyMetadata() for " + toString());
    final PropertyMetadata metadata = provider.getPropertyMetadata(getPropertyName());
    if(metadata == null) {
      Log.warn("No property metadata found for field: " + toString());
    }
    else {
      // requiredness
      setRequired(metadata.isRequired() && !metadata.isManaged());

      // maxlength
      if(this instanceof IHasMaxLength) {
        final int maxlen = metadata.getMaxLen();
        ((IHasMaxLength) this).setMaxLen(maxlen);
      }

      final GlobalFormat format = (this instanceof IHasFormat) ? ((IHasFormat) this).getFormat() : null;

      // set the type coercion validator
      switch(metadata.getPropertyType()) {
      case STRING:
      case ENUM:
        // no type coercion validator needed
        break;

      case BOOL:
        addValidator(BooleanValidator.INSTANCE);
        break;

      case DATE:
        addValidator(DateValidator.get(format == null ? GlobalFormat.DATE : format));
        break;

      case INT:
        addValidator(IntegerValidator.INSTANCE);
        break;

      case FLOAT:
      case DOUBLE:
        addValidator(new DecimalValidator(Fmt.getDecimalFormat(format == null ? GlobalFormat.DECIMAL : format)));
        break;

      case CHAR:
        addValidator(CharacterValidator.INSTANCE);
        break;

      case LONG:
        // TODO handle - intentional fall through
      case STRING_MAP:
        // TODO handle string map type coercion - intentional fall through
      default:
        throw new IllegalStateException("Unhandled property type: " + metadata.getPropertyType().name());
      }
    }
    // apply model new flag
    validateIncrementally(!isNewModelData);
  }

  public final void removeValidator(Class<? extends IValidator> type) {
    if(validator != null) validator.remove(type);
  }

  /**
   * Resolves client-side errors.
   * @param displayFlags
   */
  private void resolveError(int displayFlags) {
    if(errorHandler != null) {
      errorHandler.resolveError(this, ErrorClassifier.CLIENT, displayFlags);
    }
  }

  /**
   * Handles client-side errors.
   * @param errors
   */
  private void handleError(List<Error> errors) {
    if(errorHandler != null) {
      errorHandler.handleErrors(errors, ErrorDisplay.LOCAL.flag());
    }
  }

  public final void validate() throws ValidationException {
    validate(getValue());
  }

  public final Object validate(Object value) throws ValidationException {
    if(validator != null) {
      try {
        value = validator.validate(value);
      }
      catch(final ValidationException e) {
        for(final Error error : e.getErrors()) {
          error.setTarget(this);
        }
        throw e;
      }
    }
    return value;
  }

  private void draw() {

    Widget formWidget;

    // assemble?
    // NOTE: we *always* have the editable form control part of the DOM so as to
    // make sure change type events are handled etc.
    // the editable form control widget drives the field's behavior and is the
    // master
    formWidget = (Widget) getEditable();
    if(pnl.getWidgetCount() == 0) {
      formWidget.getElement().setPropertyString("id", domId);
      pnl.add(formWidget);
    }
    else if(formWidget != pnl.getWidget(0)) {
      pnl.insert(formWidget, 0);
    }
    assert formWidget != null;
    formWidget.setTitle(helpText);

    if(readOnly) {
      if(pnl.getWidgetCount() != 2) {
        assert rof == null;
        rof = new HTML();
        pnl.add(rof);
      }
      assert rof != null;
      // set help text
      rof.setTitle(helpText);

      String sval = getText();
      sval = StringUtil.isEmpty(sval) ? dfltReadOnlyEmptyValue : sval;
      rof.setText(sval);
    }

    // set readonly/editable display
    if(rof != null) rof.setVisible(readOnly);
    formWidget.setVisible(!readOnly);

    // apply disabled property
    // formWidget.getElement().setPropertyBoolean(Styles.DISABLED, !enabled);

    // resolve the containers
    final Widget fldContainer = container == null ? this : container;
    final Widget lblContainer = fldLbl == null ? null : labelContainer == null ? fldLbl : labelContainer;

    // apply enabled property to "containing" widget
    if(enabled) {
      fldContainer.removeStyleName(Styles.DISABLED);
      if(lblContainer != null) lblContainer.removeStyleName(Styles.DISABLED);
    }
    else {
      fldContainer.addStyleName(Styles.DISABLED);
      if(lblContainer != null) lblContainer.addStyleName(Styles.DISABLED);
    }

    if(!enabled || readOnly) {
      // remove all msgs, edit and validation styling
      resolveError(ErrorDisplay.LOCAL.flag());
      removeStyleName(Styles.DIRTY);
    }
    else if(enabled && !readOnly) {
      // show/hide edit styling
      dirtyCheck();
    }
  }

  @Override
  public final int getTabIndex() {
    return getEditable().getTabIndex();
  }

  @Override
  public final void setTabIndex(int index) {
    if(!readOnly) getEditable().setTabIndex(index);
  }

  @Override
  public final void setAccessKey(char key) {
    if(!readOnly) getEditable().setAccessKey(key);
  }

  @Override
  public final void setFocus(boolean focused) {
    if(!readOnly) getEditable().setFocus(focused);
  }

  @Override
  public void onBlur(BlurEvent event) {
    if(incrValFlag) {
      resolveError(ErrorDisplay.LOCAL.flag())// clear out old first
      try {
        validate();
        dirtyCheck();
      }
      catch(final ValidationException e) {
        handleError(e.getErrors());
      }
    }
  }

  @Override
  public final void onValueChange(ValueChangeEvent<V> event) {
    assert event.getSource() == getEditable();
    final V old = oldValue;
    oldValue = event.getValue();
    if(!ObjectUtil.equals(old, oldValue)) {
      ValueChangeEvent.fire(this, oldValue);
      // we don't want auto-transfer!!!
      //adapter.getChangeSupport().firePropertyChange(PROPERTY_VALUE, old, oldValue);
    }
  }

  @Override
  public final V getValue() {
    return getEditable().getValue();
  }

  @Override
  public final void setValue(V value) {
    setValue(value, false);
  }

  @Override
  public final void setValue(V value, boolean fireEvents) {
    getEditable().setValue(value, fireEvents);
    if(!initialValueSet) {
      initialValue = getValue();
      initialValueSet = true;
    }
  }

  public final void reset() {
    if(initialValueSet) {
      setValue(initialValue);
      resolveError(ErrorDisplay.ALL_FLAGS);
      removeStyleName(Styles.DIRTY);
    }
  }

  @Override
  public final IErrorHandler getErrorHandler() {
    return errorHandler;
  }

  @Override
  public final void setErrorHandler(IErrorHandler errorHandler) {
    this.errorHandler = errorHandler;
  }

  @Override
  public final void validateIncrementally(boolean validate) {
    this.incrValFlag = validate;
  }

  @Override
  protected void onLoad() {
    super.onLoad();
    draw();
  }

  @Override
  protected void onUnload() {
    super.onUnload();
  }

  /**
   * Fields are considered if their property names match.
   */
  @SuppressWarnings("unchecked")
  @Override
  public final boolean equals(Object obj) {
    if(this == obj) return true;
    if(obj == null) return false;
    if(getClass() != obj.getClass()) return false;
    return (!name.equals(((AbstractField) obj).name));
  }

  @Override
  public final int hashCode() {
    return name.hashCode();
  }

  @Override
  public final String toString() {
    return property == null ? name : name + " [ " + property + " ]";
  }
}
TOP

Related Classes of com.tll.client.ui.field.AbstractField

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.