Last Thursday, I attended a Wookie Widgets training day at the Oxford University Computing Service; Wookie is an Apache (incubating) project that is making huge inroads as an implementation of the W3C widget specification. This post was written on the boat home, away from the net, so I’ve not fact-checked any claims I’ve made about Wookie, which means you need to be suspicious about anything I do say. If Scott or Ross want to put me right, please do so! (Then again, these impressions may prove useful in tweaking intro material in order to pre-empt similar misunderstandings in the future;-)
It’s been several years since I last played with widgets (dabblings with Yahoo Konfabulator widgets, as they then there, Widgetbox and a few attempts at reclaiming the VLE, and so on), so what’s changed?
As with many previous widget implementations, the Wookie platfrom essentially provides a W3C widgets compliant packaging format around bundle of zipped up HTML, Javascript and CSS files that implement an app typically designed to fit in a sidebar or fill the screen of a mobile device . The language is possibly evolving here, because of lot of what we might now call apps, when implemented using open web technologies look a lot like the sort of things that can also be packaged using widget standards. Maybe the apps vs widgets divide is along the lines of: widgets are designed as a part of some wider context (a sidebar widget in a learning environment, for example, or panel on an iGoogle personal page of Netvibes dashboard); widgets are largely passive display devices, or single function micro-applications. Whereas apps are either really interactive, or multifunction (like mini-websites). Or maybe there is no distinction?
Anyway, in its current state, the Wookie server can be used to serve up the widget code into an iframe container which can then be embedded wherever. In the simplest case, what it provides with with is a server to which you can upload neatly packaged web apps, and then serve them into an iframe. And that’s pretty much as far as I got with it on the day, as the demos below will illustrate.
There are a few of other features that are potentially a bit more exciting though: features, state handling, social apps (err, widgets ;-) and integration into other environments.
First up, are features (cf. WordPress shortcodes?) These might trivially be used to provide a shortcut way of importing standard API helper libraries – such as the helper libraries imported when you want to use things like Google maps, I guess – into the widget context. These are included in the widget using a construction along the lines of:
<feature name="http://wave.google.com" required="true"/>
I’m not sure what the list of currently supported features are, or how to build the Wookie server side fulfilment services that respond to the feature request, but helpers to load in things like mapstraction, jquery, jqtouch, etc, could be handy? My possible misunderstanding about how features are handled also leaves me with the impression that the server might be able to do browser detection and serve up appropriate libraries to the widget based on browser type, or platform, for example. I’m not sure whether mutliple widgets served to the same client can also benefit from sharing downloaded libraries (I guess browser security policies mitigate against that, but maybe there are ways for them to cache and reuse multiple instances of the same library downloaded just the once?)
Secondly, there’s a mechanism for handling state, and sending updates to the server from the browser (I think?). Thirdly, Google’s Wave api has been implemented, which means that widgets can call on whatever features that API offers (I know nothing…) And fourthly, integration of the standalone Wookie server has been acheived with Moodle, elgg, and a variety of other learning environments. What this integration offers is the chance for widgets to access lists of users (id, name and avatar, I think?) from a particular environment context. I’ve no idea how that works… one more thing for the “to find out more about” list!
So what did I do?
Three things: a demo of pulling the JSON feed out of a Yahoo pipe into the widget via a javascript script load and rendering the result in the widget; a demo of pulling the RSS feed out of a Yahoo pipe into the widget using AJAX/XMLhttprequest via a Wookie server proxy, and rendering the XML result in the widget; and a demo of embedding a Google map that displays points loaded in from a KML file into the widget by generating a Google map embed code, rather than having to use the Google Maps API.
Simple Load and Render of a JSON feed
The widget was generated from the Hello World widget simply by changing the index.html file as follows:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="pragma" content="no-cache"/> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>feedtest</title> <script type="text/javascript"> //call parseJSON when the JSON data from the pipe has loaded function parseJSON(json_data){ var d= document.getElementById('ulContent'); var di = json_data.value.items; for (var i=0; i< di.length; i++) { var li = document.createElement('li'); var u=di[i].author.name; var re=/(.*) \((.*)\)/ ; //pull out twitter ID and name of person who tweeted the link u=u.replace(re,"<a href='http://twitter.com/$1'>$1</a>") li.innerHTML="<a href='"+di[i].link+"'>"+di[i].title+"</a> (from "+u+")"; d.appendChild(li); //update widget HTML dynamically } } </script> </head> <body> <h1>Shared links</h1> <ul id="ulContent"></ul> <!-- load JSON output of a pipe via a script include, wrapped in callback function -> <script type='text/javascript' src='http://pipes.yahoo.com/pipes/pipe.run?_id=b126237fdff27df4857c2614ee5ef5ef&_render=json&u=psychemedia&_callback=parseJSON'></script> </body> </html>
Download the widget .wgt package here
The widget operates by loading a JSON object (wrapped by a callback function, parseJSON), that is emitted from a Yahoo pipe, via a script tag, and then running the callback function once the object has loaded. The callback function parses the JSON object, runs through each feed item, and dynamically adds content to the page based on the contents of the feed. The Yahoo pipe does two things: firstly, it provides functionality; and secondly, it emits content as JSON.
The pipe’s functionality is to take a twitter user name, search twitter for tweets to: that person that contain links, and grab the title of the page that the links point to. The widget can then display a list of links tweeted to a user along with the page names, and the identity of the person who tweeted the link to the user.
If used in a social context, what this demo does is identify a need to be able to pass some form of identity token to the widget so that tweets to that user can be displayed. The script src URL, which passes the Twitter ID to the Yahoo pipe, can then be constructed and added to the page dynamically.
Simple Load and Render an RSS Feed via the Wookie server proxy
The second example gets round the need to create a JSON object from an RSS feed by simply pulling in content from an RSS feed via a proxy on the Wookie server.
Using the same display target element in the widget as before, this example demonstrates how to call the proxy and how to parse (crudely!) the resulting XML from the RSS feed.
function parseXMLData(xmlobject){ var root = xmlobject.getElementsByTagName('rss')[0]; var channel = root.getElementsByTagName('channel')[0]; var items=channel.getElementsByTagName("item"); var d= document.getElementById('ulContent'); for (var i=0;i< items.length; i++){ var item = channel.getElementsByTagName("item")[i]; var li = document.createElement('li'); var title = item.getElementsByTagName("title")[0].firstChild.nodeValue; li.innerHTML="<a href='"+item.getElementsByTagName("link")[0].firstChild.nodeValue+"'>"+title+"</a>"; d.appendChild(li); } } //* var twid="psychemedia|"; //twitter id of person to whom links are @d // create the URI for the RSS feed of the pipe returning links tweeted to:@twid var loc = "http://pipes.yahoo.com/pipes/pipe.run?_id=b126237fdff27df4857c2614ee5ef5ef&_render=rss&u="+twid; loc = Widget.proxify(loc); //we want the pipes RSS feed via the Wookie serverside proxy var xml_request = new XMLHttpRequest(); //this XHR stuff is standard; <feature/> it? xml_request.open("GET", loc, true); xml_request.onreadystatechange = function() { if(xml_request.readyState == 4 && xml_request.status == 200){ //if we manage to get the feed via the proxy, call the parser function parseXMLData(xml_request.responseXML); } } xml_request.setRequestHeader("Cache-Control", "no-cache"); xml_request.send(null); //*/
Download the widget .wgt package here
In order for the proxy to work correctly, the domain for the original URI needs to be whitelisted on the Wookie server via the Wookie Admin panel:
Adding http://pipes.yahoo.com/pipes/ to the whitelist means that the proxy should work with all feeds coming out of Yahoo Pipes.
Using Location Services and Generating a Google Maps Embed Code
This demo uses browser based location services, which may be enabled on browsers offering location services, either natively or via Google Gears. The demo calls on a third party library provided as tutorial code by Ross Gardler (scripts/geo.js in the .wgt package).
<script src="scripts/geo.js"></script> <iframe id='testmap' width="425" height="350" frameborder="0" scrolling="no" marginheight="0" marginwidth="0" ></iframe> <script type="text/javascript"> if(geo_position_js.init()){ //geo_position_js is defined in the geo.js script geo_position_js.getCurrentPosition(success_callback,error_callback,{enableHighAccuracy:true}); } else{ document.getElementById("message").innerHTML = "Geo-Location functionality not available"; } function success_callback(p) { //we've got the geo position so do something with it... // ...like: // 1) generate a google maps embed code // 2) around a KML feed from a Yahoo pipe // 3) that is keyed using lat/long data var locator="http://maps.google.com/maps?f=q&source=s_q&hl=en&geocode="; locator+="&q=http:%2F%2Fpipes.yahoo.com%2Fpipes%2Fpipe.run%3F_id%3D3a10c97a560ca1e4a418fdf939c74379"; locator+="%26_render%3Dkml%26distance%3D3"; locator+="%26lat%3D"+p.coords.latitude.toFixed(2); locator+="%26lon%3D"+p.coords.longitude.toFixed(2) locator+="&output=embed"; //the above is a hack for blog display; //It would be tidier to build pipes URI and then escape it... var ifl= document.getElementById('testmap'); ifl.src = locator; //load the embeddable Google map into an iframe defined within the widget } function error_callback(p) {} </script>
Download the widget .wgt package here
This demo obtains the current location data (latitude and longitude co-ordinates) from the browser and then generates a Google Maps embed code built around a KML file generated from a Yahoo Pipe. The pipe accepts latitude and longitude points as arguments and looks up the location of nearby traffic monitoring points from the data.gov.uk transport datastore. The original map embed code was generated by grabbing the KML output URL from the pipe, sticking it in a Google Maps search box, hitting search and then grabbing the resulting Google Maps embed code. (I may also have had to tidy up some of the escaping????). The widget code takes the current lat/long, generates the embeddable map URI and dynamically loads it into an iframe embedded in the widget itself.
(Hmm… the height/width of the iframe should maybe have been set at 100%? I also wonder if “value add” features of the Widget are that if the widget is viewed in a mobile device that supports switching between portrait and landscape views, will the widget handle things like resizing, or at least raise an event that can be handled by a specified function (I seem to remember there was some sort of event raising? It also occurs to me that getting things like the height/width properties of the widget might be handy? Are introspective widget properties like Widget.size.height available, I wonder?)
The take home message here is that if you can generate a KML file, you can embed a map in a widget without requiring a Google Maps API key, or having to get involved with coding against the Google Maps API.
And Finally…
Err, that’s it…
Hi Tony
I had hoped to go to that day, but couldn’t make it so could to get your impressions of the day.
If you’re interested we’re run an event on 4 March in Birmingham where we’re going to be looking at wookie and some other ways of integrating widgets and other “stuff” into learning environments. More info @ http://wiki.cetis.ac.uk/DleMarch2010
Sheila