Jump to: navigation, search

Scout/HowTo/3.9/Validation and Formatting

< Scout‎ | HowTo‎ | 3.9


Scout
Wiki Home
Website
DownloadGit
Community
ForumsBlogTwitter
Bugzilla
Bugzilla


This how-to describes how to validate and format FormFields and Forms


Field Validation

In order to validate field contents, overwrite the execValidateValue() method of a field:

ValidationExecValidate.png

In that method, return the input parameter rawValue (or a modified value based on rawValue) if the value is accepted or throw a VetoException() with a string describing the reason for not accepting the input.

@Override
protected Long execValidateValue(Long rawValue) throws ProcessingException {
  if ((rawValue % 2) != 0) {
    throw new VetoException("Value must be even.");
  }
  return rawValue;
}

Alternatively, the VetoException can also be created with a title string, an error code and a severity (though it seems that the severity is always set to IStatus.ERROR (4) internally)

throw new VetoException("Invalid input", "Value must be even.", 267, IStatus.ERROR);

If a VetoException is thrown, the field is marked with a red icon, hovering the cursor over the icon will display the string passed to the exception as tooltip.

ValidationMustBeEven.png

It is also possible to return a modified value from execValidateValue (e.g. to filter out spaces from numerical input etc.) without the need of full field formatting as described below.

Beside IStatus.ERROR there is also a IStatus.WARNING (2) available, which marks the field with a warning indicator.


Formatting a field

The basic mechanism used for formatted input and output is described on the Scout Wiki about ValueFields, specifically in the following drawing:

Scout ValueField Validation.png

Basically, the ParseValue() method is used to remove any formatting, ValidateValue() is used to check for valid input and only if validation passes will FormatValue() be called to write the formatted output back to the field. Also, the ChangedValue() method or a propertyChange() listener will be called.

The following example will take a carriage number (Wagennummer) as input (either formatted or unformatted), check it for validity and format it if it is correct. It will then also check the number's checksum and display an error if it is not correct.

execParseValue() removes any formatting information (spaces, dashes):

@Override
protected String execParseValue(String text) throws ProcessingException {
  String result = "";
  if (text != null) {
    result = text.replaceAll(" ", "").replaceAll("-", "");
  }
  return result;
} 

execFormatValue() will format the number using spaces and a dash:

@Override
protected String execFormatValue(String validValue) {
  String result = "";
  if (validValue != null) {
    for (int i = 0; i < validValue.length(); ++i) {
      result += validValue.charAt(i);
      if ((i == 1) || (i == 3) || (i == 7)) {
        result += " ";
      } else if (i == 10) {
        result += "-";
      }
    }
  }
  return result;
}

execValidateValue() will check the input for valid characters and correct length only (but not the validity of the number). Note that we first call clearErrorStatus() as the propertyChangeListener may have set an error status explicitly. Also note, that we have the possibility to further remove characters we do not want to keep (and re-display) by not including them in the value that is being returned.

@Override
protected String execValidateValue(String rawValue) throws ProcessingException {
  clearErrorStatus();
  String result = "";
  if (rawValue != null) {
    String text = rawValue.toLowerCase();
    for (int i = 0; i < rawValue.length(); ++i) {
      if (text.charAt(i) >= '0' && text.charAt(i) <= '9') {
        result += text.charAt(i);
      } else if (text.charAt(i) >= 'a' && text.charAt(i) <= 'z') {
        throw new VetoException(TEXTS.get("ErrNoAlpha"));
      }
    }
    if (result.length() != 12) {
      throw new VetoException(TEXTS.get("Err12Digits"));
    }
  } else {
    throw new VetoException(TEXTS.get("ErrNotEmpty"));
  }
  return result;
}

We also add a propertyChangeListener to the field to check the number for validity after it has been formatted. Should it be invalid, we can still mark the field as invalid (despite execValidateValue() having succeeded) by our call to setErrorStatus():

private void registerPropertyChangeListener() {
  addPropertyChangeListener(IValueField.PROP_VALUE, new PropertyChangeListener() {
    @Override
    public void propertyChange(PropertyChangeEvent e) {
      if (!WagenUtil.checkWagennummer((String) e.getNewValue())) {
        setErrorStatus(TEXTS.get("ErrWgNr"));
      }
    }
  });
}


Find below a few possible inputs and the reaction of the system:

Empty input:

ValidationCannotBeEmpty.png

Input too short (output unformatted)

ValidationInputTooShort.png

Illegal characters in input (output unformatted)

ValidationIllegalCharacters.png

Ignored characters in input (output unformatted due to other error)

ValidationIgnoredCharacters.png

Number detected as such, but invalid (output formatted)

ValidationInvalidNumber.png

Number detected and found to be valid (output formatted)

ValidationValidAndFormatted.png


Validating on any keypress

It is possible to force validation of input on any keypress. Currently this is only possible on StringFields (though a Bug has been opened to add support for this to numerical fields as well).

This is done by setting the ValidateOnAnyKey property:

@Override
protected boolean getConfiguredValidateOnAnyKey() {
  return true;
}

Currently the only benefit of doing this is the possibility to throw a veto exception on first input of illegal characters. Returning a modified value from execValidateValue() does not take effect until the field looses focus.


Form Validation

In order to validate a form when the "OK" or "Save" button is pressed, overwrite its execValidate() method. This method will only be called if all the contained fields' execValidateValue() methods have succesfully passed. It allows checking dependencies between fields. If the form passes validation, return true. If the form fails validation, throw a VetoException with an appropriate error message (which will be shown in a separate error dialog). If you want to mark certain fields in addition, do so by calling their setErrorStatus() method (though in that case, that fields execValidateValue() method must call clearErrorStatus() to make sure this error is cleared when its value is changed.

ValidationFormExecValidate.png

@Override
protected boolean execValidate() throws ProcessingException {
  if (!StringUtility.isNullOrEmpty(getPasswordField().getValue())) {
    if (StringUtility.isNullOrEmpty(getUsernameField().getValue())) {
      // if a password is provided, a username is mandatory
      getUsernameField().setErrorStatus(TEXTS.get("UsernameNeededWithPassword"));
      throw new VetoException(TEXTS.get("UsernameNeededWithPassword"));
    }
  }
  return true;
}

ValidationFormValidationResult.png


Showing validation results in the window header

A form needs to have the SubTitle property set for this to work:

@Override
protected String getConfiguredSubTitle() {
  return TEXTS.get("PersonDetails");
}

In addition, the SwtScoutDialog must be extended as follows:

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import org.eclipse.scout.commons.exception.IProcessingStatus;
import org.eclipse.scout.rt.client.ui.form.IForm;
import org.eclipse.scout.rt.client.ui.form.fields.IFormField;
import org.eclipse.scout.rt.ui.swt.ISwtEnvironment;
import org.eclipse.scout.rt.ui.swt.window.dialog.SwtScoutDialog;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.forms.IMessage;

public class ValidationSwtScoutDialog extends SwtScoutDialog {
  private P_FieldValidationPropertyChangeListener m_validationListener;

  public ValidationSwtScoutDialog(Shell parentShell, ISwtEnvironment environment, int style) {
    super(parentShell, environment, style);
  }

  @Override
  protected void attachScout(IForm form) {
    super.attachScout(form);

    if (m_validationListener == null) {
      m_validationListener = new P_FieldValidationPropertyChangeListener();
    }
    for (IFormField f : form.getAllFields()) {
      f.addPropertyChangeListener(IFormField.PROP_ERROR_STATUS, m_validationListener);
    }
  }

  @Override
  protected void detachScout(IForm form) {
    super.detachScout(form);

    for (IFormField f : form.getAllFields()) {
      f.removePropertyChangeListener(IFormField.PROP_ERROR_STATUS, m_validationListener);
    }
  }

  private void handleFieldErrorStatusChanged(IFormField formField, IProcessingStatus errorStatus) {
    if (errorStatus == null) {
      getSwtForm().getMessageManager().removeMessage(formField);
    }
    else {
      getSwtForm().getMessageManager().addMessage(formField, errorStatus.getMessage(), null, IMessage.ERROR);
    }
  }

  private class P_FieldValidationPropertyChangeListener implements PropertyChangeListener {
    @Override
    public void propertyChange(final PropertyChangeEvent e) {
      Runnable t = new Runnable() {

        @Override
        public void run() {
          if (IFormField.PROP_ERROR_STATUS.equals(e.getPropertyName())) {
            handleFieldErrorStatusChanged((IFormField) e.getSource(), (IProcessingStatus) e.getNewValue());
          }

        }
      };
      getEnvironment().invokeSwtLater(t);
    }
  }// end private class
}

This new class then needs to be returned by overwriting SwtEnvironment.createSwtScoutDialog:

@Override
protected SwtScoutDialog createSwtScoutDialog(Shell shell, int dialogStyle) {
  return new ValidationSwtScoutDialog(shell, this, dialogStyle);
}

Next, AbstractScoutView must be extended as follows:

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import org.eclipse.minicrm.ui.swt.Activator;
import org.eclipse.scout.commons.exception.IProcessingStatus;
import org.eclipse.scout.rt.client.ui.form.IForm;
import org.eclipse.scout.rt.client.ui.form.fields.IFormField;
import org.eclipse.scout.rt.ui.swt.window.desktop.view.AbstractScoutView;
import org.eclipse.ui.forms.IMessage;

/**
 * 
 */
public abstract class ValidationAbstractScoutView extends AbstractScoutView {
  private P_FieldValidationPropertyChangeListener m_validationListener;

  public ValidationAbstractScoutView() {
    super();
  }

  @Override
  protected void attachScout(IForm form) {
    super.attachScout(form);

    if (m_validationListener == null) {
      m_validationListener = new P_FieldValidationPropertyChangeListener();
    }
    for (IFormField f : form.getAllFields()) {
      f.addPropertyChangeListener(IFormField.PROP_ERROR_STATUS, m_validationListener);
    }
  }

  @Override
  protected void detachScout(IForm form) {
    super.detachScout(form);

    for (IFormField f : form.getAllFields()) {
      f.removePropertyChangeListener(IFormField.PROP_ERROR_STATUS, m_validationListener);
    }
  }

  private void handleFieldErrorStatusChanged(IFormField formField, IProcessingStatus errorStatus) {
    if (errorStatus == null) {
      getSwtForm().getMessageManager().removeMessage(formField);
    }
    else {
      getSwtForm().getMessageManager().addMessage(formField, errorStatus.getMessage(), null, IMessage.ERROR);
    }
  }

  private class P_FieldValidationPropertyChangeListener implements PropertyChangeListener {
    @Override
    public void propertyChange(final PropertyChangeEvent e) {
      Runnable t = new Runnable() {

        @Override
        public void run() {
          if (IFormField.PROP_ERROR_STATUS.equals(e.getPropertyName())) {
            handleFieldErrorStatusChanged((IFormField) e.getSource(), (IProcessingStatus) e.getNewValue());
          }

        }
      };
      Activator.getDefault().getEnvironment().invokeSwtLater(t);
    }
  }// end private class
}

Next, AbstractScoutEditorPart must be extended as follows:

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import org.eclipse.scout.commons.exception.IProcessingStatus;
import org.eclipse.scout.rt.client.ui.form.fields.IFormField;
import org.eclipse.scout.rt.ui.swt.window.desktop.editor.AbstractScoutEditorPart;
import org.eclipse.ui.forms.IMessage;

public abstract class ValidationAbstractScoutEditorPart extends AbstractScoutEditorPart {
  private P_FieldValidationPropertyChangeListener m_validationListener;

  public ValidationAbstractScoutEditorPart() {
    super();
  }

  @Override
  protected void attachScout() {
    super.attachScout();

    if (m_validationListener == null) {
      m_validationListener = new P_FieldValidationPropertyChangeListener();
    }
    for (IFormField f : getForm().getAllFields()) {
      f.addPropertyChangeListener(IFormField.PROP_ERROR_STATUS, m_validationListener);
    }
  }

  @Override
  protected void detachScout() {
    super.detachScout();

    for (IFormField f : getForm().getAllFields()) {
      f.removePropertyChangeListener(IFormField.PROP_ERROR_STATUS, m_validationListener);
    }
  }

  private void handleFieldErrorStatusChanged(IFormField formField, IProcessingStatus errorStatus) {
    if (errorStatus == null) {
      getSwtForm().getMessageManager().removeMessage(formField);
    }
    else {
      getSwtForm().getMessageManager().addMessage(formField, errorStatus.getMessage(), null, IMessage.ERROR);
    }
  }

  private class P_FieldValidationPropertyChangeListener implements PropertyChangeListener {
    @Override
    public void propertyChange(final PropertyChangeEvent e) {
      Runnable t = new Runnable() {

        @Override
        public void run() {
          if (IFormField.PROP_ERROR_STATUS.equals(e.getPropertyName())) {
            handleFieldErrorStatusChanged((IFormField) e.getSource(), (IProcessingStatus) e.getNewValue());
          }

        }
      };
      getSwtEnvironment().invokeSwtLater(t);
    }
  }// end private class
}

Now, the extended classes must be used instead of the original classes. In package org.eclipse.minicrm.ui.swt.views replace each class definition for XxxView to use ValidationAbstractScoutView:

public class CenterView extends ValidationAbstractScoutView {

Finally, we need to use ValidationAbstractScoutEditorPart by changing the class definition for ScoutEditorPart:

public class ScoutEditorPart extends ValidationAbstractScoutEditorPart {

With these changes, validation messages are not only shown in the tooltip of a field in error status but also in the header of the form

ValidationResultDisplaySubtitle.png