Archive

Archive for October, 2011

Introduction to SmartClient 8.1/SmartGWT 2.5 Validation (part 3)

October 19, 2011 Comments off

Up to this point of the introduction series you have seen how to apply validators to fields and create custom validators. Now we take a look validation features to control when validation occurs and how errors are shown.

Controlling validation

As has been shown previously validation can be performed on a form by calling myForm.validate() at any time. This is not the only way to trigger validation. A single form item can be validated by calling myFormItem.validate() as well. In both cases the return value is a boolean indicating if the validation was successful.

In addition to manually triggered form validation, a form is always validated before saving. The save is not performed if validation fails. The same process is repeated on the SmartClient Server Framework before a save is completed. Validation failures are reported back to the client and save is aborted.

Client-side validation can be disabled on a form by setting myForm.disableValidation to true. When set no validation is performed on calls to myForm.validate() or saveData(). Server validation is still performed on save. This setting does not affect any field-level validation triggers.

Automatic field-level validation

Form fields can be validated automatically on loss of focus (field exit) or on each value change. To enable validation on change set myFormItem.validateOnChange to true. Validation on the field will occur after the transformInput() handler has been called and before the form item’s change() handler. If validation fails the value change will not be saved into the field value nor will the change() handler be called.

Validating on a change is useful when the value in the field should always be in a valid state. This doesn’t work well for a date because it is invalid by design during partial entry. It should only be valid when leaving the field. Enter validateOnExit. By setting this property to true, the field will be validated just before calling the field’s editorExit() handler.

Normally, field focus moves to the next field even when validation fails when validateOnExit is true. To force the user to remain in the invalid field instead, set myFormItem.stopOnError to true. This implies that myFormItem.synchronousValidation is enabled so server-only validators are processed synchronously. The synchronousValidation setting is covered in a later in Advanced features.

Setting hierarchy

In many cases the FormItem settings can also be made at the DynamicForm level. For example, if all fields with validators should be validated when leaving the field, set myForm.validateOnExit to true. This validation will be performed if either myForm or myFormItem has validateOnExit true.

The validateOnChange setting is slightly different. This setting can be made at the form, field or validator level. If either the form or field validateOnChange is true validation is performed on each value change. Validations are also performed for any validator marked validateOnChange:true. However, any validator explicitly marked with validateOnChange:false is skipped during this validation step no matter what the other settings are. Some validators just don’t apply during the change phase.

Field dependencies

When working with custom validators they likely refer to more than one field on the record. Consider a custom validator that validates that one field is greater than another. If either field is changed the validator on the other field must be re-evaluated. This can be accomplished by providing the list of field dependencies for the validator as dependentFields:

Assume there is a pre-defined validator called “isGreaterThan” that compares the current field value against the current value of another field. The following DataSource will enforce that max is greater than min whenever the record is saved or validated or the max field value is changed. What happens when the min field value is changed? Nothing.

<DataSource ID="reorder">
    <fields>
        <field name="min" title="Min. Qty" type="integer" required="true"/>
        <field name="max" title="Max. Qty" type="integer" required="true">
            <validators>
                <validator type="isGreaterThan" otherField=”min” validateOnChange=”true” />
            </validators>
        </field>
    </fields>
</DataSource>

By adding dependentFields to the min field we can fix this situation:

<DataSource ID="reorder">
    <fields>
        <field name="min" title="Min. Qty" type="integer" required="true"/>
        <field name="max" title="Max. Qty" type="integer" required="true">
            <validators>
                <validator type="isGreaterThan" otherField=”min” validateOnChange=”true”>
                    <dependentFields>
                        <value>min</value>
                    </dependentFields>
                </validator>
            </validators>
        </field>
    </fields>
</DataSource>

Now, whenever the field value of min changes, the validator on max will be fired. This occurs even if validateOnChange is not true on the min field.

There is one other way to provide dependencies: declare a conditional validator. This will be covered in a later section.

Error display styles

SmartClient supports many different error display styles. These settings can even be mixed on a form per form item. By default errors are shown to the left of the field with just an error icon. Hovering the mouse over the mouse displays the full text of the error(s). To accomplish this, SC uses the following DynamicForm default settings:

showErrorIcons : true
showErrorText : false
showErrorStyle: true
showInlineErrors : true
errorOrientation : “left”

A FormItem has many of the same properties to control the display of errors as the DynamicForm. If no override value is provided at the field level, the DynamicForm settings applies. This makes is easy to define the form-wide settings and tweak one or two fields to show differently.

The icon shown can be changed with the following properties: errorIconSrc, errorIconHeight and errorIconWidth. These are normally provided by the selected skin or default from the base FormItem class.

The position of the icon can be changed by setting errorOrientation to “top”, “bottom”, “left” or “right.” This orientation is in relationship to the input field itself unrelated to the field title. If text is shown inline as well (showErrorText: true), it uses the same orientation and follows the error icon if displayed. When showing error text on the left or right, the amount of space taken from the field can be adjusted by setting errorMessageWidth on the specific field. The default is 80 pixels (80) and is limited by the column width.

Fields that are in an error condition can also be styled uniquely. By default a FormItem use CSS style “formCell” (as set in cellStyle). When showErrorStyle is false this same style is applied. Setting showErrorStyle to true causes to SmartClient to apply a CSS style of the value from cellStyle with “Error” appended (“formCellError” by default). This style in the latest skins writes the field title in bold.

If you are showing fields without titles and handling errors with just an icon it might be helpful to include the field title in the error hover message. To do so, set form.showTitlesWithErrorMessages to true. Messages will be placed in the tooltip after the title and a colon (:).

Showing errors as a group

In addition to the style of inline errors just covered, errors can be shown as a group for the form. This is common for a densly packed form where there is not room for error icons or text. To turn off inline errors set form.showInlineErrors to false.

For group error display, a special error cell (field type “blurb”) is created and shown at the top of the form when any field has errors. The field’s cellStyle defaults from form.errorItemCellStyle (“formItemError”) allowing customization of the display.

In this blurb the errors are shown formatted from form.GetErrorsHTML(). That is, each field in error is shown as a bullet item along with the error message(s). A preamble message is typically shown above this list. The text for this message is defined in form.errorsPreamble as defaults to “The following errors were found:”.

Choosing the cell style for the errors group is not the only customization that can be done. The error display is just a form item so any form item properties can be defined on this field by setting them in form.errorItemProperties. This property is an object with the desired properties.

For example, if you want to launch help when anything is clicked on this item, set properties like:

myForm.errorItemProperties : {
    click : “form.launchHelp()”
}

Hidden field errors

Hidden fields can be validated by a call to myForm.validate(true). Note the true argument. This tells the validation process to include hidden fields. Normally it is assumed that hidden fields are managed by code to be valid or are always valid when hidden.

When showing errors inline, there is no where to show an icon or message because the field is hidden. If errors are shown as a group, hidden errors are included. What do you do with hidden errors in inline mode? There is an event handler that allows you to do whatever you want like showing a message box.

handleHiddenValidationErrors is a string method so you can just call your display method using the errors parameter.

handleHiddenValidationErrors : “myForm.showHiddenErrorBox(errors)”;

Non-validator errors

Validators are just one way of setting errors on a field. Validation failures make the equivalent of calls to DF.addFieldErrors to store the error messages. You can do this programmatically as well. Say your form needs to perform some validation on another form or object (a valuesManager is not appropriate). After calling the normal myForm.validate() this additional validation results in errors for one or more fields. These errors can be added to the form’s error list by calling myForm.addFieldErrors. These errors then show exactly like errors that result from validators.

myForm.addFieldErrors(“email”, “Email must be entered when …”, true);

Note the last argument of true. This forces a redraw of the field so the errors show immediately. If you have a number of fields to update you can optimize this process by passing false instead and then calling myForm.showErrors() to redraw fields with errors.

WARNING: Performing form validation or validation of the field updated with your non-validator errors (like with field.validateOnChange) clears your custom message(s).

Finally, errors can be cleared on demand with a call to myForm.clearErrors (all fields) or myForm.clearFieldErrors (one specific field). MyForm.setFieldErrors is a shortcut for calling clearErrors() and then addFieldErrors().

Determine validation state

There are many cases where a form needs to determine the current validation status of its fields such as for enabling or disabling action buttons.

The SmartClient DynamicForm has some helpful methods for just this use:

  • hasErrors()
  • hasFieldErrors()
  • valuesAreValid()
  • getValidatedValues()

The first, hasErrors(), returns true if the form currently has any errors. The second, hasFieldErrors(), is the same except it only checks a single field. For both of these validation must have been performed previously to be useful.

The last two methods actually trigger validation but valuesAreValid() does not save any validation errors on the form. A call to getValidatedValues() is the same as calling getValues() except that validation is performed first and values are only returned if there are no errors.

Tags:

Introduction to SmartClient 8.1/SmartGWT 2.5 Validation (part 2)

October 14, 2011 1 comment

In the last part of the validation introduction the basics were covered. In this installment we will take a look at custom validation.

Note that SmartClient 8.1 and SmartGWT 2.5 were released since the last part so the title was updated but the content still applies to the 8.0 and 2.2+ releases.

Custom Validators

Although SmartClient offers numerous built-in validators that solve many of your standard validation needs, there are always situations where validation is required that are not covered by existing validators. Enter the custom validator.

Custom validators can be created to run on either the server or client. There is currently no mechanism to write a single validator that works on both the client and server. We’ll start with a look a custom validation on the server.

A special word on server-based validators should be added here: Just because a validator is server-based doesn’t mean it cannot be triggered during form/grid editing. In fact, by applying the validator to a DataSource field the client will recognize the validator and ask the server to perform validation just like it does for client-only validators.

Server custom validators

A serverCustom validator can process Velocity expressions or make a DMI call to a server object. These two options provide extensive flexibility to extend the server validation features.

Velocity expressions

The SmartClient Server Framework leverages Apache’s Velocity Engine to allow dynamic expressions to be evaluated for validation (and other server features not covered in this document). Let’s return to the examples to see one of these validators in action. Select Forms → Validation → Server-based → Velocity Expression.

In this example a user is selecting an item and quantity to place on an order. The user should be informed when the desired quantity is not in stock. To do this a serverCustom validator is applied to the DataSource (velocity_orderForm) quantity field. Select the velocity_orderForm tab to see the DataSource definition.

<DataSource ID="velocity_orderForm" serverType="sql">
  <fields>
    <field name="orderItem" type="sequence" primaryKey="true"/>
    <field name="itemId" foreignKey="StockItem.id"/>
    <field name="quantity" type="integer">
      <validators>
        <validator type="serverCustom">
          <serverCondition><![CDATA[
            $value < $dataSources.StockItem.fetchById($record.itemId).quantity
          ]]></serverCondition>
          <errorMessage>Not enough in stock</errorMessage>
        </validator>
      </validators>
    </field>
    <field name="instructions" type="text"/>
  </fields>
</DataSource>

Take a look at the validator for the quantity field. First, note the use of to allow the expression to be entered without conflicting with the XML source. The serverCondition must be a single expression that evaluates to true or false. Next, notice the meaningful Velocity variables in this expression: $value, $dataSources, and $record. These variables are provided automatically by the framework for context. Here are the common context variables available:

dataSources The list of all DataSources accessible by name (i.e. $dataSources.supplyItem)
dataSource The current DataSource
record The DataSource record being validated
value The value of the field
validator The configuration of this validator presented as a Map
field The field as a DSField object
util A com.isomorphic.util.DataTools object giving access to all of that class’s useful helper functions

Breaking down the expression, you can see the lookup of the related DataSource StockItem by $dataSources.StockItem. Any DataSource method can be called on that DataSource or a property can be read. Here the fetchById method is called to lookup a particular record using the primary key $record.itemid. The $record object normally has all fields of the record being validated allowing inter-field validations or references. With the StockItem record now loaded, the quantity column value (quantity in stock) is compared to our desired quantity value. The result is true if enough stock is on hand.

The validator errorMessage is also set to the desired message that the user should see upon validation failure.

If the quantity field is placed on the form as-is, when the form submit is pressed the new record will be validated on the server using this custom validator and errors reported back to the client on failure. This is great but it would be nice for the user to know the quantity is not available before trying to submit the form. To do this, validateOnExit is set true on the quantity form field. Details on this option are covered in the Controlling validation section.

Server objects

Custom server validators based on Velocity are very powerful and quickly created but cannot cover very complicated validations. For these cases the SmartClient Server Framework provides the ability to define server-based validators using DMI on custom objects. Select Forms → Validation → Server-based → DMI Validation.

This example looks identical to the one covered for Velocity validation. The end result is very similar except the error message includes details on how many items are available in stock. The form itself is the same except it refers to a different DataSource. Click on the validationDMI_orderForm tab.

<DataSource ID="validationDMI_orderForm" serverType="sql">
  <fields>
    <field name="orderItem" type="sequence" primaryKey="true"/>
    <field name="itemId" foreignKey="StockItem.id"/>
    <field name="quantity" type="integer">
      <validators>
        <validator type="serverCustom">
          <serverObject lookupStyle="new"
            className="com.isomorphic.examples.server.validation.ValidatorDMI"/>
          <errorMessage>Only $available in stock</errorMessage>
        </validator>
      </validators>
    </field>
    <field name="instructions" type="text"/>
  </fields>
</DataSource>

The serverCustom validator assigned to the quantity field is defined with serverObject. The errorMessage is defined with a variable, $available, instead of static text.

A serverObject definition can be used to reference server objects in many ways. This example refers to the custom class com.isomorphic.examples.server.validation.ValidatorDMI. A new instance will be created for each validation because the lookupStyle is set to “new.” See the SmartClient Reference for additional options.

This example refers to a class that implements exactly one method: condition. A custom validation class can implement more than one condition-type method. The specific method for the validator is then specified by the methodName property for the serverObject.

Click the ValidatorDMI.java tab to see the implementation. In all cases the “condition” method must accept the value, validator, fieldName, record and ds arguments. Note that the example validator method performs the same operations as the Velocity example using the standard SmartClient Server Framework objects and methods. One thing unique is setting a variable on the validator so that the error message can be dynamic. The $available variable is set when validation fails by calling validator.addErrorMessageVariable. All variables must be strings.

It should be obvious just from these two custom validation mechanisms that the client and server validation features of the SmartClient Server Framework are invaluable for robust user experiences and reduce development efforts to just providing the business rules.

Client custom validators

Although not as useful as server-based custom validators, client-only custom validators can be used to provide another layer of validation to forms.
Returning to the Forms->Validation->Custom Binding example, let’s add some additional choices. Above the “acceptTerms” field add the following fields:

        {type:"header", name: "choices", defaultValue:"Choose notification type(s):"},
        {name: "byMail", title: "Mail", type: "boolean"},
        {name: "byPhone", title: "Phone", type: "boolean"},
        {name: "byEMail", title: "E-mail", type: "boolean"},
        {name: "byText", title: "Text message", type: "boolean"},

Here the user is choosing one or more ways to be notified in the future. At this point in time, the user can choose any of these or none. But this is not exactly what we want – to require at least one choice. Let’s add a custom validator to the choice header to notify the user if no choice is made. Change the “choices” header field as follows:

        {type:"header", name: "choices", defaultValue:"Choose notification type(s):", validators: [{
            type: "custom",
            condition: function (item, validator, value, record) {
                 var checkboxes = ['byMail', 'byPhone', 'byEMail', 'byText'];
                 var count = 0;
                 for (var i = 0; i < checkboxes.length; i++) {
                     if (record[checkboxes[i]]) count++;
                 }
                 if (count < 1) {
                     validator.errorMessage = "Must choose at least one notification type";
                     return false;
                 }
                 return true;
            }
         }]
        },

We have now added a simple custom validator to the header that checks each checkbox field for it’s checked status (true) and return a failure if nothing is checked. Note that a validator on a field does not have to perform the validation on the same field. The validator is assigned to the field where the error is reported. This validator could be assigned to the first checkbox field instead avoiding the need for an additional field.

Following this pattern it is very easy to add as many product-specific validations as needed. However, let’s say you want to validate zip codes against a zip code database. Zip codes are used for customer addresses, ship-to addresses, vendor addresses, etc. Copying the same validation code to each form field or even DataSource field is unwieldy. A better choice is to register a custom validator as a built-in. This is covered in the Advanced features section.

In the next part we’ll take a look at options that control when validation occurs and how the results are presented to the user.

Tags:
Follow

Get every new post delivered to your Inbox.