Tutorial: Opensocial Data Pipelining and Templates in the iLike Profile view on Orkut
Early this year, the Orkut team became aware that "a small subset of OpenSocial applications [were] being used to spread phishing attacks to Orkut users."
To solve this, the Orkut team phased out support for Flash and Javascript in profile views and required that app developers use Opensocial data pipelining and templates to specify profile markup.
Data pipelining is declarative way to specify a set of REST calls to fetch the JSON data for a page, these include both OpenSocial and arbitrary REST calls. Templates are an HTML markup language (akin to JSTL or RHTML) that is interpreted on the Opensocial servers to generate the page markup. It includes simple expressions, conditional, and looping constructs, bound against the JSON data from data pipelining.
Orkut is currently the only container that has implemented these features, which are are expected to be part of the Opensocial 0.9 spec.
The specs for these features are still evolving and some of the online tutorials and wiki entries are inconsistent and no longer work. I thought it might be helpful to document an working example (as of August 2009) based on the iLike profile view.
The first step is to require the opensocial-data and opensocial-templates features in the ModulePrefs.
-
<Module>
-
<ModulePrefs>
-
<Require feature="opensocial-data"/>
-
<Require feature="opensocial-templates">
-
<Param name="process-on-server">true</Param>
-
</Require>
Next we declare the profile content...
-
<Content type="html" view="profile">
...and specify the JSON requests required by the profile view using the os:HttpRequest declaration:
-
<script type="text/os-data" xmlns:os="http://ns.opensocial.org/2008/markup">
-
<os:HttpRequest key="cache" authz="signed" href="http://philbo.dev.ilike.com/gadget/ilike_async_get_cache_key" format="json" signViewer="false" params="orkut_profile=true"/>
-
<os:HttpRequest key="profile" authz="signed" href="http://philbo.dev.ilike.com/gadget/profile_tracks_json" params="synd=orkut&key=${cache.content.key}" format="json" signViewer="false"/>
-
</script>
The ilike_async_get_cache_key request returns JSON like this: {"key": 139222}.
The profile_tracks_json request returns JSON like this:
{"tracks":[{,"artist_name":"Bouncing Souls","name":"The Pizza Song"}, {"artist_name":"The Aquabats","name":"Pizza Day"}],"track_count":2}}.
Note how the second request includes query string parameters (${cache.content.key}) derived from the JSON data in the first request. In this case, the profile_tracks_json is set to have a very long cache lifetime; the key is a profile timestamp use to force a fresh version of the profile to be fetched when the profile changes. (The timestamp is maintained in memached and can be returned very quickly by the server.)
It is poorly documented that Orkut's os:HttpRequest wraps this content in a "content" hash wrapper, which is why we have to say "cache.content.key".
Now that we have the data, we declare the template that binds to the json data to generate the markup. Note the use of "if" and "repeat" attributes for conditionals and loops and the "${}" expressions; also note the required use of osx:NavigateToApp to link to canvas pages.
-
<script type="text/os-template" xmlns:os="http://ns.opensocial.org/2008/markup" xmlns:osx= "http://ns.opensocial.org/2009/extensions">
-
<div id="profile_songs">
-
<div class="gadget_profile_header">
-
<osx:NavigateToApp params="{path:"songs_ilike"}">
-
See All
-
</osx:NavigateToApp>
-
<b>Songs iLike</b>
-
</div>
-
-
<div if="${profile.content.track_count == 0}">
-
No songs
-
</div>
-
-
<div if="${profile.content.track_count> 0}" >
-
<ul>
-
<li repeat="${profile.content.tracks}" var="track">
-
<osx:NavigateToApp params="{"track_name":"${track.name}","artist_name":"${track.artist_name}}">
-
<span class="song_title">
-
${track.name}
-
</span>
-
</osx:NavigateToApp>
-
</li>
-
</ul>
-
</div>
-
</div>
-
</script>
In our case, the template is itself generated using an RHTML template in the Rails framework. We can use Ruby expressions to help simplify generation of the static template, but not, of course, any of the dynamic content or conditionals that depend upon the JSON data.
For example, the grungy escaped NavigateToApp parameters are actually generated by this more readable RHTML code:
-
<osx:NavigateToApp params="<%= escape_json('path' => 'track_page', 'autoplay' => true, 'artist_name' => '${track.artist_name}', 'track_name' => '${track.name}') %>">