I have to attach validation methods to a bunch of edit fields' onChange. Some fields would have to contain floats (toFixed(2)), some - integers, some - capitalized strings after the field has lost focus. I'm ready with 3 functions to be attached to controls, but I would like to hear the most robust solution to this problem. Would you use ctrl.onChange = fn, or rather ctrl.addEventListener()...?

The problem of relying on the onChange event of a single field is that a click in an OK button will still close the window.

But, if you set an event listener to the OK button's click event, you can validate all fields and then cancel the event.

Here's an example that does work in ESTK2 (CS3) (I think this code will paste and show correctly, if it doesn't, sorry)

// Basic ScriptUI validation

/* This script shows the basics of validation using ScriptUI events

By using the "change" event, a script can be notified that a value has changed. The problem with this approach is that it
requires a field to lose focus for the event to fire. For example, a user can type an invalid number in a field, then, without leaving
the field, click the OK button (which is still enabled because validation has not happened on the invalid field yet). In this case, clicking
the OK button causes the field to lose focus, the validation occurs, but the OK button has already been pressed, and the dialog will
close with an invalid entry.

We could use the "changing" event that fires for every character typed; but, that approach brings its own set of complications. For example, the
user is typing "12 inches" in the field. If the validation routine accepts units and abbreviations, as the user types we get the following states:
"1" - valid
"2" - valid
" " - valid
"i" - INVALID
"n" - valid
"c" - INVALID
"h" - valid
"e" - INVALID
"s" - valid

That doesn't work.

If we intercept the OK button's mouseDown event, we can validate all fields, and either allow the OK button click event or cancel it.
If cancelled, the OK button can disabled, and a visual cue could be provided as to the offending field(s).
With this approach, the valdiation routines need to have a reference to the OK button, and all registered validations
must happen each time a field is edited to set the state of the OK button.

*/

// setControlState sets the background color of a control to "good" or "bad". The white for good, red for bad
setControlState= function( myControl ) {
if ( myControl.isValid ) { // we're good, make it white
myControl.graphics.backgroundColor = myControl.graphics.newBrush( myControl.graphics.BrushType.SOLID_COLOR, [1,1,1,1] );
} else { // we're bad, make the field background red (straight red, [ 1, 0, 0, 1] is a bit harsh, so we're using a red-ish color
var myRedColorArray = [ 210/255, 12/255, 82/255, 1];
myControl.graphics.backgroundColor = myControl.graphics.newBrush( myControl.graphics.BrushType.SOLID_COLOR, myRedColorArray );
}
}
// validateInteger will ensure that a field contains a valid integer
validateInteger = function( myEvent ) {
/*
myEvent is passed to the handler when the event fires, myEvent.target is the target control of the event.
We're adding a property "isValid" to the control so we can quickly check the state of any control
Note that parseInt parses "12x" as 12, so you can't just check for isNaN on a parseInt...
*/
// make this a required field by checking to see if there's anything there
if ( myEvent.target.text.length == 0 ) {
myEvent.target.isValid = false;
} else if ( isNaN( Number( myEvent.target.text ) ) ) { // if it can't be cast to a number, it's obviously bad
myEvent.target.isValid = false;
} else {
// if we can parseIn the content of the field, change it back to a string, and get the original value, the original value must have been a string
myEvent.target.isValid = ( parseInt( myEvent.target.text ).toString() == myEvent.target.text );
}
// set the control's background to red if it's invalid
setControlState( myEvent.target );
// check all the fields, and set the OK button state
validateAll( myEvent.target.window.okButton );
}
// validateReal will ensure a field has a valid Real number
validateReal = function( myEvent ) {
// make this a required field by checking to see if there's anything there
if ( myEvent.target.text.length == 0 ) {
myEvent.target.isValid = false;
} else if ( isNaN( Number( myEvent.target.text ) ) ) { // if casting to a number is NaN, it's bad...
myEvent.target.isValid = false;
} else {
// if user enters ".2", parseFloat turns it into "0.2" so the same parseFloat().toString() won't work like it does for integers
// using a regex instead
var regex = /\d*\.{0,1}\d*/g; // zero or more digits, possibly a decimal point, followed by zero or more digits
// if the first element of the array returned from String.match equals the original, we're good
try {
myEvent.target.isValid = ( myEvent.target.text == myEvent.target.text.match( regex )[ 0 ] );
} catch( e ) {
myEvent.targetisValid = false;
}
}
//set the control's background to red if it's invalid
setControlState( myEvent.target );
// now check all the fields
validateAll( myEvent.target.window.okButton );
}
// uses the UnitValue object to ensure whatever is typed in will be a valid measurement unit
// for this handler, we wil set a "fieldUnit" property on the edit box, all units will be converted to those units
// we're supporting all units that UnitValue supports except pixels and percents as they are based on a base unit and traditional points/picas as no one will likely enter "tpc" or "tpt" vs "pt" or "pc"
validateUnits = function( myEvent ) {
// no empty fields
if ( myEvent.target.text.length == 0 ) {
myEvent.target.isValid = false;
} else {
// this regex will validate the basic form of a unit value - a real number followed by zero or more spaces and possibly a valid unit string
var regex = /\d*\.{0,1}\d* *(?:in|inch|inches|ft|foot|feet|yd|yard|yards|mi|mile|miles|mm|millimeter|millimeters|cm| centimeter|centimeters|m|meter|meters|km|kilometer|kilometers|pt|point|points|pc|pica|pica s|ci|cicero|ciceros)?/gi;
var myMatch = myEvent.target.text.match( regex );
try {
myEvent.target.isValid = ( myEvent.target.text == myEvent.target.text.match( regex )[ 0 ] );
} catch( e ) {
myEvent.target.isValid = false;
}

if ( myEvent.target.isValid ) {
// create a new UnitValue from the target text
// it's posible that the units were left off, in which case the fieldUnits are assumed.
// so use the regex from validateReal to see if there's a unit attached to the value
var regex = /\d*\.{0,1}\d*/g; // zero or more digits, possibly a decimal point, followed by zero or more digits
// if the first element of the array returned from String.match equals the original, there's no units
try {
var hasUnits = ( myEvent.target.text != myEvent.target.text.match( regex )[ 0 ] );
} catch( e ) { // if this errors, something is very, very wrong...
myEvent.target.isValid = false;
setControlState( myEvent.target );
validateAll( myEvent.target.window.okButton );
return;
}
if ( !hasUnits ) {
var myUnitValue = new UnitValue( myEvent.target.text + " " + myEvent.target.fieldUnit );
} else {
var myUnitValue = new UnitValue( myEvent.target.text );
}
if ( isNaN( myUnitValue ) ) {
myUnitValue = new UnitValue( "0 " + myEvent.target.fieldUnit );
}
try {
// attempt to convert it to the fieldUnit - if it can't be done, this will error
myUnitValue.convert( myEvent.target.fieldUnit );
// the reason for the convert is that the UnitValue.toString() will inclue the abbreviated units for us, using UnitValue.as() will not
myEvent.target.text = myUnitValue.toString();
} catch ( e ) {
// if we errored, it wasn't a valid measurement
myEvent.target.isValid = false;
}
}
}
setControlState( myEvent.target );
validateAll( myEvent.target.window.okButton );
}

// validateAll - this function is hard wired to check the isValid state (set in the validation routines) of the edit boxes used in the test example
validateAll = function