Google Gadget API for Ajax Developers

I just made my first Google Gadget – a Digg roundup tool. Here are some quick notes on making a Google Gadget for Ajax developers. I assume you know Ajax and also that you’ve played around with iGoogle as a user.

What’s a Gadget?

  • The gadget is an XML file sitting on your server. In my case, http://ajaxify.com/run/widgets/google/diggroundup.xml. It will get cached, so effectively it must be a static file.
  • The user adds your gadget to their igoogle portal, or codes it into their own website, by specifying this URL (it may be done indirectly – via the gadget registry. You’ll appear in the registry if you’ve submitted your gadget to igoogle.)
  • The gadget is rendered as an iframe, so you have all the usual security constraints which stop you borking the portal and other gadgets. This also means you can’t communicate with other Gadgets other than via than remote calls to a common third-party server (or has anyone tried hooking them together using the iframe-fragment identifier hack? ).

The Gadget Content

  • The XML file contains three main sections: module settings, user preferences, and the iframe content.
  • Module settings (<ModulePrefs>) are the usual demographic stuff – module name, your email, blood type, etc. Mostly optional.
  • User preferences (<UserPref>) are things users can edit by hitting the Edit button. Note that these aren’t dynamic app variables like a user’s search query – they are longer-term preferences like background colour and number of results to show. Each time the user changes then, the entire gadget is reloaded, so you can assume in initialisation that the preferences have already been established, and you don’t need to bother checking if they’re changed. The nice thing about preferences is they’re declarative, so you don’t have to manage input and persistence. You just say “I need a number called resultAmount from the user” (<UserPref name="resultAmount" display_name="No. of Results" default_value="5" datatype="string" ></UserPref>). Later on, you’ll be able to interrogate a prefs object to get back the data.
  • The content. The content is pretty much the “body” part of an IFrame. ie. your html and … you’ll never expect this … the javascript. You probably expected the JS to be in a separate section of the XML file, but you simply include it using an inline script tag (<script tag="text/javascript">the script...</script>, wherever you fancy. The same goes for CSS – just include a <style type="text/css"> section at the start of your script or inline it. So much for unobtrusive Javascript/CSS, but you can and should still separate things out within your page content.

User Prefs example

This is what the user prefs looks like:

It comes from this definition:

  1. <UserPref name="bgColor" display_name="Background Color:" default_value="White" datatype="enum" >
  2.         <EnumValue value="White" />
  3.         <EnumValue value="Pink" />
  4.         <EnumValue value="Aqua" />
  5.         <EnumValue value="Lime" />
  6.         <EnumValue value="Yellow" />·
  7.         <EnumValue value="Orange" />
  8.         <EnumValue value="Black" />
  9.     </UserPref>
  10.    <UserPref name="container" display_name="Story Type:" default_value="" datatype="enum" >
  11.         <EnumValue value="" display_value="All" />
  12.         <EnumValue value="technology" display_value="Technology" />
  13.         <EnumValue value="science" display_value="Science" />
  14.         <EnumValue value="world_business" display_value="World Business" />
  15.         <EnumValue value="sports" display_value="Sports" />
  16.         <EnumValue value="entertainment" display_value="Entertainment" />·
  17.         <EnumValue value="gaming" display_value="Gaming" />
  18.     </UserPref>
  19.    <UserPref name="storyAmount" display_name="No. of Stories:" default_value="5" datatype="string" >
  20.    </UserPref>
  21.    <UserPref name="popularOnly" display_name="Popular Only?" default_value="true" datatype="bool" >
  22.    </UserPref>

and is accessed like this:

javascript
< view plain text >
  1. var prefs = new _IG_Prefs(__MODULE_ID__);
  2. message.style.backgroundColor = prefs.getString("bgColor");

Remote Content

  • Your gadget probably takes remote content. Remember it’s a static XML file, so you can’t output such content on the fly – you have to use some kind of web remoting. Because of cross-domain restrictions, you will want to use a Cross-Domain Proxy. (I experimented with direct On-Demand Javascript, but no cigar.) Fortunately, Google provides a nice API for proxying, which also caches the content you’re grabbing. The main call is _IG_FetchContent(url, callbackFunc). The callback function is given the contents of URL. As mentioned in my previous post, this might cause some problems if you need to refresh the cache more than once an hour.

Development Tips

  • igoogle can give some pretty poor error messages when you’re just starting up. If there’s nothing at the URL you specify, you will get a weird XML parsing error rather than a good old 404 warning. I also found if you have certain errors such as a typo in your XML file, igoogle says the gadget doesn’t exist at that URL, even though in this case, it does! For those reasons, be sure to start with Google’s Hello World and work from there!
  • Develop on your server directly. (If the gadget is live, you might make a test copy of it for debugging.) You don’t want to be developing on your local box and continuously uploading/checking it in, because igoogle needs to fetch it from a URL each time you want to test it.
  • The secret sauce is this: The Developer Gadget. This is a special gadget provided by igoogle that, if you include it on your portal, will let you suppress caching of your app, which you absolutely have to do. It also makes it a bit easier to add a new gadget and view the gadget source code.
  • Speaking of gadget source code, you can view the entire gadget XML for any gadget you come across, so take advantage of that and avoid reinventing the wheel.

The Developer Gadget looks like this:

Digg API – Can’t Bust the Cache

It’s often a requirement for an Ajax app to “bust the cache”, i.e. call a service and ensure its response comes direct and not from a cache. For all the talk of fancy header techniques, the easiest way to do it is by appending an arbitrary parameter, typically a random number and/or a timestamp i.e. call service.com/?random=39583483. I use this pattern not just as an Ajax programmer but also as a user; when I suspect my wiki page or blog is cached, I just add an arbitrary parameter and usually get the most recent result.

With Digg API, that’s impossible. I just tried it and discovered you get an unrecognised argument error if you pass in a parameter they weren’t expecting. This is well-intentioned as it will give early failure advice to callers who mistype or misinterpret parameter names. But unfortunately, it has the major downside of breaking this convention and thus breaking compatibility with a large number of toolkits that exploit this pattern.

I discovered this as I was writing a Google Gadget to show Digg stories and when you fetch content with the Google API _IG_FetchContent("http://digg.com/....", callbackFunc, { refreshInterval: 60 }) – the refresh option apparently leads to Google tacking on an arbitrary parameter called “cache” (it’s impossible to be sure as it’s proxied via Google, but the argument to that proxy is the Digg URL with “cache=” on the end). Net effect: I can’t update Digg stories any more than once per hour. The only way I could do it would be to write my own proxy and cache. A lot more work than the one-liner _IG_FetchContent call! Yeah, like that’s gonna happen.

For the Digg API, perhaps they need to introduce an optional “strict” parameter like a compiler offers to give extra debug info. If it’s off, then let anything through.

Finger-Friendly Interfaces

All the iPhone hype makes me wonder why so many smartphone/PDA apps still assume you’re using a stylus, when you probably lost it last month, left it at home, don’t have a hand free, or can’t be bothered pulling it out.

I, for one, welcome finger-friendly mobile UIs. Fat buttons please!

Ruby/Rails: Overriding NilClass

Rails uses “whiny nil”, which means if you call a method on an object that happens to be nil (null), you get an exception. This is good. But with strings in a web app (in any language), you often don’t know if an empty string will be nil or simply zero-length (“”). That’s because some code will see a form submitted with a whitespace string and make it nil, while others will make it “”. These might be my libraries or the web framework’s libraries or plugin libraries. This leads to ugly code like this:

  1. @message = "Oi! Enter your name fella" if name or name.empty?

When we really just want

  1. @message = "Oi! Enter your name fella" if name.empty?

So I derive great pleasure from overriding NilClass like so:

  1. class NiClass
  2.   def empty?
  3.     true
  4.   end
  5. end

If HTML Used Convention Over Configuration …

Browsers would automatically pull in CSS and JS according to the filename and I would no longer have to look for an example every time I need a link or script tag.

In the absence of any other spec, /abc/def.html would cause the browser to look for /abc/def.css and /abc/site.css and /site.css. And then it would look for the same, but in JS – /abc/def.js, /abc/site.js, and /site.js. If I had all the time in the world, I’d make an Apache filter to do just that.

This would slow things down a little, but have you noticed – the world is becoming faster? It’s an another example of abundance thinking. Also, if it was a standard, it would not really slow things down as browsers and servers would develop protocols for speeding things up.

So I created a little Ajax web app template to put all the skeleton Ajax code on one page.

HTML

<html>
  <head>
     <title>Hello World!</title>

 &lt;script type=&quot;text/javascript&quot; src=&quot;app.js&quot;&gt;&lt;/script&gt;
 &lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;app.css&quot;/&gt;

</head> <body> <h1>Hello World!</h1> <input id="search" name="search" />

</body> </html>

CSS

body { background-color: white; }
div.error { font-color: red; }

search { width: 200px; }

Javascript

function $(id) { return document.getElementById(id); }

window.onload = function() { $("search").onclick = function() { alert("The search begins!"); } }