admin管理员组

文章数量:1335542

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 e 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 mon 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!

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 e 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 mon 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!

Share Improve this question edited Jul 13, 2009 at 22:04 Cᴏʀʏ asked Jul 10, 2009 at 21:52 CᴏʀʏCᴏʀʏ 108k20 gold badges168 silver badges198 bronze badges 1
  • Cory, though you encountered this while working with web services, it's not specific to them. Maybe you should remove the web-services tag (and add "asp"). – John Saunders Commented Jul 10, 2009 at 21:56
Add a ment  | 

3 Answers 3

Reset to default 5

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...]);

I have been using the following utility function that I wrote a long time ago:

/**
 * @classDescription This class contains different utility functions
 */
function Utils()
{}

/**
 * This method returns a delegate function closure that will call
 * targetMethod on targetObject with specified arguments and with
 * arguments specified by the caller of this delegate
 * 
 * @param {Object} targetObj - the object to call the method on
 * @param {Object} targetMethod - the method to call on the object
 * @param {Object} [arg1] - optional argument 1
 * @param {Object} [arg2] - optional argument 2
 * @param {Object} [arg3] - optional argument 3
 */
Utils.createDelegate = function( targetObj, targetMethod, arg1, arg2, arg3 )
{
    // Create an array containing the arguments
    var initArgs = new Array();

    // Skip the first two arguments as they are the target object and method
    for( var i = 2; i < arguments.length; ++i )
    {
        initArgs.push( arguments[i] );
    }

    // Return the closure
    return function()
    {
        // Add the initial arguments of the delegate
        var args = initArgs.slice(0);

        // Add the actual arguments specified by the call to this list
        for( var i = 0; i < arguments.length; ++i )
        {
            args.push( arguments[i] );
        }

        return targetMethod.apply( targetObj, args );
    };
}

So, in your example, I would replace

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

With something along the lines

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

Then, inside of Agent().FieldX_Changed I would manually call getApplicationState() (and encapsulate that logic into a generic method to process field changes that all of the Agent().FieldX_Changed methods would internally call).

Closures and delegates in JavaScript: http://www.terrainformatica./2006/08/delegates-in-javascript/ http://www.terrainformatica./2006/08/delegates-in-javascript-now-with-parameters/

本文标签: aspnetCan I get some advice on JavaScript delegatesStack Overflow