Friday, October 26, 2007

Using Asynchronous Javascript with validators

Before Asp.Net Ajax was released I used to work with a library called AjaxPro (formerly Ajax.Net). It also wrapped specially decorated methods and sent them to the Page as javascript functions with a callback javascript function as the last parameter. In this library, by not using a callback you were saying that you want to execute the function synchronously, that you want to wait for a result. After all, that's a functionality included in the XmlHttpRequest object.

I was amazed to see that Asp.Net Ajax does not offer such an option. You NEED a callback function. So... how do you use a WebMethod as a javascript validation function, since the validators only accept a synchronous function?

The first idea I had was the easy way, to cycle inside the function until I had a result. Bad idea: Javascript itself is not really multitask, so it just took the CPU to 100% and that was the end of it: kill task. The second idea was the hard way: redo all code in the XmlHttpRequest wrapper and use the included synchronous functionality. Too complicated, although I found an article about how to do that. So here is the way I did it:
1. Get the validator object
2. Set a property on it to tell if the validator is inside an async call or not
3. Set a property to hold the last value that was validated
4. Set a property to hold the last result returned
5. Set a method of the validator as the call back function
6. If is in async return false
7. If the last value is the same with the current validating value, return last result
8. Execute the async validation function with the validator callback method as a parameter and return false (or "in async")

Now, the callback method just sets the result and clears the async flag and also updates the visual of the validator. By doing this I essentially don't allow postbacks during validation calls, which makes sense. I also had an idea of creating a special "in async" validator that would not allow postback and would display something nice, but the solution I am explaining keeps it local, with ValidationGroups and everything working just fine.

I will show the code in a few seconds, but first be warned that I am using a validator made by myself which expects null or true as valid and a string or false as invalid. If the result is a string, it displays it as the ErrorMessage. So, on with the code:
The function to get a reference to the validator (which in some cases, especially Ajax, is not the same as the span with the same id)
function getValidator(id)
if (typeof(Page_Validators)=='object')
for (c=0; c<Page_Validators.length; c++)
if (Page_Validators[c].id==id) return Page_Validators[c];
return false;

The preparing function, which can be reused for more validators. I couldn't find a better way to encapsulate this behaviour yet, please feel free to enlighten me with ideas
// parameters are the value to be validated 
// and the validator object
function prepareAsync(value,validator) {
if (!validator) {
return 'Error in prepareAsync: validator not found!';
// it's validating
if (validator.inAsync) {
return 'In async validation';
// it has validated this value before
if (validator.prevValue==value) {
return validator.result;
// create the callBack function as a method
// of the validator object
if (!validator.callBack)
validator.callBack=function(res) {
if (res==null) {
} else
if (typeof(res)=='boolean') {
} else {
// setting the validation message is
// as simple as changing the innerHTML
// of the validator span
// that's an ASP.Net validator function
// since .NET 1.0 upwards

and now a sample validation function:
// parameters are the value to be validated
// and the id of the validator
function ValidateProdotto(value,id) {
// get the validator object
var validator=getValidator(id);
// reuse this function to all validators in the page
var res=prepareAsync(value,validator);
// doReturn says if it has a result for us
if (validator.doReturn) return res;
// perform the actual validation
// and return false, since we are in async now
return false;

Hope this helps somebody. This applies to short validations, but feel free to enhance my design with cancellations of running async operations in case the value is changed or with an array caching all the values tried and validated.


Jesse Mandel said...

Wow, exactly what I needed! Thanks a lot

Siderite said...

You are most welcome. Please share your experience with the validator if you find the time.

Marco said...

It worked perfectly to me!

Thank you very much!!!