Cross domain, cross browser ajax with jsonp

Previously I looked at ways to send ajax GET requests cross domain using CORS. This is a great solution and works well across the browsers normally supported IE8+. This requires the server to have allow access from all origins though and is not supported by older browsers.

JSONP is a variation of json which stands for json with padding. It is a method which allows you to wrap any json data with a function callback on the server feed. This means that when it is loaded your function is called and the json is passed back as a parameter.

Here is an example of a feed of JSON
https://gdata.youtube.com/feeds/api/videos?q=dogs&v=2&alt=jsonc&max-results=5

The first line looks something like this:


{apiVersion: "2.1"...



However if we pass through a callback parameter we get can see the server also supports JSONP
https://gdata.youtube.com/feeds/api/videos?q=dogs&v=2&alt=jsonc&max-results=5&callback=functionname

Now the first line of the data returned shows:
functionname({"apiVersion":"2.1"

This means a function called functionname() {} will be called on our page with the data passed back to us.

So how do we make this usable when sending ajax requests? We can let the browser do the request for us using the script tag. First we need to generate a new function name automatically from the browser timestamp


var timestamp = 'callback'+new Date().getTime();

Then we create the function dynamically from the function name:

window[timestamp] = function(e) { console.log(e); };

Now we just need to append the url to a script tag in the page dynamically:

var script = document.createElement('script');
script.src = url+'&callback='+timestamp;
document.getElementsByTagName('head')[0].appendChild(script);

After the script tag has loaded, your function will be called and the data returned!

Lets put that into a reusable function we can call multiple times:

function ajax(url, callback) {
    var timestamp = 'callback'+new Date().getTime();
    window[timestamp] = function(e) { callback(e); };
    var script = document.createElement('script');
    script.src = url+'&callback=window.'+timestamp;
    document.getElementsByTagName('head')[0].appendChild(script);
}

usage would look like this:

ajax('https://gdata.youtube.com/feeds/api/videos?q=dogs&v=2&alt=jsonc&max-results=5', function(e) {
    console.log(e);
}

Here is an example of my full ajax function supporting CORS and JSONP

function ajax(url, callback, filetype, type) {
        filetype = filetype ? filetype : 'json';
        type = type ? type : 'GET';
        var success = function(e) {
            var items = '';
            switch(filetype) {
                case 'csv': items = csv(e); break;
                case 'json': items = JSON.parse(e); break;
                default: items = e; break;
            }
            callback(items);
        }
        var error = function(e) {
            console.log('Please enabled CORS using access-control-allow-origin');
        }
        if (filetype == 'jsonp') {
            var timestamp = 'callback'+new Date().getTime();
            window[timestamp] = function(e) { success(e); };
            var script = document.createElement('script');
            script.src = url+'&callback='+timestamp;
            document.getElementsByTagName('head')[0].appendChild(script);
        }
        else {
            var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');
            if (window.XDomainRequest && !sameOrigin(url)) { xhr = new XDomainRequest(); xhr.onload = function(){ success(xhr.responseText); }; }
            if (filetype == 'image' && xhr.overrideMimeType) { xhr.overrideMimeType('text/plain; charset=x-user-defined'); }
            xhr.onerror = error;
            xhr.onreadystatechange = function(e) { if (xhr.readyState == 4 && xhr.status == 200) { success(xhr.responseText); } }
            try {
                if ('withCredentials' in xhr) { xhr.open(type, url, true); }
                else { xhr.open(type, url); }
                xhr.send(null);
            }
            catch(e) { error(e); }
        }
}


function sameOrigin(url) {
        var split = url.split('/');
        if (split[0]+'//' == window.location.protocol+'//') { return split[2] != window.location.host ? false : true; }
        else { return true; }
}

And here is a link to my Utils module where I store my ajax and other helper functions:
http://www.kimturley.co.uk/js/modules/Utils.js