Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I get some advice on JavaScript delegates?

I'm rusty with delegates and closures in JavaScript, and think I came across a situation where I'd like to try to use one or both.

I have a web app that behaves a lot like a forms app, with fields hitting a server to change data on every onBlur or onChange (depending on the form element). I use ASP.NET 3.5's Web Services and jQuery to do most of the work.

What you need to know for the example:

  • isBlocking() is a simple mechanism to form some functions to be synchronous (like a mutex)
  • isDirty(el) checks to make sure the value of the element actually changed before wasting a call to the server
  • Agent() returns a singleton instance of the WebService proxy class
  • getApplicationState() passes a base-64 encoded string to the web service. This string represents the state of the application -- the value of the element and the state are passed to a service that does some calculations. The onSuccess function of the web service call returns the new state, which the client processes and updates the entire screen.
  • waitForCallback() sets a flag that isBlocking() checks for the mutex

Here's an example of one of about 50 very similar functions:

function Field1_Changed(el) {
    if (isBlocking()) return false;
    if (isDirty(el)) {
        Agent().Field1_Changed($j(el).val(), getApplicationState());
        waitForCallback();
    }
}

The big problem is that the Agent().Field_X_Changed methods can accept a different number of parameters, but it's usually just the value and the state. So, writing these functions gets repetitive. I have done this so far to try out using delegates:

function Field_Changed(el, updateFunction, checkForDirty) {
    if (isBlocking()) return false;
    var isDirty = true; // assume true
    if (checkForDirty === true) {
        isDirty = IsDirty(el);
    }
    if (isDirty) {
        updateFunction(el);
        waitForCallback();
    }
}

function Field1_Changed(el) {
    Field_Changed(el, function(el) { 
        Agent().Field1_Changed($j(el).val(), getTransactionState()); 
    }, true);
}

This is ok, but sometimes I could have many parameters:

    ...
    Agent().Field2_Changed($j(el).val(), index, count, getApplicationState());
    ....

What I'd ultimately like to do is make one-linen calls, something like this (notice no getTransactionState() calls -- I would like that automated somehow):

// Typical case: 1 value parameter
function Field1_Changed(el) {
    Field_Changed(el, delegate(Agent().Field1_Changed, $j(el).val()), true);
}

// Rare case: multiple value parameters
function Field2_Changed(el, index, count) {
    Field_Changed(el, delegate(Agent().Field1_Changed, $j(el).val(), index, count), true);
}

function Field_Changed(el, theDelegate, checkIsDirty) {
    ???
}

function delegate(method) {
    /* create the change delegate */
    ???
}

Ok, my first question is: Is this all worth it? Is this harder to read but easier to maintain or the other way around? This is a pretty good undertaking, so I may end up putting a bounty on this one, but I'd appreciate any help you could offer. Thanks!

UPDATE

So, I've accepted an answer based on the fact that it pointed me in the right direction. I thought I'd come back and post my solution so that others who may just be starting out with delegates have something to model from. I'm also posting it to see if anybody wants to try an optimize it or make suggestions. Here's the common Field_Changed() method I came up with, with checkForDirty and omitState being optional parameters:

function Field_Changed(el, args, delegate, checkForDirty, omitState) {
    if (isBlocking()) return false;
    if (!$j.isArray(args) || args.length == 0) {
        alert('The "args" parameter in Field_Changed() must be an array.');
        return false;
    }
    checkForDirty = checkForDirty || true; // assume true if not passed
    var isDirty = true; // assume true for updates that don't require this check
    if (checkForDirty === true) {
        isDirty = fieldIsDirty(el);
    }
    if (isDirty) {
        omitState = omitState || false; // assume false if not passed
        if (!omitState) {
            var state = getTransactionState();
            args.push(state);
        }
        delegate.apply(this, args);
        waitForCallback();
    }
}

It handles everything I need it to (check for dirty, applying the application state when I need it to, and forcing synchronous webservice calls. I use it like this:

function TransactionAmount_Changed(el) {
    Field_Changed(el, [cleanDigits($j(el).val())], Agent().TransactionAmount_Changed, true);
}

cleanDigits strips out junk characters the user may have tried to type in. So, thanks to everyone, and happy coding!

like image 377
Cᴏʀʏ Avatar asked Jul 10 '09 21:07

Cᴏʀʏ


1 Answers

OK, few things:

  1. Delegates are extremely simple in javascript since functions are first class members.
  2. Function.apply lets you call a function with an array of arguments.

So you can write it this way

function Field_Changed(delegate, args)
{
    if (isBlocking()) return false;
    if (isDirty(args[0])) { //args[0] is el
        delegate.apply(this, args);
        waitForCallback();
    }
}

And call it as:

Field_Changed(Agent().Field2_Changed, [el, getApplicationState(), whatever...]);
like image 151
Chetan S Avatar answered Oct 20 '22 11:10

Chetan S