Flickr API, Javascript and Windows Desktop

Lately I've came across a very underrated feature of Microsoft Windows, the ability to use an HTML page as a desktop wallpaper. When I've first seen this feature (I think it was in Windows 98 or Windows 95) it seemed very odd and pointless. Those were the times when web pages were seen as a dangerous ground, especially those that contained some form of scripting.

Nowadays things seem a bit different - not sure how much of this has changed from the security point of view, but Javascript can be quite a nice toy when used properly. Web services are becoming more and more popular too and AJAX seems to be like the word of the day.

But let's get back to something more focused, since this is probably a very broad term. Flickr API offers a JSON response format that can be very easily parsed using Javascript. This opens up a wide range of possibilities, but there's also is a very strong security limitation - a Javascript piece of code, when interpreted by a browser, can only make remote calls to services residing on the same domain (I'm talking here about XMLHttpRequests). Normally this would make any web service requests impossible by using only Javascript code.

The guys (and girls) from Flickr provide a very nice workaround - the JSON response is not an object, but actually a call to a local callback function (jsonFlickrApi(response)), that gets the response object as a parameter. In order to exemplify this, I'll provide the code for a nifty little desktop wallpaper that displays the most interesting photos from Flickr (in fact it's only an HTML page together with two JS files).

In order to simplify the code, I'll use the wonderful Prototype JS library.

Here's the main JS file, we will call this flickr-int.js:

var photo_store = new Array();
var photo_store_index = 0;
 
/**
 * Construct the URL for a photo.
 *
 * @param photo a photo object as returned by the JSON Flickr response
 */
function flickr_photo_url(photo) {
    return "http://farm" + photo.farm + ".static.flickr.com/" + photo.server + "/" + photo.id + "_" + photo.secret + ".jpg";
}
 
/**
 * Construct the URL for a photo page (the HTML page corresponding to the photo).
 *
 * @param photo a photo object as returned by the JSON Flickr response
 */
function flickr_photo_page_url(photo) {
    return "http://www.flickr.com/photos/" + photo.owner + "/" + photo.id;
}
 
/**
 * Flickr JSON callback function, called when the Flickr response arrived.
 *
 * @param response the response object as returned by the Flickr API
 */
function jsonFlickrApi(response) {
    var photos = response.photos;
    for (i = 0; i < photos.photo.length; i++) {
	p = {};
	p.url = flickr_photo_url(photos.photo[i]);
	p.page_url = flickr_photo_page_url(photos.photo[i]);
	p.title = photos.photo[i].title;
 
	photo_store[photo_store.length] = p;
    }
}
 
/**
 * Load another image.
 * 
 * @param targetImage the target image (id)
 * @param targetLink the link corresponding for the target image (id)
 * @param statusHolder the status holder (id)
 */
function load_image(targetImage, targetLink, statusHolder) {
    if (photo_store.length > 0) {
	if (photo_store_index < photo_store.length) {
	    var selected_photo = photo_store[photo_store_index++];
	    photo_store = photo_store.without(selected_photo);
 
	    $(targetImage).src = selected_photo.url;
	    $(targetImage).title = selected_photo.title;
	    $(targetImage).style.display = "inline";
 
	    if ($(targetLink)) {
		$(targetLink).href = selected_photo.page_url;
	    }
 
	    if ($(statusHolder)) {
		$(statusHolder).innerHTML = selected_photo.title + " (" + photo_store_index + "/" + photo_store.length + ")";
	    }
	}
	else {
	    photo_store_index = 0;
	}
    }
}

You can see, the method jsonFlickrApi(response) receives a response message, parses it and places the referred images in an array. This method will be called by the Flickr API response, when the response is available. The two functions flickr_photo_url(...) and flickr_photo_page_url(...) are responsible for building the URL of the photo, respectively the URL for the photo page.

Now this where the magic happens (let's call this the wallpaper-js.html file:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
  <head>
    <link rel="stylesheet" type="text/css" href="stylesheet.css" />
    <script type="text/javascript" language="javascript" src="prototype.js"></script>
    <script type="text/javascript" language="javascript" src="flickr-int.js"></script>
    <script type="text/javascript" language="javascript" src="http://api.flickr.com/services/rest/?format=json&method=flickr.interestingness.getList&api_key=YOUR_API_KEY"></script>
    <script type="text/javascript" language="javascript" src="clock.js"></script>
    <script language="javascript">
      var clock = null;
      var remaining_seconds = 0;
 
      function initClock() {
        clock = new Clock('clock-holder');
      }
 
      function imageSwapper() {
        clock.update();
 
	if (remaining_seconds == 0) {
	  load_image("targetImage", "targetLink", "status-holder");
	  remaining_seconds = 60;
	}
 
	remaining_seconds--;
      }
    </script>
  </head>
  <body onload="initClock(); setInterval('imageSwapper()', 1000);">
    <div class="topbar">
      <div class="right-aligned">
	<div id="clock-holder"></div>
	<div id="status-holder"></div>
      </div>
    </div>
    <div class="content">
      <a id="targetLink" href="#" target="_blank"><img id="targetImage" src="#" style="display: none;" alt="No image loaded." /></a>
    </div>
  </body>
</html>

As you can see, the line:

    <script type="text/javascript" language="javascript" src="http://api.flickr.com/services/rest/?format=json&method=flickr.interestingness.getList&api_key=YOUR_API_KEY"></script>

is where the web service call is made. We cannot call this service using a XMLHttpRequest, but we can "include" it in our page. The Javascript body that's actually included will be a call to the jsonFlickrApi(response) method, with the response as parameter. Hence, we can access this value from our JS code.

Please note that in order for this to work, you'll first need to obtain a Flickr API key and replace the YOUR_API_KEY with it. This is free and shouldn't pose any problems, but you'll have to agree to the Flickr Terms and Conditions.

Everything else is just bells and whistles. In order for our example to be complete, I'll provide the two other files required for this to work (you'll also need to get the prototype.js file).

The "clock.js" file:

var Clock = Class.create({
    /**
     * Initialize the clock.
     * 
     * @param target the target element where the clock will be displayed
     */
    initialize: function(target) {
	this.target = $(target);
    },
 
    /**
     * Updates the clock's display.
     */
    update: function() {
	var currentTime = new Date();
 
	this.target.innerHTML = this.pad_digits(currentTime.getHours(), 2) + ":" 
	    + this.pad_digits(currentTime.getMinutes(), 2) + ":"
	    + this.pad_digits(currentTime.getSeconds(), 2);
    },
 
    /**
     * Utility function for padding numbers with leading zeroes.
     * 
     * @param number the number value to be padded
     * @param lenght the desired number of digits
     * @return the padded value ("0" + "0" + ... + number)
     */
    pad_digits: function(number, length) {
	var str_number = number.toString();
	while (str_number.length < length) {
	    str_number = "0" + str_number;
	}
	return str_number;
    }
});

And last, the stylesheet.css:

body {
    background: #000000;
    color: #FEFEFE;
 
    margin: 0;
    padding: 0;
 
    font-family: Verdana;
    font-size: medium;
}
 
a img {
    border: 0;
}
 
.topbar {
    margin-top: 0;
    padding-top: 0;
 
    color: #666666;
}
 
.right-aligned {
    text-align: right;
}
 
#status-holder {
    font-style: italic;
    color: #999999;
}
 
.content {
    margin-top: 2em;
    text-align: center;
}

This should be all. Please let me know if there are any problems with this method - and as always, any feedback is welcome!

Happy coding!