Why Picasa?

  1. Picasa is the most Free
  2. Picasa is Google (Flickr is Yahoo, and we all know "Yahoo == Old Fashioned")
  3. Flickr hates businesses.
  4. Picasa acts like a desktop photo viewer, like iPhoto (so normal people can use it).

There is also Imgur, and I will probably switch over to that when I get the chance.

The Picasa API

Here is the HTTP API for Picasa. There's a lot to it, but all we need to do is list albums and list photos. And we want to do this using Google's experimental Data API feature of course, the Partial Response. The Partial Response allows us to limit the information the feed returns to us, which means significant performance improvement. For example, if all we want are the image urls, we don't need thumbs, titles, descriptions, and the author. We can cut out 80% of the feed's size. That's awesome. Still waiting for Google Docs to have that.

The requests we will make to Picasa are here:

# album
http://picasaweb.google.com/data/feed/api/user/userID
# images for an album
http://picasaweb.google.com/data/feed/api/user/userID/albumid/albumID

... where

We also want to specify specific query parameters, so the final API call for a list of images looks like this:

http://picasaweb.google.com/data/feed/base/user/114736267714313794052/albumid/5508006810704621345?alt=json&kind=photo&hl=en_US&fields=entry(title,gphoto:numphotos,media:group(media:thumbnail))

How to Stream Picasa Photos with jQuery

The goal is to stream photos without ever having to touch your server. Aka, no PHP or Ruby proxy. Reason being, what if your pages are entirely static (Github Pages!), and you want it to work the same as if they were dynamic? The code should be all Javascript.

Sidenote: Brief Anatomy of a jQuery Plugin

Here's the basic structure of a jQuery Plugin:

(function($) {
$.picasa = {

}
$.fn.picasa = function() {

}
})(jQuery);

The wrapper function is actually pretty interesting so I thought I'd describe it real quickly in case you weren't familiar. Everything in the plugin is wrapped in a javascript closure, and I am passing the jQuery object into the closure as an argument, like this:

( function($) { console.log($) } )(jQuery);

The reason you do this is to create a scope for your plugin's methods and variables. They'll ask you about that in interviews :). Javascript closures.

The final part of the plugin is to create basically a class level and instance level method or object that serves as the global access point:

$.picasa // global method
$.fn.picasa // instance method

I usually make the $.picasa an object with functions, and $.fn.picasa a function which calls methods on $.picasa.

The jQuery Picasa Plugin Source Code and Example

Here's what I've come up with:

(function($) {
$.picasa = {
albums: function(user, callback) {
var url = "http://picasaweb.google.com/data/feed/base/user/:user_id?alt=json&kind=album&hl=en_US&access=visible&fields=entry(id,media:group(media:content,media:description,media:keywords,media:title))"
url = url.replace(/:user_id/, user);
$.getJSON(url, function(data) {
var album = null;
var albums = [];
$.each(data.feed.entry, function(i, element) {
album = {
id: element.id["$t"].split("?")[0].split("albumid/")[1],
title: element["media$group"]["media$title"]["$t"],
description: element["media$group"]["media$description"]["$t"],
thumb: element["media$group"]["media$content"][0]["url"],
}
album.images = function(callback) {
$.picasa.images(user, album.id, callback);
}
albums.push(album);
});
callback(albums);
});
},

images: function(user, album, callback) {
var url = "http://picasaweb.google.com/data/feed/base/user/:user_id/albumid/:album_id?alt=json&kind=photo&hl=en_US&fields=entry(title,gphoto:numphotos,media:group(media:content,media:thumbnail))";
url = url.replace(/:user_id/, user).replace(/:album_id/, album);
var image = null;
var images = [];
$.getJSON(url, function(data) {
$.each(data.feed.entry, function(i, element) {
image = element["media$group"]["media$content"][0];
image.title = element.title["$t"];
image.thumbs = [];
$.each(element["media$group"]["media$thumbnail"], function(j, j_element) {
image.thumbs.push(j_element);
});
images.push(image);
});
callback(images);
});
}
};

$.fn.picasaAlbums = function(user, callback) {
$.picasa.albums(user, function(images) {
if (callback) {
callback(images);
}
});
};

$.fn.picasaGallery = function(user, album, callback) {
var scope = $(this);
$.picasa.images(user, album, function(images) {
if (callback) {
callback.apply(scope, images);
} else {
var picasaAlbum = "<ul class='picasa-album'>\n";
$.each(images, function(i, element) {
picasaAlbum += " <li class='picasa-image'>\n";
picasaAlbum += " <a class='picasa-image-large' href='" + element.url + "'>\n";
picasaAlbum += " <img class='picasa-image-thumb' src='" + element.thumbs[1].url + "'/>\n";
picasaAlbum += " </a>\n";
picasaAlbum += " </li>\n";
});
picasaAlbum += "</ul>";
scope.append(picasaAlbum);
}
});
}
})(jQuery);

And you can use it like this:

// adds an unordered list of the gallery images, ready for prettyPhoto
$("#picasa-gallery").picasaGallery("114736267714313794052", "5508004555121989537");

// adds a gallery of album covers
$("#picasa-albums").picasaAlbums("114736267714313794052");

// use the raw image data
$.picasa.images(user, album, function(images) {
var picasaAlbum = "<ul class='picasa-album'>\n";
$.each(images, function(i, element) {
picasaAlbum += " <li class='picasa-image'>\n";
picasaAlbum += " <a class='picasa-image-large' href='" + element.url + "'>\n";
picasaAlbum += " <img class='picasa-image-thumb' src='" + element.thumbs[1].url + "'/>\n";
picasaAlbum += " </a>\n";
picasaAlbum += " </li>\n";
});
picasaAlbum += "</ul>";
$("body").prepend(picasaAlbum);
});

// use the raw album data
$.picasa.albums("114736267714313794052", function(albums) {
$.each(albums, function(index, album) {
album.images(function(images) {
# ...
});
});
});

Cheers. Try it out, let me know what you think.