Jumping Into Javascript – Same Origin Policy, JSONP, XMLHttpRequest, and Cheating with jQuery

In the last installment we successfully created an array of rectangle objects and moved them across the screen. This post will deal with retrieving JSONP data from a different domain.

Figuring out how to accomplish this took me longer than I care to admit. I began researching the same origin policy around 14:00 today and after about 30 minutes I knew I needed to find a service that offered stock quotes in JSONP format, which allows cross-domain access. I spent another 2 hours looking for a source of historical stock data in JSONP format before stumbling upon this post by IBM developerWorks. That led me to the Yahoo Query Language (YQL) which is a SQL-like language for retrieving tons of different datasets in XML or JSONP format. I proceeded to fool around with that for another 2 hours or so before figuring out how to access Yahoo historical stock data through YQL.

After 4 1/2 hours of looking for a new data source that was consumable by Javascript, I was not really in the mood to figure out the intricacies of portable XMLHttpRequest (which is bound to be a post, if not series, in itself). So, at least for the moment, I cheated and included the jQuery library from Google for the sole purpose of retrieving the JSONP data. Overkill? Yes, most definitely. For the moment though, it will have to do.

Total time to get this very basic feature implemented? 6 1/2 hours, including 2 meals and a number of games of darts with the new blowgun I bought yesterday. Moral of the story: the security practices in Javascript can make your life a real pain in the…

index.html

<html>
<head>
  <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.js"></script>
  <title>JamochaTrade - A Javascript stock charting program.</title>
  <style>
    body {text-align: center;}
    canvas {border: 1px solid #000;}
  </style>
</head>
<body>
  <canvas id="chart"></canvas>
  <script src="chart.js"></script>
</body>
</html>

chart.js

function getUrl(symbol) {
  // This should use the current date instead of a hardcoded date.
  return "http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20csv%20where%20url%3D'http%3A%2F%2Fichart.finance.yahoo.com%2Ftable.csv%3Fs%3D" + symbol + "%26d%3D2%26e%3D27%26f%3D2011%26g%3Dd%26a%3D0%26b%3D1%26c%3D2000%26ignore%3D.csv'&format=json&callback=?";
}
  
function parseData(result) {
  // This returns an array of date objects the first row is labels.
  // For now use console.log so we can inspect the result in FireBug
  console.log(result.query.results.row);
}

// Loading and using jquery just for this is definately overkill.
$.getJSON(getUrl("ibm"), parseData)


var chart = document.getElementById("chart");
var c = chart.getContext("2d");

chart.onclick = moveRects;

function drawRects() {
  for (var i in rects) {
    var r = rects[i];
    c.fillRect(r.x, r.y, r.width, r.height);
  }
}

function moveRects() {
  for (var i in rects) {
    rects[i].x += 20;
  }
  c.clearRect(0, 0, chart.width, chart.height);
  drawRects();
}

function Rect(x, y, width, height) {
  this.x = x;
  this.y = y;
  this.width = width;
  this.height = height;
}

var rects = [new Rect(10, 25, 20, 50), new Rect(40, 25, 20, 50)];

drawRects();

For the moment I considered it more instructive to simply simply spit the result out to the console so we can inspect it. If you’re interested in seeing the full result object just use this code at line 9: console.log(result).

 

An Asynchronous Request

jQuery’s getJSON procedure makes an asynchronous request for JSONP data. What does this mean exactly? For one, it means that some things you might have thought would work, simply won’t. Take this as an example:

var data = false;
$.getJSON(getUrl("ibm"), function(result) 
                           { data = result.query.results.row; } );

console.log(data);

In a synchronous environment one would expect data to contain the result of the getJSON request. However, getJSON is an asynchronous method to fetch data. This means that the variable data may or may not hold the result of getJSON depending on how long it takes getJSON to fetch the data. If console.log is called before getJSON returns, the value of data will be false.

There are a number of solutions to this problem and I’m not really sure which is the best practice. First, using jQuery’s ajax procedure and setting async to false will not work with JSONP, just read the async entry in the link. There’s also a note in there about synchronous requests possibly freezing the browser, which is the major downside of this next approach.

data = false;
$.getJSON(getUrl("ibm"), function(result) 
                           { data = result.query.results.row; } );

while (data === false) { }

console.log(data);

Almost every time I try this I get the, “A script has become unresponsive” alert. Though it does work, this is not the kind of user experience I want in my apps.

My next thought was to use a sleep function and check the value of data only so often to prevent the above error. Well it turns out that the timer in Javascript is also implemented asynchronously! In addition it would seem that there is no standard sleep function and no easy way of implementing one, which I find a bit odd, but I digress.

There are a few other approaches that I have found. However there is only one that I actually like; just put the init code for the chart in the callback function! Humorously this is what I intended to do from the beginning and what my first example does. I wanted to make a simple example to show off here using a global to store the data. Well it turns out that the simplest way to do that is to do it in the callback.

$.getJSON(getUrl("ibm"), init);

function init(result) {
  var data = result.query.results.row;
  // do something with your data here
  console.log(data);
}

This approach may require rethinking the design of your app a bit, but it’s usually pretty simple. One approach is to simply put all your top-level commands inside a main or init function and use that as your callback function. A discussion of the different approaches to asynchronous code is outside the scope of this article.

That just about concludes this part of the Jumping Into Javascript series. Before ending this, I’ll leave you with the updated code. I have temporarily removed the ability to move items on the canvas, however the chart is now drawn from real data. It still doesn’t look all that great because I used a very naïve drawing algorithm to keep things simple and focused on fetching and handling the data, but you should get the idea.

var width = 600, height = 500
var chart = document.getElementById("chart");
var c = chart.getContext("2d");
chart.width = width; chart.height = height

//chart.onclick = moveRects;

//___________________________________________________________________________//
// Query and Init Procedures
//___________________________________________________________________________//

function getUrl(symbol) {
  return "http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20csv%20where%20url%3D'http%3A%2F%2Fichart.finance.yahoo.com%2Ftable.csv%3Fs%3D" + symbol + "%26d%3D2%26e%3D27%26f%3D2011%26g%3Dd%26a%3D0%26b%3D1%26c%3D2000%26ignore%3D.csv'&format=json&callback=?";
}

$.getJSON(getUrl("ibm"), init);

function init(result) {
  var data = result.query.results.row;
  drawRects(data, 1);
}


//___________________________________________________________________________//
// Chart Drawing Procedures
//___________________________________________________________________________//

function drawRects(d, s) {
  var e = s + 30;
  for (var i = s; i < e; i++) {
    c.fillRect(i*30, d[i].col1, 20, d[i].col1 - d[i].col4);
  }
}

A quick screenshot too…

Screenshot

With retrieving and handling data out of the way, the next article will likely be a short digression into the namespace pattern. After that I will most likely cover a few possible implementations of the chart drawing algorithm and play around with Javascript’s first-class functions.

GOTO Part 5 – Namespaces we should have more of these

Get JamochaTrade on GitHub

Advertisements