Click here to Skip to main content
Click here to Skip to main content

Tagged as

Go to top

Using jQuery Binding to make cross-domain calls with Closure Callbacks

, 12 Aug 2014
Rate this:
Please Sign up or sign in to vote.
It was hard to come up with a title to this post because I somehow needed to convey the awesomeness for a problem which I don’t think a lot of people realise they have. Quite simply, it is to do with the asynchronous manner in which we make JSONP calls (if you’re not sure how […]

It was hard to come up with a title to this post because I somehow needed to convey the awesomeness for a problem which I don’t think a lot of people realise they have.

Quite simply, it is to do with the asynchronous manner in which we make JSONP calls (if you’re not sure how JSONP works, I recommend this simple article from Rick Strahl).  As you know (or will after reading the article), JSONP relies on dynamically injecting a <script/> tag into our document.  Within this tag are two parts of javascript:

  1. the object you are returning from the server (in JSON format)
  2. a function wrapper which ‘pads’ the object (the ‘P’ in JSONP).

For example, if I wish to return Person object from the server, I would stream down:

  1. on the server, create the object in JSON (using Response.Write() for example in .Net):
Person{FirstName : Larry, LastName : Phillips}

2. wrap the object in a function name ‘mycallback’:

mycallback({Person{FirstName : Larry, LastName : Phillips }})

On the awaiting HTML page, I would have already written the following javascript function:

function mycallback(person){

    alert(person.FirstName);

}

The script tag is injected into the page, the response is sent back, the browser automatically runs the Javascript within the script tag, the awaiting function is thereby called and voila – an alert() box is shown

Problem: the callback is decoupled from the caller

This looks tidy enough with one example, but when you have dozens or even hundreds of callbacks (such as I have on www.stringsof.me, which prompted this solution) it becomes very hard to manage because for every server call you need to write a separate corresponding callback.  It’s not shown in the example, but often the callback needs to tie back to the caller in some way to alter its state, which makes things even more complicated.

This is extra frustrating because if you are working with jQuery (for example), you are dealing with nice ‘inline’ callbacks when using regular AJAX calls:

$.ajax({

    url: that.SiteRoot + route,

    data: params,

    type: 'GET',

    dataType:"json",

    success:function(person){

        alert(person.FirstName);

    }

});

See?  The callback is written right within the AJAX call.  For programmers, it is tidy and easy to follow.  Unfortunately, if you specify the dataType property as ‘jsonp’, the callback doesn’t work.

Unfortunately, we can’t do this with a cross-domain call….

The reason the callback above doesn’t work for jsonp/cross-domain is (presumably) because it is not technically an AJAX call.  From Javascript’s point of view, it is just injecting a new DOM element into the page (the <script/> tag).  Once the tag’s src is downloaded, Javascript has already moved on to the next task.  It is the hack I described above which allows us to link the two.

…until now!  Using jQuery’s bind and trigger

Enter jQuery’s bind and trigger functionality.  Observe…if I write…

$(&lsquo;body&rsquo;).bind(&lsquo;foo&rsquo;, function(){alert(&ldquo;I&rsquo;m called");});

…and then at any time later, I write….

$('body').trigger('foo');

…the popup appears.  So I saw this, and I thought, perhaps I can fake a callback between the two JSONP events.  So, here goes…

var Data = function () {
    var that = this;
    
    
    return {
        CallJSON : function(route, params, callback) {
            // The trigger name must be unique for each call, so that multiple (almost) concurrent AJAX
            // calls can be made and assigned back to the same trigger each time
            var triggerName = new Date().getTime().toString();
            params._JsonPCbTrigger = triggerName;
 
            // The traditional JSONP callback is provided, and points to the OnCallJSON function below
            // Note that because it is hard-coded (and therefore the same each time), it could equally be hard-coded on the server
            params._JsonPCb = 'Data.OnCallJSON';
            
            // Use jQuery to prepare for a trigger called triggerName                
            $("body").bind(triggerName, function(e, result) {
                // Within the callback, we ignore the 'e' parameter (a jQuery artifact), and
                // just pass the result straight through to the callback we passed into the main function
                callback(result);  
            });
        
            // Make the JSON call as usual
            $.ajax({
                url: 'http://www.stringsof.me/ + route,
                data: params,
                type: 'GET',
                dataType:"json"
            });
        },
        
        // This is the generic handler for *all* JSONP calls.
        OnCallJSON : function(result, triggerName) {
            $("body").trigger(triggerName, result);
 
            // We unbind afterwards, simply to release memory
            $("body").unbind(triggerName);
        }
    };
} ();

Now, anywhere in my code, I can call (for example):

var params = { personID: 1 };

Data.CallJSON('GetPersonByID', params, function(person) {

    alert(person.FirstName);

});

The Server Code

That actually concludes the article, but for completeness and in case readers are still a little confused over JSONP, I’ll include the server code that is required to make this work.  It’s in C#, but in essence it is simply writing regular Javascript to the response.

// Get the variables from the Request that we have sent up from Javascript
var callBackTrigger = context.Request.Params["_JsonPCbTrigger"];
var callbackFunctionName = context.Request.Params["_JsonPCb"];
var personID = int.Parse(context.Request.Params["personid"]);
 
 
// Defer to service to get the requested person
var person = new PersonManager().GetPerson(personID);
 
// Encode to JSON format e.g. {FirstName:'Larry', LastName:'Phillips'}
var personJSON = Newtonsoft.Json.JsonConvert.SerializeObject(person);
 
// The callback function on the client receives TWO parameters - the result that
// we want, and the name of the trigger that jQuery needs for the .trigger()
var parameters = personJSON + ", " + callBackTrigger;
 
// Finally, wrap the parameters in the function so that it is automatically
// executed when the client renders it
var functionCall = callbackFunctionName + "(" + parameters + ")";
 
// Write to response
context.Response.Write(functionCall);
context.Response.End();

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Ben Liebert
Architect BlackBall Software
New Zealand New Zealand
No Biography provided
Follow on   Twitter

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Mobile
Web03 | 2.8.140905.1 | Last Updated 12 Aug 2014
Article Copyright 2014 by Ben Liebert
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid