Building a simple app using OpenSocial Templates
From OpenSocial
Warning: parts of this article are out of date. While we work on updating it, please refer to the OpenSocial Templates Developer's Guide for the most up-to-date reference information.
Contents |
Overview
OpenSocial Templates enable you to develop apps using markup language instead of Javascript. Using them in combination with Data Pipelining, you can build apps to display your social information. Templates can be used both on the profile and canvas views of an app.
The following tutorial illustrates how to use the server side capabilities of OS Templates to build an OpenSocial app, with the help of a sample. The sample uses OpenSocial JS API in the canvas view to save the data and OS Templates in the profile view to display it. You can find the complete code sample here.
Using OS Templates
To use OpenSocial Templates in our app, we include the following tags in the ModulePrefs:
<ModulePrefs title="Gifting friends" descrition="App that demonstrates the use of templates in the profile view to retrieve and display data."> <Require feature="opensocial-0.8"/> <Require feature="opensocial-data"/> <Require feature="opensocial-templates"> <Param name="process-on-server">true</Param> </Require> </ModulePrefs>
You need to bind your templates to either data from an external server or the container, using OpenSocial Data Pipelining.
In order to use data pipelining in the app, we also include the following tag in the ModulePrefs:
<Require feature="opensocial-data"/>
To specify the data to be used by the app, we include the <script type="text/os-data"> tag within the <Content> tag and use the <os:..> tag to indicate the type of data required. The key field is used to specify the name that can be used to refer to the data returned :
<script type="text/os-data" xmlns:os="http://ns.opensocial.org/2008/markup"> <os:OwnerRequest key="owner" /> <os:PersonAppDataRequest key="gifts" method="appdata.get" userId="@viewer" appId="@app" fields="giftsData,lastGiftBy" /> </script>
Now we use the data obtained through the os-data section, by defining a template section using <script type="text/os-template"> also within the <Content> :
<script type="text/os-template" xmlns:os="http://ns.opensocial.org/2008/markup" xmlns:osx= "http://ns.opensocial.org/2009/extensions"> This app belongs to ${owner.url}<a href="${owner.url}">${owner.name.givenName} ${owner.name.familyName}</a> ..... </script>
The following sections describe how we use specific template tags to retrieve the person, friends and app data and display them.
Fetching Person data
First we retrieve the Viewer and Owner details using the <os:ViewerRequest> and <os:OwnerRequest> data tags.
To specify extra profile fields that need to be retrieved, we use the fields=comma-separated-fieldNameList in the following manner:
<script type="text/os-data" xmlns:os="http://ns.opensocial.org/2008/markup"> <os:ViewerRequest key="viewer" fields="name,gender"/> </script> <script type="text/os-template" xmlns:os="http://ns.opensocial.org/2008/markup"> <span class="${viewer.gender}"><b>Welcome ${viewer.name.givenName}</b></span> </script>
Fetching Friends and linking to their profile
We move on to fetching the viewer's friends, using <os:PeopleRequest> and display their names, by iterating through the list using the template keywords: repeat, Context, Cur as shown below:
<script type="text/os-data" xmlns:os="http://ns.opensocial.org/2008/markup"> <os:PeopleRequest key="viewerFriends" userId="@viewer" groupId="@friends" fields="name,profileUrl"/> </script> <script type="text/os-template" xmlns:os="http://ns.opensocial.org/2008/markup" xmlns:osx= "http://ns.opensocial.org/2009/extensions"> This app belongs to ${owner.url}<a href="${owner.url}">${owner.name.givenName} ${owner.name.familyName}</a> <p/>Your friends : <ul> <li repeat="${viewerFriends}"> <osx:NavigateToPerson person="${Cur}"> <span class="name" id="id${Context.Index}">${Cur.name.givenName}</span></osx:NavigateToPerson> </li> </ul> </script>
The tag <osx:NavigateToPerson> is used to build a link to the profile page of the user, who is specified using the field person. In this case we build a link to the profile of each of the viewer's friends.
Fetching App data and parsing it
Then we fetch the app data using the tag <os:PersonAppDataRequest> with the userId=<user Id of the user for whom you want to fetch the data> and fields="comma separated list of key names for the data"
<script type="text/os-data" xmlns:os="http://ns.opensocial.org/2008/markup"> <os:PersonAppDataRequest key="gifts" method="appdata.get" userId="@viewer" appId="@app" fields="giftsData,lastGiftBy" /> </script> <script type="text/os-template" xmlns:os="http://ns.opensocial.org/2008/markup" xmlns:osx= "http://ns.opensocial.org/2009/extensions"> <li repeat="${viewerFriends}"> <span class="name" id="id${Context.Index}">${Cur.name.givenName}</span> <span class="gift" if="${osx:parseJson(gifts[viewer.id]['giftsData'])[Cur.id] != null}"> received <os:Html code=${osx:parseJson(gifts[viewer.id]['giftsData'])[Cur.id]} from you /></span> </li>
The giftsData returned in this case is a stringified JSON object (please see the code of the canvas view of code sample). Hence when we retrieve it, it is passed into the function osx:parseJson, which returns a JSON object.
Note that the data is being updated only in the canvas view since we currently do not support updating app data using templates.
Navigating to canvas view
We use the tag <osx:NavigateToApp> to build a link to the canvas view of the app, as shown below:
<script type="text/os-template" xmlns:os="http://ns.opensocial.org/2008/markup" xmlns:osx= "http://ns.opensocial.org/2009/extensions"> <osx:NavigateToApp>Send gift to a friend</osx:NavigateToApp>
If you wish to pass parameters to the canvas view, then you can use the params argument to NavigateToApp, as shown below:
<osx:NavigateToApp params="{a:b}">Goto Canvas</osx:NavigateToApp>
and you can use the view params in javascript land as before on canvas page:
var prefs = gadgets.views.getParams(); if (prefs['a'] == 'b') { ... }
Using Joins in data pipelining
'Joins' can be used in data pipelining to pass the data returned from one request to the next one. In our app, we use this feature to pass the 'lastGiftBy'(the app data that stores the ID of the user who gave the last gift) returned by the <os:PersonAppDataRequest> as the value of the userId field in <os:PeopleRequest>
<script type="text/os-data" xmlns:os="http://ns.opensocial.org/2008/markup"> <os:PersonAppDataRequest key="gifts" method="appdata.get" userId="@viewer" appId="@app" fields="giftsData,lastGiftBy" /> <os:PeopleRequest key="lastGift" userId="${gifts[viewer.id]['lastGiftBy']}" groupId="@self" /> </script> <script type="text/os-template" xmlns:os="http://ns.opensocial.org/2008/markup" xmlns:osx= "http://ns.opensocial.org/2009/extensions"> The last gift was sent by : ${lastGift.name.givenName}
os:Html
The os:Html tag is used to display data as raw html. In our app, we use it to display the gift data for each friend:
<span class="gift" if="${osx:parseJson(gifts[viewer.id]['giftsData'])[Cur.id] != null}"> received <os:Html code=${osx:parseJson(gifts[viewer.id]['giftsData'])[Cur.id]} from you /></span>
Looping and Conditionals
To iterate through collections and test for conditions, OpenSocial templates support special variables and attributes like repeat, Context, Cur, if.
In our app use the 'repeat' attribute to loop through the list of friends, while 'Cur' indicates the current friend element during the iteration :
<script type="text/os-template" xmlns:os="http://ns.opensocial.org/2008/markup"> <ul> <li repeat="${viewerFriends}"> <span class="name" id="id${Context.Index}">${Cur.name.givenName}</span> </ul>
The 'if' variable is used to test whether the viewer and the owner are the same user and display the right message:
This app belongs to <span if="${viewer.id != owner.id}"> <a href="${owner.url}">${owner.name.givenName} ${owner.name.familyName}</a></span> <span if="${viewer.id == owner.id}">you</span>
Internationalization/Localization
MessageBundles are supported in OpenSocial templates for internationalization. These work in just they way they do for regular non-templated gadgets. Here's a quick example below to get you started.
Create a message bundle like this (say this is at http://example.org/app/en_ALL.xml)
<?xml version="1.0" encoding="UTF-8"?> <messagebundle> <msg name="hello"> Hello! </msg> </messagebundle>
Include the message bundle for the languages you wish to support in ModulePrefs, and then use __MSG_messageName__ in your gadget content.
<ModulePrefs> <Locale messages="http://example.org/app/en_ALL.xml" lang="en" country="ALL" /> <Locale messages="http://example.org/app/fr_FR.xml" lang="fr" country="FR" /> </ModulePrefs> ... <Content type="html"..> <![CDATA[ ... __MSG_hello__ ]]> </Content>
You can find more about localization here: Localizing OpenSocial applications
Complete sample
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?> <Module> <ModulePrefs title="Gifting friends" descrition="App that demonstrates the use of templates in the profile view to retrieve and display data."> <Require feature="opensocial-0.8"/> <Require feature="opensocial-data"/> <Require feature="opensocial-templates"> <Param name="process-on-server">true</Param> </Require> </ModulePrefs> <Content type="html" view="profile"><![CDATA[ <style type="text/css"> body,span,p { font-family:times; } .name { color: blue; } .male { color: red; } .female { color: purple; } .gift { font-family: arial; font-size: 12px; padding: 8px; } </style> <script type="text/os-data" xmlns:os="http://ns.opensocial.org/2008/markup"> <os:ViewerRequest key="viewer" fields="name,gender"/> <os:OwnerRequest key="owner" /> <os:PeopleRequest key="viewerFriends" userId="@viewer" groupId="@friends" fields="name,profileUrl"/> <os:PersonAppDataRequest key="gifts" method="appdata.get" userId="@viewer" appId="@app" fields="giftsData,lastGiftBy" /> <os:PeopleRequest key="lastGift" userId="${gifts[viewer.id]['lastGiftBy']}" groupId="@self" /> </script> <script type="text/os-template" xmlns:os="http://ns.opensocial.org/2008/markup" xmlns:osx= "http://ns.opensocial.org/2009/extensions"> <span class="${viewer.gender}"><b>Welcome ${viewer.name.givenName}</b></span> <p/> This app belongs to <span if="${viewer.id != owner.id}"><a href="${owner.url}">${owner.name.givenName} ${owner.name.familyName}</a></span> <span if="${viewer.id == owner.id}">you</span> <p/>Your friends : <ul> <li repeat="${viewerFriends}"> <osx:NavigateToPerson person="${Cur}"> <span class="name" id="id${Context.Index}">${Cur.name.givenName}</span></osx:NavigateToPerson> <span class="gift" if="${osx:parseJson(gifts[viewer.id].giftsData)[id] != null}"> received <os:Html code=${osx:parseJson(gifts[viewer.id].giftsData)[id]}></span> </li> </ul> The last gift was sent by : ${lastGift.name.givenName} <br/> <br/> <osx:NavigateToApp>Send gift to a friend</osx:NavigateToApp> <p/> <img src="http://code.google.com/apis/opensocial/images/opensocial.jpg" alt="OpenSocial logo" height="50%" /> <p/> </script> ]]></Content> <Content type="html" view="canvas"><![CDATA[ <script type="text/javascript"> var availableGifts = ['a music CD', 'a PSP game', 'a novel', 'a dress']; var givenGifts = {}; function requestViewerAndFriends() { // Create a new data request skeleton var request = opensocial.newDataRequest(); // Add the viewer ID to the request request.add(request.newFetchPersonRequest('VIEWER'), 'viewer'); request.add(request.newFetchPersonRequest('OWNER'), 'owner'); // Add the friends to the request var viewerFriends = opensocial.newIdSpec({"userId" : "VIEWER", "groupId" : "FRIENDS", "networkDistance" : "2"}); var opt_params = {max:100}; request.add(request.newFetchPeopleRequest(viewerFriends, opt_params), 'viewerFriends'); var personIdSpec = opensocial.newIdSpec({"userId": "VIEWER"}); request.add(request.newFetchPersonAppDataRequest(personIdSpec, 'giftsData'), 'giftData'); request.send(processFriends); }; /** * Method to process the data returned from the request. * The friends list is displayed as a drop down and the * app data is saved in the local gifts list, so it can used * when updating the app data when a new gift is given */ function processFriends(data) { var viewer = data.get('viewer').getData(); if (viewer != null) { var owner = data.get('owner').getData(); document.getElementById('viewerName').innerHTML = viewer.getDisplayName(); document.getElementById('viewerId').value = viewer.getId(); var html = []; html.push("<select id='friend'>"); var friends = data.get('viewerFriends').getData(); friends.each( function(person) { html.push('<option value="', person.getId(), '">', person.getDisplayName(), '</option>'); }); html.push('</select>'); document.getElementById('friendNames').innerHTML = html.join(''); // Get the list of gifts var giftData = data.get('giftData').getData(); var json = null; if (giftData[viewer.getId()]) { json = giftData[viewer.getId()]['giftsData']; } if (!json) { givenGifts = {}; } try { // The app data is an escaped string, hence needs to be unescaped // before being parsed as a json object givenGifts = gadgets.json.parse(gadgets.util.unescapeString(json)); } catch (e) { givenGifts = {}; } // Display the gifts given var giftsHtml = []; giftsHtml.push('The following are the gifts that you have given to your friends:', '<br/>', '<ul>'); for (i in givenGifts) { if (i.hasOwnProperty) { giftsHtml.push('<li>', friends.getById(i).getDisplayName(), 'got', gadgets.util.escapeString(givenGifts[i])); } } giftsHtml.push('</ul>'); document.getElementById('givenGifts').innerHTML = giftsHtml.join(' '); } else { document.getElementById('main').innerHTML = "Please install the app in order to view the content"; } }; /** * Method to display the constant list of available gifts as a drop down */ function displayGiftOptions() { var html = []; html.push("<select id='gift'>"); for (var i=0; i<availableGifts.length; i++) { html.push('<option value="', availableGifts[i], '">', availableGifts[i], '</option>'); } html.push("</select>"); document.getElementById('giftNames').innerHTML = html.join(''); }; /** * Method to save the name of the gift and the message entered * with the ID of the friend who it was given to */ function saveGift() { var giftHtml = []; giftHtml.push(document.getElementById('gift').value); giftHtml.push(" - ") giftHtml.push(document.getElementById('yourMessage').value); var gift = giftHtml.join(''); var friend = document.getElementById('friend').value; givenGifts[friend] = gift; // Now since each element in gift data was unescaped, it needs to be re-escaped before saving for (i in givenGifts) { givenGifts[i] = gadgets.util.escapeString(givenGifts[i]); }; var jsonRep = gadgets.json.stringify(givenGifts); var updateReq = opensocial.newDataRequest(); updateReq.add(updateReq.newUpdatePersonAppDataRequest('VIEWER', 'giftsData', jsonRep)); updateReq.add(updateReq.newUpdatePersonAppDataRequest('VIEWER', 'lastGiftBy', document.getElementById('viewerId').value)); // Get the list of friends and given gifts again var viewerFriends = opensocial.newIdSpec({"userId" : "VIEWER", "groupId" : "FRIENDS", "networkDistance" : "2"}); var opt_params = {max:100}; var personIdSpec = opensocial.newIdSpec({"userId": "VIEWER"}); updateReq.add(updateReq.newFetchPeopleRequest(viewerFriends, opt_params), 'viewerFriends'); updateReq.add(updateReq.newFetchPersonAppDataRequest(personIdSpec, 'giftsData'), 'giftData'); updateReq.send(giftSent); }; function giftSent(data) { document.getElementById('status').innerHTML = "Your gift was saved"; document.getElementById('yourMessage').value = ""; requestViewerAndFriends(); }; function init() { requestViewerAndFriends(); displayGiftOptions(); }; gadgets.util.registerOnLoadHandler(init); </script> <div id="main"> <form> <span id='status' style="font-size:12px;color:red"></span> <p/> <span id='viewerName'></span> wants to gift a <span id='giftNames'></span> to <span id='friendNames'></span> <p/> Enter the message to go with your gift <br/> <input type='text' id='yourMessage'></input> <p/> <input type='button' value='Give' onClick='saveGift();' /> <input type="hidden" id="viewerId" /> <p/> <span id='givenGifts'></span> </form> </div> ]]></Content> </Module>
