Wednesday, June 10, 2009

Service based, on-demand web forms with AJAX, JSON and JavaScript

If you are thinking what's with the weired title, then you are right. I am yet to come up with a better name of this design approach that I am going to talk about. But before that let me start with a real world example of a web-application.

Imagine you are part of a distributed project team responsible for building a production enabled, web-based service desk application in ASP.NET. One of the many features of the application is to allow users to create new tickets. In the create new ticket page users need to select from a drop down the type of service they want and based on their selection different set of forms with validation will be displayed on the page for the user to fill up and submit. Finally, after the form is submitted, it would gather all the information and create an HTML formatted email and send it to the service owner.

How would you go about implementing this functionality?

  1. One simple way to do it is to create separate web-forms for each type of service and redirect to the appropriate page depending on the user's selection of the type of service. Since the requirement is to have the forms be displayed within the same page we need to ensure that the look and feel of all the web-forms are consistent. Easiest and recommended way to achieve that will be to make use of Master pages and style sheets.
  2. Another option will be to create seperate DB table in line with the form structure and store the information there. Then build the form on the fly based on that structure and show it to the client
  3. We could also create an associative table (key,val) where the value of the rows are column names depicting the fields in the form.
Simple enough but do you see the problem with either of the above approaches?
  1. What happens when a form changes from one release to another ?
  2. What if we need to change any of the forms mid-release?
  3. What if we want to add a new form to a type of service once the application is in production?
  4. What if the team designing the forms are different from that application development team and only know HTML, JavaScript and CSS?
  5. What if we want to change the client side validation logic?
  6. What if the HTML display of the email needs to be changed?
For each of the above scenario every change to the form requires code change, database table tructure changes, testing cycles, build, deployment and a separate release just to put through an UI change. This could eventually lead to slower go-to-market time and be detrimental to the business. We need a way to be able to handle all the scenarios (1-6) more effectively. Ideally we would want team responsible for maing changes to the forms to be able to log in to the application, go to a maintenance section and make necessary changes and publish the form without having to make any "code change". So how to achieve this goal?

The Design:

The basic design is very simple. Instead of physically creating the forms and storing them on the web-server we store the forms in the database. Then when the client requests for a particular form for a selected type of service, we pull the necessary information from the database (making an AJAX call) and show it to the user.
Now you may ask, ok, displaying the form to the client is one thing (simple enough) but how about (i) handling form elements actions like click of a button, selecting a drop down, initializing some part on load on the client (ii) overall form validation before being submitted, (iii) reading the values of the form elements and (iv) creating the HTML display for the email?

First, let us examine the table structure where we store the forms. Let the table name be 'FormTable' which has the following columns (left out some of the columns for sake of brevity):

[frm_svc_id] [int] NOT NULL,
[frm_input_elements] [varchar](max) NOT NULL,
[frm_display] [varchar](max) NULL,
[frm_name_map] [varchar](max) NULL,
[frm_actions] [varchar](max) NULL,
[frm_validation] [varchar](max) NULL

frm_svc_id = The id of the service for which we need the form
frm_input_elements = The input form's HTML structure and elements
frm_display = The output HTML form that needs to be displayed after the form is submitted
frm_name_map = The display names of the input form fields
frm_actions = Event handling functions
frm_validation = The form validation function before submission

Let's look at an example what goes in [frm_input_elements]:

<table>
<tr>
<td>Date of Service</td>
<td><input type="text" name="frmdos"/></td>
</tr>
<tr>
<td>Environemnt</td>
<td>
<select nme="frmselect">
<option value="DEV">DEV</option>
<option value="PROD">PROD</option>
</select>
</td>
<td>
<input type="text" name="frmselectedenv"
value=""/>
</td>
</tr>
<tr>
<td>
<input type="button" name="frmsubmitbutton"
value="Submit"/>
</td>
</tr>
</table>

Now we want to add the following functionality to the form. On selecting the value from the 'frmselect' drop-down, display the selected value in the 'frmselectedenv' textbox. To do this all we need to do is to attach a function to the "onchange" event of the 'frmselect' dropdown. The function code will be something like this:

function dowork()
{
var selectobj = document.getElementById("frmselect");
var txtobj = document.getElementById("frmselectedenv");
txtobj.value = selectobj.options[selectobj.selectedIndex].value;
}

So how can we store this information? Enter JSON. The [frm_actions] column holds data in the following JSON structure:
[{id:'',eventtype:'',action:''},{id:'',eventtype:'',action:''},...,{EOF:true}]
So in our example, frm_actions will be as follows:

[
{id:'frmselect',
eventtype:'onchange',
action:'var selectobj = document.getElementById("frmselect");
var txtobj = document.getElementById("frmselectedenv");
txtobj.value = selectobj.options[selectobj.selectedIndex].value'
},
{EOF:true}
]

Similarly, the [frm_name_map] column, which is used to display the data after the form is submitted, holds data in the following JSON structure:
[{SystemFieldName:'',DisplayFieldName:''},...,{EOF:true}]
In our eaxmple it will be as follows:

[
{ SystemFieldName:'frmselectedenv',
DisplayFieldName:'SelectedEnvironemnt'
},
{ SystemFieldName:'frmdos',
DisplayFieldName:'Service Date'
},
{EOF:true}
]

We are all almost done. Now all is needed is to retrieve the JSON data for the form, display it and attach the proper event handlers, set up the vlaidation (if avialable) and read the form vlaues before submission. Enter JavaScript and JQuery.

The Implementation

The AJAX call to retrive the form's information willl return the following JSON object from the server side:
({frmelements:'',frmvalidation:'',frmdisplay:'',frmnamemap:'',frmactions:''})
where:
"frmelements" <= [frm_input_elements]
"frmvalidation" <= [frm_validation]
"frmdisplay" <= [frm_display]
"frmnamemap" <= [frm_name_map]
"frmactions" <= [frm_actions]
On the client side the JavaScript function "GetForm()" is the main engine of this design approach. It makes use of Javascript's ability to create and attach "anonymous functions" to events. We need to be careful when working with anonymous functions functions is JS as it can lead to memory leak.

//Global definition
var objArr = new Array();
var frmformdatadisplay = "";
var frmnamemap = "";
var frmFormValidation;

function GetForm(svcid)
{
var selectedid = document.getElementById(svcid).value;
//Clear up the array to prevent memory leaks
for (i = 0; i < objArr.length; i = i + 3) {
if ((objArr[i] != null) && ($get(objArr[i]) != null)) {
$get(objArr[i]).detachEvent(objArr[i + 1],
objArr[i + 2]);}
objArr[i] = null;
objArr[i+1] = null;
objArr[i+2] = null;
}
PageMethods.GetForm(selectedid, GetFormSuccess, GetFormFailed);
}

function GetFormSuccess(result)
{
var frmobj = eval(result);
if (typeof (frmobj) !== 'undefined')
{
$get("divfrm").innerHTML = frmobj.frmelements;
var j = 0;
for (i = 0; i < frmobj.actions.length; i++) {
var actionobj = frmobj.actions[i];
if (actionobj.id != "")
{
objArr[j] = actionobj.id;
objArr[j + 1] = actionobj.eventtype;
objArr[j + 2] = new Function(actionobj.action);
$get(actionobj.id).attachEvent(actionobj.eventtype, objArr[j + 2]);
j += 3;
}
else
{
//id = "" ==> action is not specific to any element. so execute it right away
if (actionobj.action != "") {
new Function(actionobj.action)();
}
}
}
frmformdatadisplay = frmobj.frmdisplay;
frmnamemap = frmobj.frmnamemap;
frmFormValidation = new Function(frmobj.frmvalidation);
}
}
And that's it! The only thing I left out is the parser to parse the form and extract the form fields and get the corresponding values and storing them. It's easy enought to iterate through the form element collection, and for each type of input element get the value and store it in a JSON object and save it in the datastore.

As with every thing in life, this design is not a one-solution-fits-all. It has its own drawbacks. With this aproach you loose the ability to store data on the server and use SQL Query to generate reports and perform searches. Also we are putting lot of functionality and processing on the client which could prove fatal. Moreover, you need write more code in contrast to other standard solutions and there will be learning curve involved for maintainng the forms.

But if flexibility is your goal, you do not want to go through a release cycle to push in a change, the form structures will be modified often enough by businesss and you can live without having to do SQL Query then this desing could prove beneficial.

In the next part, I'll explain how to create a Web 2.0 rich interface using JQuery to edit and manage these forms.



No comments:

Post a Comment