Introduction to SmartClient 8.1/SmartGWT 2.5 Validation (part 2)
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.
Dude, I hardly find your tutorials interesting. You cover only things already presented and well documented in SmartClient docs. I really don’t see the usefulness of your tutorials.