Frogspotter v4 and GGD Afternoon Hack

15 November 2009, 03:41

Today was the second GGD Afternoon Hack. We had six women show up for some Saturday arvo social hacking, with projects ranging from learning Rails, exploring Zend, trying out some Golog style Prolog…and I was working on my JavaScript for frogspotter, following on from the MelHack event last week.

Although frogspotter doesn’t look too different, I managed to get the check boxes and cluster markers working correctly, and do some nice Flickr images (see below). And now you don’t need to zoom in too far before it switches to individual markers.

One problem came from the fact that the library actually had a bug! I was starting to think I was going mad, so I was happy to find it actually was a problem with the library and not with me. The bug report says “fixed in dev” so I tried to find the dev branch so I could reference that file instead of 1.0. I was using http://gmaps-utility-library.googlecode.com/svn/trunk/markerclusterer/1.0/src/markerclusterer.js and from poking around here I just could not find the development code. After some more searching and clicking I realised they had moved their project and the development was now at http://gmaps-utility-library-dev.googlecode.com/svn/trunk/markerclusterer/src/markerclusterer.js. Rather subtle.

Getting the Flickr images in the pop-ups was really a trial. Note: my approach here was purely naive, to see if I could make it work. I will likely rewrite it so that Python gets and stores the images, to save 30-odd calls to the Flickr API on every page load (!). Doing that will also be far simpler than that JavaScript approach below, so I only put this as a cautionary tale…!

Pop-ups themselves are simple:

var html = '<b>' + spotting.name + '</b><br/><a href="' + url + '">' + spotting.specy + "</a><br/>" ;
marker.openInfoWindowHtml(html);

Querying Flickr was quite easy, once I decided to start using jQuery.

   $.getJSON(
		"http://api.flickr.com/services/rest/?jsoncallback=?",
		{ method : "flickr.photos.search",
			api_key : flickr_key,
			format : "json",
			text : specy,
			sort : "relevance",
			per_page: 1
			},
		callbackwrapper(specy, id)
    );

(“Specy” is my backformed singular of “species”.) So as you can see I just get the first most relevant image from Flickr by giving it the species name to search on. The “$” means the results get stored in some magical jQuery place. NO REALLY.

The problem is…the HTML in the pop-ups is not, AFAIK, a normal part of the page that jQuery might know about. And jQuery really likes to hang off existing stuff in the page rather than just return a string of HTML. So there didn’t seem to be any way I could get a plain old return value back from that json query.

After a bit of thought, I decided that I would put the image HTML in some hidden divs on the page, which the pop-ups could then display. That was it was easy for jQuery to store and also accessible for the pop-up.

So now I have this:

function getFlickrImage(specy) {
    var id = specyToID(specy);
    var html = '<div id="' + id + '"></div>';  //holder for flickr image;
    $("#speciesimages").append(html);
    $("#" + id).hide();
    $.getJSON(
		"http://api.flickr.com/services/rest/?jsoncallback=?",
		{ method : "flickr.photos.search",
			api_key :  flickr_key,
			format : "json",
			text : specy,
			sort : "relevance",
			per_page: 1
			},
		callbackwrapper(specy, id)
    );
}

$(”#foo”) is a cool jQuery short-hand for “find the element with the id foo.” So in my HTML page, I simply added <div id="speciesimages"></div> and with this call, I put one div per species inside it.

specyToID is also very simple:

function specyToID(specy) {
    return specy.replace(" ","-").replace(".","");
}

I spent waaaaaay too long trying to figure out why nothing was happening, not realising that spaces and full stops are not valid in CSS identifiers. Bah! JavaScript accepts whatever you throw at it, why not CSS?! ;)

Now the only mystery left is callbackwrapper(specy, id).

function callbackwrapper(specy, id) {
 return function( data, status) {
    var small_url = "";
    var photo_url = "";
    $.each(data.photos.photo, function(photoIdx, photo) {
	    // Build the thumbnail url
	    small_url = ["http://farm", photo.farm, ".static.flickr.com/", 
			photo.server, "/", photo.id, "_", photo.secret, "_s.jpg"].join("");
	    // Build the photo url
	    photo_url = ["http://www.flickr.com/photos/", 
			photo.owner, "/", photo.id].join("");
	});
    imagehtml = '<a href="' + photo_url + '" target="_blank"><img src="' + small_url + '" width=75 height=75 border=0 /></a>'; 
    $('#' + id).append(imagehtml);
  }
}

(Hm, and I can see now that I don’t even need to pass in specy!)

The each bit above is pretty hacky, because I know I’m only going to get one result. jQuery also really likes those “each” pseudo loop constructions… it was easier to use that than figure out a more accurate syntax.

getJSON is one of jQuery’s AJAX functions. It was a hard lesson for me (and lots of other newbies, from the looks of Google) to remember that the first A in AJAX is asynchronous. So when you call getJSON, you actually call it with three bits: a URL, a set of parameters and values for the URL, and a “callback” function. It seems the callback function will be executed at some time of jQuery’s choosing (hence asynchronous).

The main difficulty with the callback function is that it’s not very straightforward to pass extra data items to it. See the “data” and “status” arguments? I don’t really know how JavaScript or jQuery knows what their values are, but somehow it does. data is what comes back from the getJSON query. But how do I find what my original search term was when it comes time for this function to be executed? Because in my case I want to add a div with an identifier for that.

So there’s this wrapper thing. All I really know is, doing it this way WORKS. Don’t touch the second line. You will regret it.

It seems so simple in the retelling. But figuring this much out was painful. :) You can read all the JavaScript together on Launchpad.

tags: , ,

---

Comment

Subscribe to comments on this post: rss / atom

Commenting is closed for this article.