Wednesday, December 12, 2012

2012-12-12 : A good day for a proposal for a JavaScript Validation Framework

Being a fortuitous date, 2012-12-12, I thought I'd make a fortuitous post about some work I'm doing in JavaScript. This pattern came into use while I was working with Mark Pahlavan on a design called gdef, for Gui DEFinition. We implemented gdef in Tcl (Tool Control Language). I extended the datastructure to support web requirements for form-wizard validation (groups, pages, a nextPage(page) function).

I propose that this structure, updated for JavaScript/JSON, and with some of the lessons learned from my open source web framework, "dynamide", would be ideal to transmit web service requests and futures back and forth.

All URLs are considered to be https URLs, with authentication (web-key), so authentication steps are ommitted below.

All code and structure will have a time-to-live (TTL) parameter, so should be cached aggressively.

The client requests a web service URL.

The server sends back a form to fill in.

  • Default values are in the form, correct for the authorized user.
  • Long lookups can be supported by URL placeholders, or by being tagged as auto-suggest.
  • The type of the form is given.
  • This type will have the actual validator code, so should be retrieved, used, and cached.
  • Inline validators in the form are allowed and encouraged. Where it gets clunky, move code to the validator Type.
  • All validators will work in a javascript sandbox, with a private global space [we can do this client-side, right???] (At the least, define sandbox as function sandbox(window, $, document) and then call as sandbox({},{},{}), and locking down arguments.caller, etc.)


The client looks at the form type and sees if it has form validator in cache, etc.
Client allows user to fill in datastructure, using some View.

View applies validator at will.

Client submits form to service.

Note that the service is an authorized view into the data available on the server. Just the allowed fields are allowed through. The application can send a full form, but will mark the fields as required based on the state on the server. The client app is allowed to show any of the fields and validators, but is only required to show required fields. Since validation and lookup code are provided in the form or form TypeValidator, the form can also contain calculated fields and the View can choose to display these as well.

 
This structure is an example, but is pretty rough.
I'll be updating it.   

Sandbox :: functions provided by the framework
  
function next(pageArray, page){
    if (!page || page.equals("INIT") ){
        return pageArray[0];  
    }
    var nextP = pageArray[pageArray.indexOf(page)+1];
    if (nextP){
      return nextP;
    } else {
      return "DONE";
   }
    
}
  

datastructure :: the thing that gets passed around.

FormType:  {
 pages: ["page1", "page2"],
 fieldGroup:["field1", "field2", "field3"],
 fields: [
  "field1": {type: "", 
  value: "", 
  validator: {code: "", 
  url: "", 
  registeredType: ""} 
  // Only one of these is used.  
  } ,
  "field2":{value: "3.33"}    
 ]
 nextPage(page){
  return next(this.pages, page);
 }
 errorPage(page, e){}
 
 validateField(oldValue, newValue){
  if (newValue....){
  return true;
  }
  return {ok: false, reason: "should be less than 550"};
 }
 
 validatePage(){}  
 
 validate(){
}  
NOTES:
  • Work out if the url can be used as a backup.
  • Work out if registeredType (namespace) is any different from a url.
  • Rules are open and shipped in the record-type struct.
  • type: "RecordType/com.myco.RenterDatabase.LeaseForm"
  • Rules only interact with js engine and Sandbox.
  • Sandbox should have String, Math, array goodies, plus jspath, and mongo-like queries.
      providers:
    • jspath
    • underscore.js
      But it would be important to get the set of functions in the sandbox right the first release.
    • LiveValidation
      This utility seems to have all the single-field cases nailed.
    • some mongo-like operators may or may not be essential. Compare with underscore.js
      tower stores
State machine:
==============================
LOAD: 
        type defined
        code defined
        data loaded
INIT: 
          init code given chance to run
SHOWING_PAGES:
          nextPage("INIT")
          nextPage(currentPage, error)
          errorPage(currentPage, error)
POSTING: 
POSTED_WAITING:
POST_DONE:
==============================

POST_DONE should have a message or signal that triggers a state or page from the type that shows where to go when done with this app or pageset. So the POST_DONE state would look at the signal and the type and figure out the internal page, or the external URL to go to.



errorPage should be called first, so if you want to handle both in nextPage(currentPage, error) you can say: errorPage = nextPage;

Your app can decide if errors prevent submission.

So now that we have the data structure defined, and its validator, we can run the same validation server-side, and also throw an enhanced, server-side validator at it. If we sign the struct's md5sum, or somesuch technique, we could vouch that that object passed our validation, and send the result off to some other service.



The data structure plus the data definition are then an application. They miss a view, and display rules, and i18n, but they can be driven completely like an application or a complex business object.

The page names are logical pages, so show grouping of fields, and also a namespace to pull values from for a View with subviews. Some fields are input, some fields are display only. So a view can generically or specifically represent this object using any number of templating or widget frameworks.

TODO: nextPage could contain page names or URLs, in which case processing can fly off. How to handle this is really a controller/view question, but should be written to support dialogs, etc.