PDA

View Full Version : class design stuff


MichaelxxOA
06-10-2008, 04:14 AM
Three good class traits

Note that in the following I'm saying class (to emphasize that this is about class design), but I'm really referring to an instance of that class (or an object). anyway ...

- all of the variables in a class are useful for as long as the class lives

- all of the methods in a class can be called independently of one another and in any order

- only the following methods are called:
-- methods that belong to the class
-- methods that belong to objects created within the method
-- methods that belong to objects which are arguments to the method
-- methods that belong to objects stored in the classes variables

Thoughts or opinions? None of these things are my creation, but I find them incredibly helpful (in practice) for quickly identifying weak points in my class (besides the usual stuff ... no duplication, complexity, etc.).

Try applying these three things to the code you are working with now. Chances are if one of them is being violated it should be moved to a new class (either one that exists or a new one altogether).

Of course there are quite a few other things that can be talked about, but these three things are easy to identify and tend to naturally lead me in the direction of a good design.

- M

Flash Gordon
06-10-2008, 07:33 AM
so how do these apply to build airplanes?? :p (sorry had to ask bro)

Flash Gordon
06-10-2008, 07:34 AM
could you give a bad example and good example of each point. I think that might help me a little.

MichaelxxOA
06-10-2008, 10:27 PM
so how do these apply to build airplanes?? :p (sorry had to ask bro)

LoL

could you give a bad example and good example of each point. I think that might help me a little.

I've learned that the best way to demonstrate these things is with your own code. Do you have a class you've recently written that you feel could use some cleaning up?

I hope things are well mang.

yell0wdart
06-11-2008, 01:36 AM
Sounds about right. I've always looked at class design with one thing in mind:

You should be able to describe what your class does in one short sentence.

Flash Gordon
06-11-2008, 02:21 AM
I've learned that the best way to demonstrate these things is with your own code. Do you have a class you've recently written that you feel could use some cleaning up?

For sure. I'll post all of the code and we can walk through the elements and discuss some OO principles/patterns as well for the solution.

So the first to need is what's the problem: I'm trying to simplify the process of collecting data from a form and sending it server side. I need for the validation and object types to be encapsulated so that it may be changed out.

Here's what I got:
http://www.therealjoshua.com/actionscript_org/Form%20Collection.jpg

IFormCollection.as

package com.*.v1.forms
{
import flash.utils.Dictionary;
import com.*.v1.forms.FormValidationTypes;

public interface IFormCollection
{
function get allObjs():Array;
function get errorObjs():Array;
function get data():Dictionary;
function get requests():Dictionary;

function addObj(obj:*, request:String=null, typeCheck:String="notRequired", errorMes:String=null, defaultTxt:String=null, noMatch:Array=null):void
function validate():Boolean;
function collect():void;
function focusActions():void;
function errorState():void;
function reset():void;
}
}


TextFieldCollection.as

package com.*.v1.forms
{
import flash.text.TextField;
import flash.utils.Dictionary;
import flash.events.FocusEvent;

import com.*.v1.forms.FormValidationTypes;
import com.*.v1.forms.ValidationExps;

public class TextFieldCollection implements IFormCollection
{
protected var _servRequests:Dictionary = new Dictionary();
protected var _typeChecks:Dictionary = new Dictionary();
protected var _errorMes:Dictionary = new Dictionary();
protected var _defaultTxt:Dictionary = new Dictionary();
protected var _noMatches:Dictionary = new Dictionary();

protected var _allObjects:Array = new Array();
protected var _errorObjects:Array = new Array();
protected var _collectData:Dictionary = new Dictionary();

public function get allObjs():Array { return _allObjects; }
public function get errorObjs():Array { return _errorObjects; }
public function get requests():Dictionary { return _servRequests; }
public function get data():Dictionary { return _collectData; }

public function TextFieldCollection()
{
//
}


/**
* Function to add an object to the data collection
*
* @param obj For this class only TextFields are excepted
* @param typeCheck is how to validate the object's data. For instance NOT_REQUIRED or as EMAIL.
* @param noMatch This is an array of strings that if the objects data matches will add to an error count
*/
public function addObj(obj:*, request:String=null, typeCheck:String=FormValidationTypes.NOT_REQUIRED, errorMes:String=null, defaultTxt:String="", noMatch:Array=null):void
{
if (!obj as TextField) throw new Error("TextFieldCollection::addObject obj argument must be a TextField object");

_allObjects.push(obj);
_servRequests[obj] = (request != null) ? request : obj.name;
_errorMes[obj] = errorMes;
_defaultTxt[obj] = defaultTxt;
_typeChecks[obj] = typeCheck;

if (noMatch == null) noMatch = new Array();
noMatch.push(errorMes);
noMatch.push(defaultTxt);
_noMatches[obj] = noMatch;

}

/**
* This method will cycle the allObjects array and validate the data
*
* @param return Returns true if data is all valid and false if there are errors
*/
public function validate():Boolean
{
_errorObjects = new Array();

for (var i:int=0; i<_allObjects.length; i++)
{
var txt:TextField = _allObjects[i];

switch (_typeChecks[txt])
{
case FormValidationTypes.TEXT:
checkText(txt);
break;
case FormValidationTypes.EMAIL:
checkEmail(txt);
break;
case FormValidationTypes.TELEPHONE:
checkTelephone(txt);
break;
}
}

return (_errorObjects.length == 0) ? true : false;
}

/**
* This collects the data from the objects and stores it in a dictionary instance
*/
public function collect():void
{
for (var i:int=0; i<_allObjects.length; i++)
{
var txt:TextField = _allObjects[i];
_collectData[txt] = txt.text;
}
}


/**
* This method loops through all of the registered textFields and if they text equals the default text or
* error message text it sets the textfield's text to blank ""
*/
public function focusActions():void
{
for (var i:int=0; i<_allObjects.length; i++)
{
_allObjects[i].addEventListener(FocusEvent.FOCUS_IN, focusIn);
_allObjects[i].addEventListener(FocusEvent.FOCUS_OUT, focusOut);
}
}

/**
* This method loops through all of the textfield that has invalid data and sets the error message
*/
public function errorState():void
{
for (var i:int=0; i<_errorObjects.length; i++)
{
_errorObjects[i].text = _errorMes[_errorObjects[i]];
}
}


/**
* This method sets the TextFields in the collection back to the state before any errors
* or user input
*/
public function reset():void
{
for (var i:int=0; i<_allObjects.length; i++)
{
_allObjects[i].text = _defaultTxt[_allObjects[i]];
}
}


/**
* This method check and text for to make sure it doesn't have empyt text and that it
* doesn't match one of the avoid strings if any of those conditions are met, it
* adds the textfield to an array of error and returns 1.
*
* @param return Returns 1 if there is an error and 0 if there is no error
*/
protected function checkText(txt:TextField):Number
{
if (txt.text == "")
{
_errorObjects.push(txt);
return 1;
}

for each (var value:* in _noMatches[txt])
{
if (txt.text == value)
{
_errorObjects.push(txt);
return 1;
}
}

return 0;
}

/**
* This method check a textfield for a valid email address
*
* @param return Returns 1 if there is an error and 0 if there is no error
*/
protected function checkEmail(txt:TextField):Number
{
var emailReg:RegExp = ValidationExps.EMAIL;

if ( !emailReg.test(txt.text) )
{
_errorObjects.push(txt);
return 1;
}

for each (var value:* in _noMatches[txt])
{
if (txt.text == value)
{
_errorObjects.push(txt);
return 1;
}
}

return 0;
}

/**
* This method check a textfield for a valid telephone number address
*
* @param return Returns 1 if there is an error and 0 if there is no error
*/
protected function checkTelephone(txt:TextField):Number
{
var emailReg:RegExp = ValidationExps.PHONE_EXT;

if ( !emailReg.test(txt.text) )
{
_errorObjects.push(txt);
return 1;
}

for each (var value:* in _noMatches[txt])
{
if (txt.text == value)
{
_errorObjects.push(txt);
return 1;
}
}

return 0;
}


/**
* Protected function to set the focused textfield text to blank ""
*/
protected function focusIn(e:FocusEvent):void
{
var txt:TextField = e.currentTarget as TextField;
if( txt.text == _defaultTxt[txt] || txt.text == _errorMes[txt])
txt.text = "";
}

/**
* Protected function to set the focused textfield text to default text if text is blank
*/
protected function focusOut(e:FocusEvent):void
{
var txt:TextField = e.currentTarget as TextField;
if( txt.text == "" && _defaultTxt[txt] ) txt.text = _defaultTxt[txt]
}
}
}

Flash Gordon
06-11-2008, 02:21 AM
continued.......

FormHandler.as

/**
* This class is basically a command-fascade pattern for handling forms. This class uses classes with the IFormCollection
* to collect data and validate contents. It then sends the data to a server side script.
*
* <p>The events dispatched from this class include:
* Event.INIT - whenever the vars sends it to the server
* Event.COMPLETE - whenever the server returns a response and it is in valid syntax
* ErrorEvent.ERROR - whenever something goes wrong from getting a response from the sever such as invalid syntax
* IOErrorEvent.IO_ERROR - whenever the url is not found.</p>
*
*/

package
{
import flash.text.TextFormat;
import flash.utils.Dictionary;
import flash.net.URLRequest;
import flash.net.URLVariables;
import flash.net.URLRequestMethod;
import flash.net.URLLoader;
import flash.net.URLLoaderDataFormat;
import flash.events.Event;
import flash.events.ErrorEvent;
import flash.events.IOErrorEvent;
import flash.events.EventDispatcher;
import flash.net.navigateToURL;

import com.*.v1.forms.IFormCollection;

public class FormHandler extends EventDispatcher
{
protected var collections:Array = new Array();
protected var allObjects:Array = new Array();
protected var errorObjects:Array = new Array();

protected var allData:Dictionary = new Dictionary();
protected var allRequest:Dictionary = new Dictionary();

protected var urlVars:URLVariables = new URLVariables();
protected var response:String;


public function FormHandler()
{
//
}


/**
* This method adds a collection
*
* @param collect A class that implements IFormCollection to be registered
*/
public function addCollection(collect:IFormCollection):void
{
allObjects = new Array();
collections.push(collect);
for (var i:int=0; i<collections.length; i++)
allObjects = allObjects.concat(collections[i].allObjs);

}


/**
* This method check all collections to see if they all have valid enteries
*
* @param return Returns true if everything is valid. False if there are errors.
*/
public function validate():Boolean
{
var valid:Boolean = true;

for (var i:int=0; i<collections.length; i++)
{
var temp:Boolean = collections[i].validate();
valid &&= temp; // must write this out the long was and it won't loop through if short hand
}

for (var j:int=0; j<collections.length; j++) errorObjects.concat(collections[j].errorObjs);
return valid;
}


/**
* This method loops through all the collections and add the data to a dictionary instance.
*/
public function collect():void
{
var i:int;
for (i=0; i<collections.length; i++)
{
collections[i].collect();
}

for (i=0; i<collections.length; i++)
{
var objs:Array = collections[i].allObjs;
for (var j:int=0; j<objs.length; j++)
{
allData[ objs[j] ] = collections[i].data[ objs[j] ];
allRequest[ objs[j] ] = collections[i].requests[ objs[j] ];
urlVars[ allRequest[objs[j]] ] = allData[ objs[j] ];
}
}
}



/**
* This method loops through all the collections and sets the objs to perform task upon an initial click
* such as clearing the text.
*
* @param tf is the TextFormat to apply to the obj after it has been clicked....MAYBE
*/
public function focusActions(tf:TextFormat=null):void
{
for (var i:int=0; i<collections.length; i++)
{
collections[i].focusActions();
}
}


/**
* This method loops through all the collections and sets the errorObjs to the error status.
*
* @param tf is the TextFormat to apply to the error message....MAYBE
*/
public function setErrors(tf:TextFormat=null):void
{
for (var i:int=0; i<collections.length; i++)
{
collections[i].errorState();
}
}


/**
* This method loops through all the collections and sets the objects back to the orignal states
*/
public function reset():void
{
for (var i:int=0; i<collections.length; i++)
{
collections[i].reset();
}
}


/**
* This method allows additional variables to be sent in addition to the ones collected
* by the collections
*
* @param vars The URLVariables to send in addition to the collections
*/
public function addVars(vars:URLVariables):void
{
for (var prop:* in vars)
{
urlVars[prop] = vars[prop];
}
}


/**
* This method sends the collected var to a url
*
* @param url Where to send the data
* @param method How to send the data
*/
public function sendVars(url:String, method:String=URLRequestMethod.POST):void
{
var loader:URLLoader = new URLLoader();
loader.addEventListener(Event.COMPLETE, onComplete);
loader.addEventListener(ErrorEvent.ERROR, onError);
loader.addEventListener(IOErrorEvent.IO_ERROR, onError);

var request:URLRequest = new URLRequest(url);
request.method = method;

loader.dataFormat = URLLoaderDataFormat.VARIABLES;
request.data = urlVars;
loader.load( request );
//navigateToURL(request, "_blank"); // for debugging

dispatchEvent( new Event(Event.INIT) );
}

protected function onComplete(e:Event):void
{
try
{
var loader:URLLoader = URLLoader(e.target);
var variables:URLVariables = new URLVariables(loader.data);
response = variables.echo;

dispatchEvent( new Event(Event.COMPLETE) );
}
catch(e:Error)
{
dispatchEvent( new ErrorEvent(ErrorEvent.ERROR) );
}
}

protected function onError(e:ErrorEvent):void
{
dispatchEvent(e); // this will dispatch an IO_ERROR or plain ERROR
//dispatchEvent( new ErrorEvent(ErrorEvent.ERROR) );
}

}
}


Ok so there are many many problems with this. The first of which I've addressed above. The way it currently validates the input make it very difficult to extend it.

Maybe the class is trying to do too much and the client should be more responsible for handling errors in the validation.

What you think Michael? How's that mess for a start?

MichaelxxOA
06-11-2008, 02:37 AM
I need for the validation and object types to be encapsulated so that it may be changed out.

-

I know what you mean by validation, but what do you mean by "object types"?

Flash Gordon
06-11-2008, 02:39 AM
TextField, comboBox, radioButton, TextInput, etc.....

I was trying to treat the all the same: as a type of IFormCollection.

MichaelxxOA
06-11-2008, 03:06 AM
What about ...

A Form which contains a collection of FormElements.
A FormElement has a validate method which accepts a Validator object.
A Validator object also has a validate method that returns a boolean.

A FormElement can be something like a CheckBox, TextField, InputText, etc.

A Validator can be something like ... ZipCodeValidator, CreditCardValidator, etc.

You can handle multiple validations by making a CompositeValidator.

This is different from what I am going on about in this thread since I'm introducing a new design, but ... I don't know ... these are my initial thoughts.

Feedback.

Flash Gordon
06-11-2008, 03:27 AM
ok, i like it. I was thinking the same thing in terms of the validation, just haven't gotten around to it yet.

How about I (or if anyone else wants to) mock up the new design in a couple of days and post in back on here. We can discuss it in terms of your points in the initial thread.

MichaelxxOA
06-11-2008, 04:07 AM
Let's avoid documentation for a little bit ... that way we can stay focused on the code.

Form stuff ...

- forms are processed
- forms are validated
- forms are made up of elements

All of these things vary from form to form. A registration form will have text input elements for username and password, button elements for cancel and submit, and maybe a simple registration processor (maybe processing gets more generic later? idk). On the other hand a form to submit comments will behave just a little differently.

Code could look like


var registrationForm:Form = new Form();

var lengthValidator:Validator = new StringLengthValidator( 4 );

form.addFormElement( new TextInputElement( "Username", lengthValidator ) );
form.addFormElement( new TextInputElement( "Password", lengthValidator ) );

form.addFormElement( new ButtonElement( "Cancel" ) );
form.addFormElement( new ButtonElement( "Submit" ) );

// don't know how this works
// don't care for it either, but it's something to look at
// we can always (and should always) refactor LATER
form.setFormProcessor( new RegistrationProcessor( "http://my.registration.script" ) );


I think that looks reasonable so far, yeah?

There are some little steps in there, but ... they are things we can make the computer do later. It would be fairly easy to come up with a simple xml format ...


<form processURL="http://my.registration.script">
<textInput name="username" minLength="4" />
<textInput name="password" password="true" minLength="4" />

<button name="Cancel" />
<button name="Submit" />
</form>


A parser can be written for that to generate our classes from a second ago. We don't need to bother with it, but I wanted to just bring up that this stuff can be automated once it's broken up pretty well.

?

Flash Gordon
06-11-2008, 04:59 AM
Looks good. **thumbs up** For my specific needs, the xml would be overkill and actually not needed. My forms consist of: user typed text, radio buttons, comboboxes, {other components}, and 1 submit button to send it to an SSL. By the xml definately demonstrates how easily it should be able to change.

So far, it looks close to my intial design, with now that the validation has been abstracted away (into what looks like it could be a command pattern).

One thing still to consider is who's job is it to handle the errors in the form? From form to form the way error are handled could greatly vary. ComboBox errors aren't handled the same and input text. Or maybe just a red arrow appears on the errors. Where does this belong? I think this was one on your point in the initial thread.

and once this gets take care of, I'm gonna make you talk about packages....I know you love 'em.

MichaelxxOA
06-11-2008, 06:47 PM
What are some examples of how the error handling can be different?

Flash Gordon
06-11-2008, 07:07 PM
ComboBoxes could either blink is nothing was selected or a red arrow could appear.
TextFields could display an error message inside of them, blink, have a red arrrow, launch an error window, or display a message down by the "submit" button.

There are many ways to handle errors.

MichaelxxOA
06-11-2008, 07:18 PM
Maybe those things you mention are features of the form elements? (collaborating with their validators).

Other than that there are two higher level scenarios: invalid and process failure. These things can be dispatched by the Form itself and passed off to the pop-up engine (or whatever you want .. logging?).

What are you thinking?

Flash Gordon
06-11-2008, 08:56 PM
Maybe those things you mention are features of the form elements? (collaborating with their validators). Yea, definitely. I see it the same way.


Other than that there are two higher level scenarios: invalid and process failure. These things can be dispatched by the Form itself and passed off to the pop-up engine (or whatever you want .. logging?).
I was think for invalid data the only things that would happen would be the "invalid errors" would display. For process failure, I've just been ignoring it. But yea, that is certainly something to consider, esp in a flexible design. Perhaps the process failure is a feature of the form.


So to bring it back to the original post a bit, in the initial design.
-- methods that belong to the class:
Validation and display error was part of the TextFieldCollection class. It is now moved away into it's only class as it didn't belong

not really too sure what these mean. Can you elaborate in terms of the sample posted-class?
-- methods that belong to objects created within the method
-- methods that belong to objects which are arguments to the method
-- methods that belong to objects stored in the classes variables

MichaelxxOA
06-11-2008, 09:20 PM
What else comes to mind? I'm thinking about it too ... I'll post when I have something. (in and out of working at work right now)

MichaelxxOA
06-11-2008, 09:24 PM
How about we come up with a simple case we can test against?

What is an example of a form you have to put together? Let's start with one, and then we'll implement a few others (to ensure our design is good).

Try to list everything you can think of. We need to know the types of input it's expecting, validation criteria for that input, and how that input is going to be processed. Also try to think of what is going to happen if that data is not valid, or the processing fails (any kind of notification, logging, etc.).

Flash Gordon
06-11-2008, 10:35 PM
Ok, I've uploaded an example of a sample form. I didn't include every input type, but I did include 3 different ones with differnt types of validation. (I have the RegExs for all the validations so don't spend any time on that. I can send it over if you want it ). I'm not very worried about logging errors as the form's main point is to collect user input. If any data is invalid, simple show the appropriate error and don't send the data server side.

Form example (http://www.therealjoshua.com/actionscript_org/form example.zip)

MichaelxxOA
06-12-2008, 07:09 PM
Lets start with the validators, yeah? They seem to be a simple and well understood part.

I figure we have an interface Validator with one method validate( input )

Validatar
- validate( input:* ):Boolean;

There are three validators which deal with strings so lets take care of those.

StringLengthValidator
- min/max

EmailValidator
- valid e-mail

SameStringValidator
- two strings are equal

You want to write them?

Flash Gordon
06-12-2008, 08:25 PM
yup....give me about 30 minutes and I'll be back with thoses.

Flash Gordon
06-12-2008, 10:41 PM
Tester:

package {
import flash.display.Sprite;

import org.actionscript.validations.*;

public class Form_Validation extends Sprite
{
protected var _lengthVal:IValidator = new StringLengthValidator(2, 4);
protected var _sameString:IValidator = new SameStringValidator(true);
protected var _emailVal:IValidator = new EmailValidator();

public function Form_Validation()
{
trace( "StringLengthValidator", _lengthVal.validate("heo") );

var animals:Array = ["dog", "Dog", "dog"];
trace( "SameStringValidator",_sameString.validate(animals) );

trace( "EmailValidator", _emailVal.validate("[email protected]") );
}
}
}



package org.actionscript.validations
{
public interface IValidator
{

/**
* @return True if the validation passes
*/
function validate(input:*):Boolean;
}
}



package org.actionscript.validations
{

public class StringLengthValidator implements IValidator
{
protected var _min:int;
protected var _max:int;

public function StringLengthValidator(minLength:int=0, maxLength:int=int.MAX_VALUE)
{
_min = minLength;
_max = maxLength;
}

public function validate(input:*):Boolean
{
var min:Boolean = String(input).length >= _min;
var max:Boolean = String(input).length <= _max;

return min && max;
}
}
}


package org.actionscript.validations
{
import flash.errors.IllegalOperationError;


public class SameStringValidator implements IValidator
{
protected var _caseSensitive:Boolean;
protected var _inArr:Array

public function SameStringValidator(caseSensitive:Boolean=true)
{
_caseSensitive = caseSensitive;
}

public function validate(input:*):Boolean
{
var results:Boolean = true;
_inArr = input as Array;
if (!_inArr) throw new IllegalOperationError("SameStringValidator/validate @param input must be of type Array");

for (var i:int; i<_inArr.length; i++)
{
results = results && compare(i);
}

return results;
}

protected function compare(index:int):Boolean
{
var needle:String = _inArr[index] as String;
if (!needle) throw new IllegalOperationError("SameStringValidator/validate @param input must be of type Array/Vector.<String>");
var j:int=0;

if (!_caseSensitive)
{
needle = needle.toLowerCase();
for (j = index+1; j<_inArr.length; j++)
{
if (needle !== String(_inArr[j]).toLowerCase()) return false;
}
}
else
{
for (j = index+1; j<_inArr.length; j++)
{
if (needle !== String(_inArr[j])) return false;
}
}

return true;
}
}
}


package org.actionscript.validations
{
import org.actionscript.validations.ValidationExps;

public class EmailValidator implements IValidator
{
public function EmailValidator()
{
}

public function validate(input:*):Boolean
{
return String(input).search(ValidationExps.EMAIL) > -1;
}

}
}


package org.actionscript.validations
{
public class ValidationExps
{

/**
* Match a U.S. telephone number in the format (###) ###-####, where the area code is optional,
* the parentheses around the area code are optional and could be replaced with a dash, and
* there is optional spacing between the number groups.
*/
public static const PHONE:RegExp = /^(\(\s*\d{3}\s*\)|(\d{3}\s*-?))?\s*\d{3}\s*-?\s*\d{4}$/;


/**
* Match a U.S. telephone number, like the previous expression, except allow for an optional
* one- to five-digit extension specified with an "x", "ext", or "ext." and optional spacing.
*/
public static const PHONE_EXT:RegExp = /^(\(\s*\d{3}\s*\)|(\d{3}\s*-?))?\s*\d{3}\s*-?\s*\d{4}\s*((x|ext|ext\.)\s*\d{1,5})?$/;


/**
* Match a credit card number with four group of four digits separated by optional dashes
* and optional spacing between the groups.
*/
public static const CREDIT_CARD:RegExp = /^(\d{4}\s*\-?\s*){3}\d{4}$/;


/**
* Match U.S. currency starting with a $ and followed by any number with at most two
* optional decimal digits.
*/
public static const US_CURRENCY:RegExp = /^\$\d(\.\d{1,2})?$/;

/**
* Match an email address where the domain is not an IP address and may contain any number
* of optional subdomains. When creating this regex, it's a good idea to set the
* ignoreCase flag to true:
*/
public static const EMAIL_NO_IP:RegExp = /^[a-z0-9][-._a-z0-9]*@([a-z0-9][-_a-z0-9]*\.)+[a-z]{2,6}$/i;


/**
* Match an email address when the domain is either a domain name consisting of any number
* of optional subdomains or an IP address.
*/
public static const EMAIL:RegExp = /^[a-z0-9][-._a-z0-9]*@(([a-z0-9][-_a-z0-9]*\.)+[a-z]{2,6}|((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?))$/i;


/**
* Match a date in the format ##/##/####, where both the day and month value can be 1 or 2 digits,
* and the year can be either 2 digits or 4 digits when starting with 19 or 20.
*/
public static const SLASH_DATE:RegExp = /^\d{1,2}\/\d{1,2}\/(\d{2}|(19|20)\d{2})$/;


/**
* Match a five-digit U.S. Zip Code with an optional dash and four-digit extension.
*/
public static const US_ZIPCODE:RegExp = /^\d{5}(-\d{4})?$/;


/**
* Match a Canadian Postal Code in the format L#L #L# (where L is a letter). There is a
* restriction placed on the first letter in the Postal Code to ensure that a valid province,
* territory, or region is specified.
*/
public static const CA_POSTAL_CODE:RegExp = /^[ABCEGHJKLMNPRSTVXY]\d[A-Z] \d[A-Z]\d$/;

/**
* Match a social security number in the format ###-##-####, where the dashes are optional
* and the three groups can have optional spacing between them:
*/
public static const SSN:RegExp = /^\d{3}\s*-?\s*\d{2}\s*-?\s*\d{4}$/;


/**
* Match an IP address when you are only concerned that the address is formatted
* correctly with four groups of one to three digits separated by periods.
*/
public static const IP_ADDRESS:RegExp = /^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$/;


}
}

MichaelxxOA
06-12-2008, 10:49 PM
You want to take a crack at CompositeValidator???

(I)Validator??? ewwwww

CompositeValidator -> Validator

-addValidator
-removeValidator

MichaelxxOA
06-12-2008, 10:50 PM
Nevermind you don't need a composite validator so far. Rule #1 ... only do what you need to now! (that means don't code for the future, code for now)

Flash Gordon
06-12-2008, 11:00 PM
(I)Validator??? ewwwww

LoL :p I figured you'd love that one. HAHA

what should we tackle next, the InputElement(s)?

MichaelxxOA
06-12-2008, 11:07 PM
Yeah, let's try working on the form items. You know what ... I think we should no longer think of each individual thing on the form as a form element... instead lets think of them as a form item. Yeah?

FormItem
- value
- validate( validator:Validator ):Boolean;

Maybe start with the TextInputItem?

MichaelxxOA
06-12-2008, 11:14 PM
So I just noticed something in your SameStringValidator class.

_inArr is a field of the class, but is only useful while the validate and compare methods are running.

all of the variables in a class are useful for as long as the class lives

We can change this by keeping the variable alive for only as long as it is being used. We can make it so that you pass _inArr into the compare method which is being called by validate.


package org.actionscript.validations
{
import flash.errors.IllegalOperationError;


public class SameStringValidator implements IValidator
{
protected var _caseSensitive:Boolean;

public function SameStringValidator(caseSensitive:Boolean=true)
{
_caseSensitive = caseSensitive;
}

public function validate(input:*):Boolean
{
var results:Boolean = true;
_inArr = input as Array;
if (!_inArr) throw new IllegalOperationError("SameStringValidator/validate @param input must be of type Array");

for (var i:int; i<_inArr.length; i++)
{
results = results && compare(i, _inArr);
}

return results;
}

protected function compare(index:int, _inArr:Array):Boolean
{
var needle:String = _inArr[index] as String;
if (!needle) throw new IllegalOperationError("SameStringValidator/validate @param input must be of type Array/Vector.<String>");
var j:int=0;

if (!_caseSensitive)
{
needle = needle.toLowerCase();
for (j = index+1; j<_inArr.length; j++)
{
if (needle !== String(_inArr[j]).toLowerCase()) return false;
}
}
else
{
for (j = index+1; j<_inArr.length; j++)
{
if (needle !== String(_inArr[j])) return false;
}
}

return true;
}
}
}


Maybe something like that? What do you think?

Flash Gordon
06-12-2008, 11:25 PM
So I just noticed something in your SameStringValidator class.

_inArr is a field of the class, but is only useful while the validate and compare methods are running.

all of the variables in a class are useful for as long as the class lives
Great catch! Yes, I completely agree. :)

Flash Gordon
06-12-2008, 11:53 PM
So I'm run into a problem with the SameString validation design I believe. There is no way for me to handle a single InputTextField and use a SameStringValidator on it. I'm trying to come up with a slightly different design. What do you think?

package {
import flash.display.Sprite;
import flash.text.TextField;
import flash.text.TextFieldType;

import org.actionscript.forms.IFormItem;
import org.actionscript.forms.TextInputItem;
import org.actionscript.validations.*;

public class Form_Validation extends Sprite
{
protected var _lengthVal:IValidator = new StringLengthValidator(2, 4);
protected var _sameString:IValidator = new SameStringValidator(true);
protected var _emailVal:IValidator = new EmailValidator();

public function Form_Validation()
{
var txt:TextField = new TextField();
txt.type = TextFieldType.INPUT;
txt.text = "[email protected]";

var input1:IFormItem = new TextInputItem(txt);
trace( "TextInputItem (length)", input1.validate( _lengthVal ) );
trace( "TextInputItem (email)", input1.validate( _emailVal ) );
trace( "TextInputItem (same string)", input1.validate( _sameString ) );

}
}
}



package org.actionscript.forms
{
import org.actionscript.validations.IValidator;

public interface IFormItem
{
value:Object;

function validate(validator:IValidator):Boolean;
}
}


package org.actionscript.forms
{
import flash.text.TextField;

import org.actionscript.validations.IValidator;

public class TextInputItem implements IFormItem
{
public var value:String //= _tf.text;

protected var _tf:TextField;


public function TextInputItem(tf:TextField)
{
_tf = tf;
}

public function validate(validator:IValidator):Boolean
{
return validator.validate( _tf.text );
}

}
}


NEW CLASS VERSION

package org.actionscript.validations
{
import flash.errors.IllegalOperationError;


public class SameStringValidator implements IValidator
{
protected var _match:String;
protected var _caseSensitive:Boolean;

public function SameStringValidator(match:String, caseSensitive:Boolean=true)
{
_match = match;
_caseSensitive = caseSensitive;
}

public function validate(input:*):Boolean
{
var result:Boolean = false;
if (_caseSensitive)
{
result = (_match === input);
}
else
{
result = ( String(_match).toLowerCase() === String(input).toLowerCase() );
}

return result;
}
}
}

MichaelxxOA
06-13-2008, 12:07 AM
Thinking ... and busy ... you may not hear back from me for a bit, but I am pondering this intensely.

Let me know if you come up with any ideas.

Flash Gordon
06-13-2008, 12:07 AM
(all files in the zip, but here are current sections)

So as for error handling the error, we seems to have decided that the error notice will be a attribute of the Validation. It seems to make more sense that each FormItem has it's own way of handling the error and not the validation. However, I'm not exactly sure how "TextInputItem" should move a red arrow (from the sample form file a couple of post back). It's an external item. I suppose the red arrow should be an observer of the FormItem. Any thoughts on this.....when you're not so busy ;)

off topic, we're head to six-flags the following week if you wanna go with us.


package
{
import flash.display.Sprite;
import flash.text.TextField;
import flash.text.TextFieldType;

import org.actionscript.forms.IFormItem;
import org.actionscript.forms.TextInputItem;
import org.actionscript.validations.*;

public class Form_Validation extends Sprite
{
public function Form_Validation()
{
var txt:TextField = new TextField();
txt.type = TextFieldType.INPUT;
txt.text = "[email protected]";

var input1:IFormItem = new TextInputItem(txt);
trace( "TextInputItem (length)", input1.validate( new StringLengthValidator(2, 4) ) );
trace( "TextInputItem (email)", input1.validate( new EmailValidator() ) );
trace( "TextInputItem (same string)", input1.validate( new SameStringValidator("[email protected]", false) ) );
trace( "TextInputItem @param value ", input1.value );
}
}
}


package org.actionscript.forms
{
import org.actionscript.validations.IValidator;

public interface IFormItem
{
function get value():Object;

function validate(validator:IValidator):Boolean;
}
}



package org.actionscript.forms
{
import flash.text.TextField;

import org.actionscript.validations.IValidator;

public class TextInputItem implements IFormItem
{
public function get value():Object { return _tf.text; }

protected var _tf:TextField;


public function TextInputItem(tf:TextField)
{
_tf = tf;
}

public function validate(validator:IValidator):Boolean
{
return validator.validate( _tf.text );
}

}
}

MichaelxxOA
06-13-2008, 02:09 AM
Let's get them to work first.

MichaelxxOA
06-13-2008, 02:11 AM
The solution to SameStringValidator is easy. Simplify. SameTextValidator pass in a text field and it will use the validate method to check that their text properties equal ==.

MichaelxxOA
06-13-2008, 02:31 AM
As for the red arrow ... does the form let you know what form item you are on or does the form item let you know that you are on it?

Flash Gordon
06-13-2008, 02:32 AM
I went with this way of SameStringValidator("[email protected]") where the argument passed in the constructor because the pattern to match. Looks like either one would work.

The zip file I posted up in post 32 has all the current files and it is working. I can remake it to the way you suggested if you think it's a better solution, though I kind of like keeping it to strings as it seems more flexible that locking it into a textfied.

Flash Gordon
06-13-2008, 02:34 AM
As for the red arrow ... does the form let you know what form item you are on or does the form item let you know that you are on it?

Not sure what you mean by "on." The point of the red arrows was just to display an error message when the submit button was pressed. Thus, highlighting the area where the error occurred. Make sense?

MichaelxxOA
06-13-2008, 02:37 AM
No that works, sorry, I thought there was an issue. I didn't realize that you got it to work. Are you hard coding the string? It doesn't matter if you are though because you can just pass in the text string anyway.

Lets clear up the red arrow real quick though.
Edit: Yes it makes sense, I was thinking of it as if you were tabbing through, but it would be the same for errors too. Is that a feature of the form item?

Flash Gordon
06-13-2008, 02:57 AM
Edit: Yes it makes sense, I was thinking of it as if you were tabbing through, but it would be the same for errors too. Is that a feature of the form item?
I don't know. Is it a feature of the form or of the item? My initial thought is that it is decoupled from the Form or FormItem by make it an observer of the validation. If validation fails, the "ErrorItem" does what it needs to do.

What were you thinking?

MichaelxxOA
06-13-2008, 03:01 AM
In my opinion it's going to be easier to think of the arrow as a feature of that particular kind of form item, and then do what ever kinds of generalizations and such there. (perhaps the error view is a decorator of form items? idk)

I say you do the simplest thing you can ... so which do you think is simpler?

Flash Gordon
06-13-2008, 03:04 AM
I like the idea of making it a decorator to the form item. that sounds really nice, plus I haven't had much of a chance to play around with them. I'll have to read up on them tonight and give it a shot. I'll post back when I get some progress....unless you didn't mean decorator in the literal sense.

MichaelxxOA
06-13-2008, 03:05 AM
I did. :-) lol, let me know what you think.

OT: Yes I'm down for six flags 100% ... give me a date/time through e-mail or something

MichaelxxOA
06-13-2008, 03:56 AM
(all files in the zip, but here are current sections)

So as for error handling the error, we seems to have decided that the error notice will be a attribute of the Validation.

The error viewing is a feature of a particular kind of form item. The form item only collaborates with its validators to know whether or not the item is in an invalid state.

Sorry for the confusion.

Flash Gordon
06-13-2008, 05:28 PM
Ok here is what I have so far for the text error and the arrow error indicator. I haven't done anything with decorators before so if you see something off let me know. Also, I'm not sure who's responsibility it is to clear the red arrow when the textfield gets reset back to an empty string once you click back into it.

all src attached:


package org.actionscript.forms.decorators
{
import flash.errors.IllegalOperationError;

import org.actionscript.validations.IValidator;

public class ErrorDecorator implements IValidator
{
protected var _validator:IValidator

public function ErrorDecorator(validator:IValidator)
{
if (Object(this).constructor === this)
throw new IllegalOperationError("ErrorDecorator is an abstract class and can not be instantiated directly");
_validator = validator;
}

public function validate(input:*):Boolean
{
return _validator.validate(input);
}

}
}



package org.actionscript.forms.decorators
{
import flash.events.Event;
import flash.events.FocusEvent;
import flash.text.TextField;

import org.actionscript.validations.IValidator;

public class TextMessError extends ErrorDecorator
{
protected var _errorMes:String;
protected var _reciever:TextField;

public function TextMessError(validator:IValidator, reciever:TextField, errorMes:String)
{
super(validator);
_reciever = reciever;
_errorMes = errorMes;
}

override public function validate(input:*):Boolean
{
var valid:Boolean = super.validate(input);

if (!valid)
{
_reciever.text = _errorMes;
_reciever.addEventListener(FocusEvent.FOCUS_IN, focusIn, false, 0, false); // note** can't use a weak reference here
}

return valid;
}

protected function focusIn(e:Event):void
{
_reciever.text = "";
_reciever.removeEventListener(FocusEvent.FOCUS_IN, focusIn);
}

}
}



package org.actionscript.forms.decorators
{
import org.actionscript.display.IHideable;
import org.actionscript.validations.IValidator;

public class VisualNoticeError extends ErrorDecorator
{
protected var _visualNotice:IHideable;
public function VisualNoticeError(validator:IValidator, visualNotice:IHideable)
{
super(validator);
_visualNotice = visualNotice;
}

override public function validate(input:*):Boolean
{
var valid:Boolean = super.validate(input);

if (!valid)
{
_visualNotice.show();
}

return valid;
}

}
}

Document Class

package
{
import flash.display.MovieClip;
import flash.display.Sprite;
import flash.text.TextField;
import flash.text.TextFieldType;

import org.actionscript.display.IHideable;
import org.actionscript.forms.IFormItem;
import org.actionscript.forms.TextInputItem;
import org.actionscript.forms.decorators.*;
import org.actionscript.validations.*;

public class Form_Validation extends Sprite
{
public var arrow1_mc:MovieClip;
public var email_txt:TextField;

public function Form_Validation()
{
email_txt.text = "Hello$foobar.com";
validate();
}

protected function validate():void
{
var txtItem:IFormItem = new TextInputItem(email_txt);
var txtError:IValidator = new TextMessError( new EmailValidator(), email_txt, "Invalid Email" );
var arrowError:IValidator = new VisualNoticeError(txtError, arrow1_mc as IHideable);
txtItem.validate( arrowError );
}
}
}

MichaelxxOA
06-13-2008, 08:30 PM
My immediate thoughts ...

- The error decorators should not have anything to do with the validation.

What if when you create the form item you have the option of passing in a Validator object. The form item would have a validate() method which can be called ... each type of form item will implement validate in it's own way. If you have a TextInputItem and you are validating it's StringLength you would pass the .text of the TextField into the validator's validate( input ) method. If that returned false you could ... idk create ... the proper decorators (red arrow, red textfield)???

I don't know ... just some initial thoughts.

Flash Gordon
06-13-2008, 10:34 PM
Yea, I was thinking the same thing too. The error notices not really decorating the validation. It's a completely different functionality. The current way is a little weird.

If you did notice, I was able to use several decorators and not just one. This would be the goal as well as several validations (for example, the alternative email should be an valid email address AND should match another email address.)

I'm not quite sure I follow the new way of going about this that you posted above. Can you elaborte on your idea on how to revamp this with some stub-code signitures? I'll go through and actually implement.

Also, I'll be gone for a week. So I may not be posting a lot in the coming days, but I'm completely interested in learning and seeing this through to completion. It just may be a little slower, so bare with me :)

Flash Gordon
06-17-2008, 03:44 AM
well, I redesigned the overall structure so that i used a basic composite pattern though most items are leaf nodes. I think my final question is who's responsibility is it to send the URLVariables to the SSL and listen for the response? Is it the Form class's responsibility to send the data? It seems like Form's responsibility is to collect and manage the URLVariables. Sending the variables and listening for the response is another responsibility, no?

Here's what I currently have. It's close not but exactly like the current code.
http://img207.imageshack.us/img207/2610/classdiagram1fw5.jpg

MichaelxxOA
06-17-2008, 04:42 AM
So ... I think that form processing should be the responsibility of a FormProcessor object.

I would remove all of the url variable related stuff from the Form interface and instead make that a specific kind of FormProcessor. I would also have it so that you pass a Form the FormProcessor it needs to use ... and then have a process() method which invokes that whole operation.

So Form looks something like this now ...

Form( FormProcessor )
-addItem( FormItem ):void
-removeItem( FormItem ):void
-getItems():Iterator
-getValueOfItem( itemName:String ):String // stinks
-validate():Boolean
-process():void

Now the interface seems to be focusing on the domain of a Form ... which just "feels" nicer. There are no implementation details about vars and such and such. If you want something done (item added/removed, validation, processing) you just do it.

I did put in a note there about how that getValueOfItem method stinks. The fact that a FormItem has a name which is a string associated with it is an implementation detail ... leaking implementation details will most often lead to duplication. Suddenly I'm dealing with itemName all over the place. Another common implementation detail leak which leads to terrible kinds of duplication is returning Array's (except where they make sense) ... resulting in for loops doing all sorts of different things in all sorts of different places.

What do you think?

Flash Gordon
06-17-2008, 05:10 AM
The reason that FormItem has a name which is a string associated is for the URLVariables. Which becomes key => value on server side, where key is the string and value is the value of FormItem. I suppose this "key" could be added as a property to the FormItem but I'm not sure it is the FormItem's responsibility to set how it will be labeled as a server request. It sounds like you were interpreting that as a key to access the FormItem from the collection....i dunno, but just wanted to clear that up.

I would also have it so that you pass a Form the FormProcessor it needs to useI can only think of 1 type of FormProcess: collect the data and send it server side. What types of FormProcessors were you thinking?

MichaelxxOA
06-17-2008, 05:16 AM
I'm going to go ahead and point out a few other things too ... since I don't know if you are going to post before I leave work (I'm here late :-X).

The logic for displaying information about errors belongs to a particular kind of FormItem. FormItem itself is simple ...

FormItem( Validator )
- value
- validate()

For now let's just know that whatever kind of FormItem we add will collaborate with it's Validator (when its validate() method is called) and show the errors by creating it's decorators (how it gets its decorators idk yet ... we'll leave that for when we make that kind of FormItem).

I would just put the Hideable stuff in its own bubble. It's relevant, but not to the logic of what we are working on now.

MichaelxxOA
06-17-2008, 05:23 AM
The reason that FormItem has a name which is a string associated is for the URLVariables. Which becomes key => value on server side, where key is the string and value is the value of FormItem. I suppose this "key" could be added as a property to the FormItem but I'm not sure it is the FormItem's responsibility to set how it will be labeled as a server request. It sounds like you were interpreting that as a key to access the FormItem from the collection....i dunno, but just wanted to clear that up.

I can only think of 1 type of FormProcess: collect the data and send it server side. What types of FormProcessors were you thinking?

So for the first point ... we can make the FormItem.toString() return the proper name=value, and just iterate over the item's ... validate each one and if it passes add it to the write string (This is just one particular kind of serialization) ... this process of collecting the data from the form items and sending it is the responsibility of the FormProcessor.

For the second point ...

The logic and data associated with FormProcessing are only useful when processing the form. FormProcessing itself is useful whenever I want to process the form, but it's details are unimportant to me. This is me applying that whole class variables should live for the same amount of time as the rest of the variables (only I'm doing it a little prematurely ... but we can see that it's already going to happen just by thinking about it in concept)

Flash Gordon
06-17-2008, 05:32 AM
Section 1:
First off....go home!!!

Section 2:
Using FormItem.toString() return the proper name=value will give me several pairs like this:
$_POST['TextFieldItem'] = John Doe;
$_POST['TextFieldItem'] = foobar @gmail.com;
at least from what I understand what you are saying. I need specific key to data relationships not determined by the class name.
$_POST['name'] = John Doe;
$_POST['email'] = foobar @gmail.com;


Section 3:
For the second point ...

The logic and data associated with FormProcessing are only useful when processing the form. FormProcessing itself is useful whenever I want to process the form, but it's details are unimportant to me.For that, I have no idea what you mean :p. I only see 1 type of FormProcessing, which is to send key => data pair relationship to the severside.


Section 4:
I think I see the FormProcessor more as
IFormItemCollection
- getItems():Iterator;

Form implements IFormItemCollection

FormProcessor(IFormItemCollection)
- send(URLRequest)

MichaelxxOA
06-17-2008, 05:35 AM
So .. the thing is that the implementation detail itself is fine ... but we should strive really really hard just to make it an implementation detail and not a design detail. Making it available through the interface is leaking that implementation detail into the design.

Instead of exposing the name try to make it internal by introducing a function which does exactly what you need it to ... so you can tell a form item to getProcessableString ... or something ... idk ... just trying to illustrate the point (doing terribly sorry)

MichaelxxOA
06-17-2008, 05:40 AM
Section 3:
For that, I have no idea what you mean :p. I only see 1 type of FormProcessing, which is to send key => data pair relationship to the severside.


Section 4:
I think I see the FormProcessor more as
IFormItemCollection
- getItems():Iterator;

Form implements IFormItemCollection

FormProcessor(IFormItemCollection, URLRequest)
- send

Why not let the Form take care of those details? Your design is going in exactly the same direction except that you are exposing the details of how a Form works and making them the responsibility of other objects and the person using them.

Flash Gordon
06-17-2008, 05:45 AM
urgh....i'll be back tomorrow to post more about it. It's late on E.S.T. and it seems like I'm heading in circles.

MichaelxxOA
06-17-2008, 05:46 AM
For sure, I need to finish up work anyway. :) Later mang.

Flash Gordon
06-17-2008, 11:22 PM
So .. the thing is that the implementation detail itself is fine ... but we should strive really really hard just to make it an implementation detail and not a design detail. Making it available through the interface is leaking that implementation detail into the design.

Instead of exposing the name try to make it internal by introducing a function which does exactly what you need it to ... so you can tell a form item to getProcessableString ... or something ... idk ... just trying to illustrate the point (doing terribly sorry)

no idea what you are talking about here :confused: How is programming to an interface leaking and implementation?

Why not let the Form take care of those details? Your design is going in exactly the same direction except that you are exposing the details of how a Form works and making them the responsibility of other objects and the person using them.

How does an Iterator expose the details of a class? Isn't the sole purpose of the Iterator is to hide the internal implementation of the collection thus decoupling it from the implementation?

-------------------------
new section:
Whatever the case, the FormProcessor needs to get the collection of values from the Form in some respect or another. The FormProcessor is responsible for formatting it as XML or URL Variables (and there are the 2 differences I've been looking for in the FormProcessor) and sending it to a SSL. So the question becomes how to pull the data from the Form? I seem to think that using an Iterator to go through the objects would be ideal. I completely agree with what you said early The logic and data associated with FormProcessing are only useful when processing the form. FormProcessing itself is useful whenever I want to process the form, but it's details are unimportant to me.

As for making the validation and error displays Decorators, there seems on be 1 senerio that I'm thinking wouldn't work very well for me:
addValidator( new ErrorDisplay( new Validator() ) );
addValidator( new Validator() );

if the first validator passes but the second one fails, no error display will be shown for the FormItem. The current implementation show all error when any validation fail. I might be very nice to have specific error for each validation that fails, which decorators will certainly do nicely, but that means each Validator must have it's own error. If all error messages are the same, it's seems a bit wasteful. And maybe this should be for different FormItem(s): one that displays all errors (composite) and one that displays specific errors (decorators).

thoughts?

MichaelxxOA
06-18-2008, 12:23 AM
First Point (exposing name & serialization details)

So you are definitely programming to an interface there is no question about that. The problem is that you are leaking implementation details into that interface. The Form contains the details of how a form item needs to be serialized for the processing script. That is the leak. Those details are also only really useful when you are about to process the form. Lets try to keep our related logic and data as close as possible.

Ideally the FormProcessor will use the Form.getItems():Iterator method and iterate over each FormItem ... calling it's ... toString() ... or toXMLString() ... toSaveString() ... and adding that string to what will be sent to the server (or whatever).

The name of a FormItem can be exposed ... but the details of how it is used should not. Maybe you want Form.getItemByName( name ). Does that make sense?

For the second point.

We shouldn't mix the Validators in with the error view decorators.

So when we create the FormItem we pass it the Validators we want and we assume it already has error views .... a red arrow and a red text box or whatever.

When we call the Form.validate() method it will iterate over all of the FormItems and call each of their validate() methods. Inside of the FormItem.validate() method we could pass the proper input to that FormItem's Validator and if that validate returns false the FormItem creates the proper error views.

?

Flash Gordon
06-18-2008, 12:49 AM
Oh, you are talking about the pairing of getting the form item to the key value. I don't think it is going to be possible to serialize it based upon any class values. A name textfield input and an email textfield input can't have a key of "TextFieldItem." That doesn't make any sense to me (and that's probably not even what you are saying), but that's how I'm reading method calls like toString() ... or toXMLString() ... toSaveString(). Each instance is going to need it's own unique key. Which maybe means it's key should be passed into the constructor as the whole point of a FormItem it to send it's data server side with a key => value relationship.

?

For the validators, I think I'm going to finish up the way I was doing them. I can't seem to wrap my head around the situation you are talking about. When it's done, I'd love to go back through and rehash that issue a bit. sound good? Or maybe some more elaboration...but I don't want to wear you out on that issue...just yet. haha

MichaelxxOA
06-18-2008, 01:09 AM
Oh, you are talking about the pairing of getting the form item to the key value. I don't think it is going to be possible to serialize it based upon any class values. A name textfield input and an email textfield input can't have a key of "TextFieldItem." That doesn't make any sense to me (and that's probably not even what you are saying), but that's how I'm reading method calls like toString() ... or toXMLString() ... toSaveString(). Each instance is going to need it's own unique key. Which maybe means it's key should be passed into the constructor as the whole point of a FormItem it to send it's data server side with a key => value relationship.


So .. you would override the toString() method to return what you wanted, or if you wanted you could make more specific toMethods (ie. toXMLString, toSaveString). The FormItem would have a name property which you would use in the serialization .. toString() could return "email=[email protected]" or it could be toSaveString ... the point is that that particular implementation detail is encapsulated within FormItem and you use it indirectly by asking the FormItem for its "save" string.
?

For the validators, I think I'm going to finish up the way I was doing them. I can't seem to wrap my head around the situation you are talking about. When it's done, I'd love to go back through and rehash that issue a bit. sound good? Or maybe some more elaboration...but I don't want to wear you out on that issue...just yet. haha

For this maybe some code will help ...


class FormItem
{
var value;
var validator;

function validate()
{
var valid = validator.validate( value );
var message = "Content is not valid";

if ( !valid )
{
addError( new ErrorDisplay( this, message ) );
}
}
}


The FormItem collaborates with its Validator to determine whether or not an ErrorDisplay needs to be created.

?

Flash Gordon
06-18-2008, 01:54 AM
blah blah .. toString() could return "email=[email protected]" ...blah blah :p

Yea, exactly, but to even get to that point, you have to pass to FormItem a value of "email" somewhere. That's all I'm saying.....

I'm going to be working on the rest of the form tonight. I'll post of what I get and we can go from there.

Thanks for all the brilliant help, Michael!

MichaelxxOA
06-18-2008, 03:01 AM
You are absolutely right ... but it's a DETAIL! haha!

Looking forward to seeing what you got. Do you get what I was saying about the validators and error views???

Take care bro.

MichaelxxOA
06-18-2008, 03:06 AM
class FormItem
{
var value;
var validator;

function validate()
{
var valid = validator.validate( value );
var message = "Content is not valid";

if ( !valid )
{
addError( new ErrorDisplay( this, message ) );
}
}
}

?

Look I even messed up my example code. Always keep the data as close to the logic that uses it as possible.


class FormItem
{
var value;
var validator;

function validate()
{
var valid = validator.validate( value );

if ( !valid )
{
var message = "Content is not valid";
addError( new ErrorDisplay( this, message ) );
}
}
}

Flash Gordon
06-18-2008, 05:15 AM
Looking forward to seeing what you got. Do you get what I was saying about the validators and error views???
Actually no, that's why I wanted to put it off for a bit until I got the Form to work the way that I want it to. Now that it works, I'm in a better position to go back through and refactor some.

I believe everything is working correctly and I've even included some documentation. I think I'm pretty happy with it: it's seems cohesive and well decoupled. But what do you see????



It's too large to attach to the forms, so I've zip up the sources here (http://www.therealjoshua.com/actionscript_org/Form.zip). But here is the Document Class which demonstrates the API

http://img508.imageshack.us/img508/8121/formcollectionek8.jpg



package
{
// import * truncated for space

/**
* @private
*/
public class Form_Test extends Sprite
{
// declare stage instances
// ...truncated for space....

// actionscript instances
protected var _form:Form;
protected var _emailConfirm:SameStringValidator;

public function Form_Test()
{
initForm();
addListener();
}


protected function initForm():void
{
var item1:FormItem = new TextFieldItem(name_txt, "name", new StringLenValidator(3), new InsideTextError(name_txt, "name error") );
item1.addValidator( new DiffStringValidator("name error") );
item1.addDisplayError( nameErr as IHideable );

var item2:FormItem = new TextFieldItem(email_txt, "email", new EmailValidator(), new InsideTextError(email_txt, "email error") );
item2.addDisplayError( emailErr as IHideable );

var item3:FormItem = new TextFieldItem(confirm_txt, "confirm", new EmailValidator(), new InsideTextError(confirm_txt, "confirm error") );
item3.addValidator( _emailConfirm = new SameStringValidator(email_txt.text) );

_form = new Form();
_form.addItem(item1);
_form.addItem(item2);
_form.addItem(item3);
}



protected function addListener():void
{
submit_btn.addEventListener(MouseEvent.CLICK, submitForm, false, 0, true);
}

protected function submitForm(e:Event=null):void
{
_emailConfirm.testCase = email_txt.text;

if (_form.validate())
{
var processor:FormProcessor = new FormProcessor();
processor.collectVars( _form.getItems() );
processor.addVars( new URLVariables("foo=bar") );
processor.removeVarByKey("confirm");
processor.traceURLVariables();
processor.send( "form_processing.php", URLRequestMethod.POST );
}
}
}
}

Flash Gordon
06-21-2008, 02:06 AM
any quick thoughts before I go through an implement this thing into a site?

MichaelxxOA
07-07-2008, 08:40 AM
Sorry man I've been really busy.

I just wanted to drop by and mention two things ...

1. I don't think the display error stuff should be a part of the FormItem abstraction

2. You shouldn't have to have those methods public in FormProcessor.

... I can go into why if you are still interested, but I'm sure this project is done.

I hope all is well.

Flash Gordon
07-07-2008, 05:00 PM
yea it is "done." and I'm pretty happy with it. It is very flexible from what I've seen so far.

BUT i'm still interested in learning from the master so that I can apply the principles to my other designs if you want to continue talking about this one.

Cheers buddy :)

NickZA
12-16-2008, 09:44 AM
@Flash Gordon

I agree with what Michael said at the start about how to structure this. The method he suggested is the Flex method, almost exactly.

Next time consider using Flex for forms-based applications. You can then use Flex's Canvas class to tie in graphical elements that aren't form based, giving you the best of both worlds: Flash and Flex together.