Working with JSONP
In petfinderAPI4everybody.js, I fetch data from Petfinder’s API on the client side using only native JavaScript—no jQuery needed.
Today, I want to share my code and talk about my approach.
The Technique
To use API data hosted on a domain other than your own, you need to use a technique called JSONP. From Wikipedia:
JSONP or “JSON with padding” is a communication technique used in JavaScript programs running in web browsers to request data from a server in a different domain, something prohibited by typical web browsers because of the same-origin policy. JSONP takes advantage of the fact that browsers do not enforce the same-origin policy on
<script>
tags.
Here’s how it works: The API request URL is set as the src
of a <script>
tag, which gets appended to the DOM. That data is then passed into a callback method on load. The callback is included in the request url as ?callback=callbackMethodName
;
The Code
/**
* Get JSONP data for cross-domain AJAX requests
* @private
* @link http://cameronspear.com/blog/exactly-what-is-jsonp/
* @param {String} url The URL of the JSON request
* @param {String} callback The name of the callback to run on load
*/
var loadJSONP = function ( url, callback ) {
// Create script with url and callback (if specified)
var ref = window.document.getElementsByTagName( 'script' )[ 0 ];
var script = window.document.createElement( 'script' );
script.src = url + (url.indexOf( '?' ) + 1 ? '&' : '?') + 'callback=' + callback;
// Insert script tag into the DOM (append to <head>)
ref.parentNode.insertBefore( script, ref );
// After the script is loaded (and executed), remove it
script.onload = function () {
this.remove();
};
};
And here’s an example using the Petfinder API:
var logAPI = function ( data ) {
console.log( data );
}
getJSONP( 'http://api.petfinder.com/shelter.getPets?format=json&key=12345&shelter=AA11', 'logAPI' );
Client-Side Performance
Loading data from APIs can be slow, so you want to minimize the amount of times you have to request data.
With petfinderAPI4everybody.js, I decided to store API data in localStorage so that after initial page load, the data is immediately available for use on each subsequent page load. I added an expiration date so that after a certain period of time, new data will be fetched to replace it.
The Code
Here’s the code that makes it all work:
var getJSONP = ( function( window, document, undefined ) {
// Set Variables
var getJSONP = {}; // Object for public APIs
var settings = {}; // Object for settings
var url, localAPI;
/**
* Get JSONP data for cross-domain AJAX requests
* @private
* @link http://cameronspear.com/blog/exactly-what-is-jsonp/
* @param {String} url The URL of the JSON request
* @param {String} callback The name of the callback to run on load
*/
var loadJSONP = function () {
// Create script with url and callback (if specified)
var ref = window.document.getElementsByTagName( 'script' )[ 0 ];
var script = window.document.createElement( 'script' );
script.src = url + (url.indexOf( '?' ) + 1 ? '&' : '?') + 'setAPIData';
// Insert script tag into the DOM (append to <head>)
ref.parentNode.insertBefore( script, ref );
// After the script is loaded (and executed), remove it
script.onload = function () {
this.remove();
};
};
/**
* Save remote API data to localStorage and set variable for use
* @public
* @param {Object} data API data object from Petfinder
*/
getJSONP.setAPIData = function ( data ) {
// Check for error codes
// Replace with relevant name and code from your API
if ( data.someCode === '000' ) {
console.log('Unable to get data from the API. Using expired localStorage data instead.');
run();
return;
}
// Save API Data to localStorage with expiration date
var expirationMS = parseInt( settings.expiration, 10 ) * 60 * 1000;
localAPI = {
data: data,
timestamp: new Date().getTime() + expirationMS
};
localStorage.setItem( settings.localAPIid, JSON.stringify(localAPI) );
// Run methods after data loads
run();
};
/**
* Get API data from localStorage
* @private
*/
var getAPIData = function () {
// Get API data from localStorage
localAPI = JSON.parse( localStorage.getItem( settings.localAPIid ) );
// If local data exists and hasn't expired, use it
if ( localAPI ) {
if ( new Date().getTime() < localAPI.timestamp ) {
run();
return;
}
}
// If local data doesn't exist or has expired, get fresh data
getJSONP( url );
};
/**
* Do stuff after API is loaded
* @private
*/
var run = function () {
// If no API data is available, log error
if ( !localAPI ) {
console.log( 'Unable to retrieve data from the API or localStorage.' );
return;
}
// Do stuff here...
console.log( localAPI );
};
getJSONP.init = function ( url, options ) {
// Check for localStorage support before running
if ( !window.localStorage ) return;
// If no URL is supplied, quit
if ( !url ) return;
// Merge options with defaults
settings.expiration = options.expiration || 60;
settings.localAPIid = options.localAPIid || 'localJSONP';
// Get API data
getAPIData();
};
// Return public methods
return getJSONP;
})( window, document );
And here’s an example using the Petfinder API:
getJSONP.init( 'http://api.petfinder.com/shelter.getPets?format=json&key=12345&shelter=AA11' );