Mashing Up the War .:. kentbrewster.com

[Mashing Up the War was an Editor's Pick on gallery.yahoo.com and Mashup of the Day on ProgrammableWeb, on 5Dec2006.]

Over the weekend the San Jose Mercury News had a story about Michael White's icasualties.org. Widely regarded to be the leading authority on the US-led coalition's casualty count, the Iraq Coalition Casualties site is an excellent example of how much good the Internet can do in the hands of a motivated individual. While browsing the site I noticed Mr. White hadn't created a way for interested people to include the current total on their sites, so I sat down and started hacking.

While searching to see if anyone else had built a badge, I ran into two more interesting sites, iraqbodycount.org and costofwar.com, which tabulate civilian deaths and the total cost to U.S. taxpayers since the war began.

While the Iraq Body Count site does have a Web badge, it's not as useful as it could be. It's hard-coded with a group of images that you might or might not want to include on your site, and the numbers themselves come down as images, which don't do blind or mechanical users any good.

The Cost of War site, currently maintained by the National Priorities Project, uses JavaScript to dynamically update the total cost of the war so far. It takes the budget for the war--currently $378 billion--divided by the number of microseconds between 17 April 2003 and the end of the current budget period, 31 March 2007, and multiplies the dollars spent per microsecond by the actual number of microseconds since the start of the war. Problems with this approach include hard-coding the start and stop numbers into the client JavaScript and depending on the client's clock to be correct. (Oh, and I don't think I understand how their algorythm actually works; wouldn't you want to divide the total budget amount by the number of seconds in the budget period, set your counter to the current time, and increment your total from there? When I tried it, I got a much higher number than the costofwar counter, so I went with their method.)

It occurred to me that I could help all three sites spread the word without increasing bandwidth costs to any of them, by creating an API for all three sources of information and using it here. And, like a good little Yahoo! evangelist, I also figured I'd throw in a link to our top stories from Iraq, plus a thumbnail image to keep things interesting. Please feel free to use this on your site if you think it's worthwhile; the lessons in API building that I learned pulling it together were invaluable.

Goals for the Project

  1. Minimum impact on the source sites. This is crucial; since I'm screen-scraping four different Web sites for the information in the badge, it absolutely can't happen every time somebody loads my tiny widget.
  2. Maximum flexibility for the intermediary site. Although it's tempting to use document.write to slam the whole thing into place, I'm leaving it open to anyone's intepretation. Don't like the colors, the image, the title, or any of the numbers I'm using? Fine; leave 'em out. (Please do leave the link back here, and all the links back to the source sites; they deserve the referral traffic.)
  3. Best performance for the user. This ought to go without saying, but it often doesn't. Structure, presentation, and behavior are all separate, and the JavaScript is encapsulated and object-oriented, for maximum security.

The Client

Here's the code for the client. It ought to look familiar to anyone who's taken a look at SpiffY!Search; more about the methods used appears in Build a Search App in Under an Hour. There are two scripts in play, one inline that shows the badge, and one remotely served from iraqStats.js that actually supplies the data.

If you copy and paste this into an HTML file and save it to your desktop, it ought to come right up and run:

<style type="text/css">
div#iraqStats {
  color:#fff;
  border:5px solid black;
  background-color:#600;
  text-align:center;
  font-size:small;
  width:132px;
  padding-bottom:2px;
}
div#iraqStats a {
  color:#ff0;
  text-decoration:none;
}
div#iraqStats a img {
  border: 1px solid #600;
  margin-bottom:2px;
}
div#iraqStats a:hover img {
  border: 1px solid #ff0;
}
div#iraqStats a:hover {
  color:#fff;
  text-decoration:underline;
}
</style>
<div id="iraqStats"></div>
<script type="text/javascript">
var KENTBREWSTER = window.KENTBREWSTER || {};
KENTBREWSTER.iraqStats = function() {
  $ = [];
  $.runInterval = 250;
  return {
    showData : function(obj) {
      $.budgetDollars = obj.budgetDollars;
      $.budgetTime = obj.budgetTime;
      $.budgetSpentPerMicroSecond = $.budgetDollars / $.budgetTime;
      $.startOfWar = new Date(obj.startOfWar);
      $.runTime = new Date();
      if ($.runTime < obj.runTime) { $.runTime = obj.runTime; }
      $.elapsedTime = $.runTime - $.startOfWar;
      document.getElementById('iraqStats').innerHTML = '<a href="http://news.yahoo.com/fc/World/iraq" target="_vu"><img border="0" width="130" src="' + obj.imageUrl + '" title="Full Coverage on Yahoo! News" /></a><br /><strong>Spent So Far In Iraq:</strong><br />Soldiers: <a href="http://icasualties.org/oif/default.aspx" target="_vu" title="Coalition Casualties">' + obj.soldiers + '</a><br />Civilians: <a href="http://www.iraqbodycount.org/" target="_vu" title="Civilian Deaths">' + obj.civilians + '</a><br />$<a href="http://costofwar.com" id="moneySpent" title="U.S. Dollars Spent" target="_vu"></a><br /><a href="' + obj.getThis + '">get this for your site</a>';
      setInterval(this.addMoney, $.runInterval);
    },
    addMoney : function() {
      $.moneySpentSoFar = parseInt($.elapsedTime * $.budgetSpentPerMicroSecond);
      $.elapsedTime += $.runInterval;
      document.getElementById('moneySpent').innerHTML = $.moneySpentSoFar;
    }
  }
}();
</script>
<script src="http://kentbrewster.com/iraqStats.js"></script>

Some Notes

  • Yes, the CSS and JavaScript could be served from linked files, separating structure from presentation from behavior. I strongly recommend you do this, if you're using the badge on more than one page. I won't be doing this here, since I want people to serve up their own code, not mine.
  • I'm using buried returns instead of a more-semantically-correct unordered list. The CSS is much simpler this way.
  • Feel free to change the name of the containing division. You won't be able to change out the root JavaScript object KENTBREWSTER, however; it's hard-coded in the data return. (No, I won't be changing that; it would take a PHP call instead of just serving up some flat JavaScript.)
  • Don't crank $.runInterval too much lower than 250 microseconds; it will interfere with the user's ability to click the number and visit costofwar.com.

The Data

Here's a snapshot of the data, the current version of which may be found and downloaded from iraqStats.js:

KENTBREWSTER.iraqStats.showData({
	"imageUrl" : "http:\/\/d.yimg.com\/a\/p\/afp\/20090414\/capt.photo_1239727700693-1-0.jpg",
	"soldiers" : "4273",
	"civilians" : "99774",
	"budgetDollars" : "456100000000",
	"budgetTime" : "140659200000",
	"startOfWar" : "Apr 17, 2003",
	"runTime" : "1239728404000",
	"getThis" : "http://kentbrewster.com/mashing-up-the-war"
});

The data is returned in JSON format, wrapped in the showData function call. It's pretty easy to understand; there aren't any dynamic script tags being created, just one when the page first loads.

All information except the last two items come from external sites. I'm supplying runTime from this server; if the client's clock is set to anything less than this, we start counting from the server's run time and not the client's local time, which is not correct. (Yes, this is a very US- and West Coast-centric way of handling things, but it works.)

The Screen Scraper

For the curious, here's part of the screen scraper. Please note that you do not need to run this to make the mash-up work; it's only here for educational purposes. You'll want to create your output file in the proper directory and set its file permissions to read and write before trying this the first time, or you will probably get an access error.

I'm not going to publish the methods I used on the non-Yahoo! sites; you should be pulling down their numbers from here instead of scraping them yourself.

<?php
# these values are hard-coded for this example only:
$soldiers = 3130;
$civilians = 54191;
$budgetTime = 124761000000
$budgetDollars = 378000000000;
$imageUrl = "";
$url = "http://rss.news.yahoo.com/rss/iraq";
if ($data = @file_get_contents($url)) {
  $foo = split("media:content url=\"", $data);
  $bar = split("\"", $foo[1]);
  $imageUrl = $bar[0];
}
if ($soldiers && $civilians && $imageUrl && $budgetTime && $budgetDollars) {
  $output = "KENTBREWSTER.iraqStats.showData({\n\t";
  $output .= "\"imageUrl\" : \"$imageUrl\",\n\t";
  $output .= "\"soldiers\" : \"$soldiers\",\n\t";
  $output .= "\"civilians\" : \"$civilians\",\n\t";
  $output .= "\"budgetDollars\" : \"$budgetDollars\",\n\t";
  $output .= "\"budgetTime\" : \"$budgetTime\",\n\t";
  $output .= "\"startOfWar\" : \"$startOfWar\",\n\t";
  $output .= "\"runTime\" : \"" . time() . "000\",\n\t";
  $output .= "\"getThis\" : \"http://kentbrewster.com/mashing-up-the-war\"\n";
  $output .= "});";
  $filename = "iraqStats.js";
  if ($handle = fopen($filename, 'w')) {
    fwrite($handle, $output);
    fclose($handle);
  }
}
?>

If during the scraping run we hit any problems, the program quits and leaves the most-recently acquired data online. (Yes, we could load the current version, break it down into pieces, and only update the sources that came through; I'll leave that as an exercise for the student.)

At some point in the future this is guaranteed to fail ... one can only hope it's sooner rather than later, and is because our troops have all come home and no more bystanders are dying.

Comments from before Disqus:

Kent Brewster .:. 2010-08-31 22:03:55
Right, I know, the image is broken. Looking forward to shutting this one down!
Eon G. Cooper .:. 2010-07-13 12:43:33
Would love to post the Spent So far in Iraq counter on my Facebook page. Can you make an app for that if there is not one already?