When Jeremy talked about Huffduffer at March's £5app he revealed that pretty much every page in Huffduffer had a RSS/JSON/XML version. A couple of weeks back, I asked whether Huffduffer supported JSONP. After a quick reply back from Jeremy that it wasn't, there was another reply within an hour or two saying Jeremy had added JSONP support. At that point I felt my curiosity had got the better of me and I felt duty bound to a least do something with the newly added JSONP support...
So here I present a very simple embed-able Javascript widget for Huffduffer:
Both the page used to generate the widget HTML and the widget code itself are 100% client-side. There's no server-side code involved at all. For the widget itself this seemed pretty key. Such widgets may be embedded in pages that get a lot of hits, so doing anything beyond serving up a static file seemed best to avoid.
I'd not really written a widget like this before, but planned to create something similar for chrss at some point, so it seemed like a good thing to have a go at. Most of the techniques are based on code from Dr Nic's DIY widgets - How to embed your site on another site article. However I tried to add a few more features/constraints into the mix:
Allow more than one widget with different data on the same page
Don't pollute the global namespace
Don't require special html embedded with the widget (just a single script tag)
The first point involved allowing a static javascript file to server different data. The easy way would be to have some server side code serve up some slightly different Javascript depending on the parameters given, but I decided there's no reason why the Javascript can't do that itself.
When the widget script runs it looks at the last known script element - which should be the element used to load the script itself to parse the parameters. In this case I just want a single parameter. So if the script tag used to embed the widget looks like this:
<script src="/huffduffer/build/huffduffer.js?lilspikey" type="text/javascript"></script>
I can extract everything after the ? thus:
function findURL() { // assume that last script in page is this script var scripts = document.getElementsByTagName('script'); var script = scripts[scripts.length-1]; var scriptURL = script.getAttribute('src'); var m = scriptURL.match('^.*\\?(.*)'); if ( m ) { return m[1]; } return '' }
So that solves the first problem.
The next problem is not polluting the global namespace. First I make sure that everything is wrapped inside an anonymous function that is called immediately. This reduces the scope of the functions declared in there. However we do need at least one global function to act as a callback for the JSONP data. In addition this function must be unique to each widget embedded in the page, so that the correct data gets given to the correct widget.
So first I generate a relatively unique id:
function uuid() { var id = "_"+(new Date()).getTime(); for ( var i = 0; i < 8; i++ ) { id += "0123456789".charAt(Math.floor(Math.random()*10)); } return id; } var id = uuid(); var div_id = 'huffduffer'+id;
Then we use that id as a name for the function and create a callback that embeds the id of the div the widget will appear in via a closure:
function createCallback(div_id) { // return a function with the div_id in a closure return function(data) { // real callback code here } } // create a function with a generated name, to ensure // we get the right div window[id] = createCallback(div_id); document.write("<div id='"+div_id+"'></div>");
This means that we can have multiple widgets on the page all showing different data and the global namespace pollution is limited to one randomly named function per widget.
In addition by creating this unique id we can tie the callback function to the relevant div in the page without needing any more magic. Which means we've also dealt with the third problem.
The "real" widget has been minimized using YUICompressor, but you can see the regular version of the code here:
I've tested everything in most of the major browsers, but can't guaranteed it'll work perfectly everywhere.