I think I'm going to be stuck writing a messy dispatch function, but thought I'd check the S.O. community first to see if someone has come up with an easier solution.
Bless the World Bank for having an open API, but they have some implementation problems. First problem is their server doesn't implement CORS Access-Control-Allow-Origin: *
, so that means JSONP instead of JSON. That wouldn't be a problem, except that they don't preserve the case of the callback function! To cite a specific example, I make the following request in my code:
var deferredRegionsRequest = $.getJSON(
"http://api.worldbank.org/regions/?prefix=?",
{format: "jsonp"}
);
(Yes, they use the parameter prefix
instead of the traditional callback
.)
jQuery dutifully creates a random callback function and issues the AJAX request as:
http://api.worldbank.org/regions/?prefix=jQuery18308848262811079621_1393981029347&format=jsonp&_=1393981029990
Note that the callback function has an uppercase Q
.
The response that the World Bank returns, however, has renamed that callback to all lowercase!
jquery18308848262811079621_1393981029347([
{ "page": "1", "pages": "1", "per_page": "50", "total": "32" },
[ {
"id": "",
"code": "AFR",
"name": "Africa"
}, {
...
Obviously that's not going to work. If I were just making a single request, it wouldn't be too painful to provide my own callback function and use the jsonpCallback
parameter of $.ajax()
. Unfortunately, I need to traverse and loop through their REST API, so I'll be making dozens of (parallel) API calls. I've checked a custom callback, and this
is set to the global window
object, so there doesn't seem to be any obvious way for a single callback function to manage multiple responses. It seems like I might be stuck with a messy, custom dispatcher implementation, which I'd really like to avoid.
The only alternative I've come up with so far is to define a beforeSend
callback that takes the fully populated URL, parses it to find the callback function (jQuery...
), creates a new callback function dynamically that's an all lowercase version of the original (jquery...
), and does nothing by call the original. The hack quotient is so high my head spins, but I've verified that this does work:
$.ajaxSetup({
beforeSend: function(xhr, settings) {
var prefix = settings.url.match(/prefix=(.*?)&/);
if (prefix.length > 1) {
var callback = prefix[1];
if (callback !== callback.toLowerCase()) {
window[callback.toLowerCase()] =
new Function("response", callback + "(response)");
}
}
}
});
I sure hope I'm missing something really simple and someone very kind will point it out to me.
Your last alternative that you call a hack sounds like a mighty simple way to leverage all that jQuery does for you and just work around the one case issue by making your own function that's lowercase and just calling the mixed case one from it. Other than getting the World Bank to fix their server, this seems like the least hackish way of doing things to me. I can't see any better work-around.
If the jQuery callback function already exists at beforeSend
time, then all you need to do is to create a global variable with the lowercase name and put a reference to the mixed cased function in it. You don't even have to create a function body.
So, if you see jQuery18308848262811079621_1393981029347
in the URL in beforeSend
, then you just do:
window.jquery18308848262811079621_1393981029347 = jQuery18308848262811079621_1393981029347;
I guess it might take eval()
to make that work since you have the function's name as a string. Or perhaps one of the geniuses here can figure out how to create that line of code without eval()
.
Another option might be to make a single character patch to the jQuery library you use so it outputs only a lowercase string.
I think the "jQuery" string part comes from this:
jQuery.extend({
// Unique for each copy of jQuery on the page
expando: "jQuery" + ( core_version + Math.random() ).replace( /\D/g, "" ),
though it would take some debugging to confirm.
Or, it looks like maybe there's a callback function that creates the jsonpCallback name that could be either modified or replaced:
// Default jsonp settings
jQuery.ajaxSetup({
jsonpCallback: function() {
var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( ajax_nonce++ ) );
this[ callback ] = true;
return callback;
}
});
I found that this would work in my demo JSONP app http://jsfiddle.net/jfriend00/UM9NL/:
$.ajaxSetup({
beforeSend: function(xhr, settings) {
var origCallback = settings.jsonpCallback;
settings.url = settings.url.replace(/=jQuery/, "=jquery");
window[origCallback.toLowerCase()] = function() {
window[origCallback].apply(this, arguments);
}
}
});
And, this also works http://jsfiddle.net/jfriend00/GXte2/:
// Default jsonp settings
(function() {
var myajax_nonce = jQuery.now();
jQuery.ajaxSetup({
jsonp: "callback",
jsonpCallback: function() {
var callback = ( jQuery.expando.toLowerCase() + "_" + ( myajax_nonce++ ) );
this[ callback ] = true;
return callback;
}
});
})();