Tuesday, November 29, 2005

AJAX Podlets for Lower TCO

I was playing around with this past weekend with trying to create a personalization system that could be plugged into any web site. It is basically an experiment, so it is almost all completely driven by JavaScript. One thing I found was that using AJAX it was possible for me to create, for example, a login form, that could potentially be placed on any site with almost no coding. This is because AJAX changes the way we validate forms. In this entry I will discuss the traditional way of handling a login (or any other) form, and the AJAX way.

The triditional approach to form handling is a four part coding process.

1. Display an HTML form.

Just your everyday plain old HTML form.

2. Server-side validation of the form.

My rule is that my developers MUST implement validation on the server, and OPTIONALLY on the client. Because it is possible to manipulate JavaScript run on the client, and can be turned off, there must be a server-side validation component to the process.

3. On validation error, redisplay form with pre-populated values.

If there is a validation error you need to redisplay the form. If you want to be user-friendly, you also need to pre-populate the form with the values that the user has already entered. This is where we really deviate from a clean and reusable implementation.

4. On successful validation, show "Thank You" page.

This could be an entry confirmation, the main page after authentication, or any other page that lets the user know that the process has been completed.

The Issues

The messy part in the implementation is step #3, where we need to redisplay the pre-populated form. We will need to set the values of the text boxes, write a little code to pre-check checkboxes, and functions to select the correct options in drop-down menus. This typically requires some sort of server-side template mechanism, which could be JSP, PHP, ASP, Velocity, HTML::Template, Mason, or any one of a hundred different tools.

This is further complicated if, for example, I want to place this form on multiple pages. I will need to templatize every page that I want to place the form on, which may also involve changing link if the page needs to be renamed from a .html to a .asp.

What if I wanted to use this form in multiple servers using different technologies? I might end up coding an ASP version on one server, and a PHP version on another. I would end up having to do twice the work.

What if I wanted to reuse the form, perhaps on muliple client's sites? Pure develop ment houses often have a library of prebuilt applications that can be pulled off the shelf and customized for a client. This lowers the cost of delivering a product.

The big question... How can I create a reusable component from a form?

The Answer

You can tell by the title of this entry what the answer is. It's AJAX. AJAX changes the model for creating a form by removing step #3 ("On validation error, redisplay form with pre-populated values"). When the user submits the form we can submit the data to the server using AJAX, and the server returns just the validation/submission results instead of having to redisplay the form when an error occurs.

The flow is more like this:

1. Display an HTML form.
2. User clicks "submit", data is sent to server via AJAX.
3. Browser recieves results of validation, displays errors.
4. If no validation error, browser redirects to "Thank You" page.

This solves every one of the traditional issues, and if done with care, it is possible to package the form as a component that can be simply added to any page.

A Concrete Example

For my login component I am using three JavaScript libraries:

Prototype : Form helper functions, AJAX helpers, and much more.
script.aculo.us : Visual effects.
Behaviour : Add events in a script include (i.e. clean seperation of code and logic).

Here is my HTML for my login component. I can drop this into any page.

<div id="my_login_area">
<form id="my_login_form" action="" method="post">
<div class="my_login_text">
username
</div>
<div class="my_login_input">
<input id="my_login_username" type="text"
name="username" value="" />
</div>
<div class="my_login_text">
password
</div>
<div class="my_login_input">
<input id="my_login_password" type="password"
name="password" value="" />
</div>
<div class="my_login_input">
<input id="my_login_button" type="button" value="login" />
</div>
<div id="my_login_status"></div>
</form>
</div>


Note the absense of any onclick, onsubmit, or other event attributes. The Behaviour library will allow us to add the events in our script file, so this HTML won't change. Because this is pure HTML, without even JavaScript, your designer can plug it into their design and add CSS to style the form without any help from a developer. Because the developer doesn't need to know anything about the HTML, other than that this HTML block is being used without change, they don't need to give any special instructions to the designer. This clear seperation makes development and maintenance easier and less costly.

The code below will preferrably live in an external JavaScript file, or could be in the head of the HTML page.


Behaviour.addLoadEvent(
function () {
Form.enable($('my_login_form'));
Form.reset($('my_login_form'));
Form.focusFirstElement($('my_login_form'));
$('my_login_status').innerHTML = '';
}
);

var myrules = {

'#my_login_button' : function(e) {
e.onclick = function() {
sendLogin();
}
}

};

Behaviour.register(myrules);


The call to Behavior.addLoadEvent() adds the shown function to the body onload even handler. This code uses the Prototype library to reset the form, put focus on the first element of the form, and clear any status message.

All the rest does is does add an onclick even to the submit button, which calls sendLogin() when the button is clicked. It might seem counter-intuitive to do it this way instead of just adding onclick and onload attributes to the HTML code, but again, this is to completely seperate the HTML from the code.

The last step is to write the actual AJAX part to query the server. For this login component the server will send back a simple text message containing up to 3 lines of text.

Line 1: "OK" on success, or "FAIL" on failure to authenticate
Line 2: On success this will be the value of the session cookie.
Line 3: On success this will be the page to redirect to.

Here is the code.


function sendLogin ()
{
Form.disable($('my_login_form'));
$('my_login_status').innerHTML = 'Status: Authenticating.';
new Effect.Pulsate($('my_login_status'), {duration: 7.0});

var url = '/cgi-bin/login.cgi?'
+ Form.serialize($('my_login_form'));

new Ajax.Request(url, {
asynchronous: true,
method: "get",
onSuccess: function (request) {
setLogin(request);
},
onFailure: function (request) {
$('my_login_status').innerHTML
= 'An error occured. Unable to authenticate.';
}
});
}

function setLogin (request)
{
var res = request.responseText.split(/\r?\n/);

if (res[0] == 'OK') {
$('my_login_status').innerHTML
= 'Success. Starting session.';
document.cookie = 'myLoginKey=' + res[1];
document.location = res[2];
}
else {
$('my_login_status').innerHTML
= 'Incorrect credentials.';
Form.enable($('my_login_form'));
}
}


I leave disecting the code above as an excercise to the reader, but a breif explanation will help. When the user submits the form, the Form.serialize() helper from the Prototype library creates a querystring of the form data for us so that we can send it to the server. Right before the data is sent we use the Effect.pulsate() visual effect from the script.aculo.us library to flash the status message so that the user can see that something is happening. Visual cues like this are important since we are taking a non-traditional form submission approach that the user may not be familiar with. We then send the request and set handlers to handle success and failure events.

The setLogin() function is called when the results from the server are returned. In our case a successful login will set a session cookie, and then redirect us to some other page, as specified by the server response. On autentication failure we let the user know, and let them try again.

In the end, assuming that we put all of our JavaScript in external files, we have a complete component that can be dropped into any HTML page, and solves all of our specified issues.

2 comments:

Anonymous said...

Great article. Thanks
Do you have an example demo to go with this article?
Thanks again

Anonymous said...

I tried your example using classic ASP and it worked like a charm. This is one of the best tutorials in Ajax I have seen. Very well explained and very easy to follow. Keep it up.
Thanks again