Continue to Netlog

more seconds

Developer / Documentation / OpenSocial tutorial: OpenSocial JS API

Introduction

OpenSocial is an API used to create applications integrated in Netlog - or any social network that supports it for that matter. Using the OpenSocial API, web developers can send and receive 'social data': profile info, friends, activities, pictures, etc...

There are two quite distinct aspects to OpenSocial, often used together:

  • OpenSocial JS API - for creating a 'gadget' embedded in Netlog.
  • OpenSocial REST API - using http requests, possibly from a remote location (after properly authenticating of course).
This tutorial will cover both, starting with OpenSocial JS API. It should cover everything needed for the vast majority of OpenSocial applications.

Some of the concepts in this tutorial are illustrated in the devdemo app. (you will need authorisation to view it)

Note: in this context we often refer to the social network as the container, so when refering to 'container' in this tutorial we mean the social network, Netlog. Similarly, we will use the terms gadget and application interchangeably.

A gadget is defined by its XML specification

Every gadget is defined by its XML specification, a .xml file containing information for the container needed to properly load the gadget.

Each XML spec contains a <ModulePrefs> section, containing among others the required features: many parts of OpenSocial JS are only available after requiring the appropriate feature.

Apart from this 'meta-information' for the container, a gadget also contains the actual application in the <Content> section which contains the app itself, specified with the usual web technologies: HTML, CSS and JS, possibly embedding Flash (SWFObject is your friend).

Here is an example of a basic XML spec.

<Module>
 <ModulePrefs title="Hello World!">
 <Require feature="opensocial-0.8" />
 </ModulePrefs>
 <Content type="html" view="canvas">
 <![CDATA[
 <!-- HTML and JS content be here, just as if this were the <body> content of a web site -->
 <script type="text/javaScript">alert(\'Hello world!\')</script>
 <h2>Hello world!</h2>
 ]]>
 </Content>
 </Module>

The view="canvas" attribute of the Content tag indicates that we are specifying how the gadget will look in 'canvas view', the default view of all apps on Netlog (including the sandbox).

Netlog also allows apps to have a 'profile view', which are specified in a separate Content part of the XML spec with view="profile". This content part then specifies how the app will look as a 'component' within a user's profile - if the user chose to install and add it.

Start experimenting with OpenSocial within Netlog: create and save an XML spec file, upload it somewhere, and point to its online location in the Netlog OpenSocial Sandbox.

To create and edit files many good free text editors are available online, e.g. NotePad++ (Windows) or Smultron (Mac).

Viewer and owner

Users in the container can typically 'install' an application, allowing them to easily access it from their account and/or show it on their profile where other visitors can see/use it. We call the user whose profile the app is on the owner, and the user looking at the app the viewer.

In Netlog, many applications live in their very own page where they can be used without any need for the user to install them. In this case, only the viewer makes sense (e.g.HotPrints). On the other hand, and a bit more rarely, an app's sole purpose may be to show something on a user's profile, in which case the owner matters most for the application (e.g. Love clock).

In this tutorial we will only care about the viewer, the person visiting the page containing the application.

Fetching and showing profile info about the viewer

Time to let our application do something! The example code below (put it in the CDATA section of your xml spec) calls the init() function when the app is loaded, which in turn calls a function loadViewerInfo().

This loadViewerInfo() requests the viewer info from Netlog, and specifies a callback function to run once the info is loaded: onViewerInfoLoaded(response) will output the fetched info to our application.

Requests to the container are always built up in the same way:

  • Create a new data request object using something like
    var req = OpenSocial.newDataRequest();
  • Add specific specifiers to the request, including additional parameters, and a key string to identify each chunk of data within the response (see example below).
  • Send the request and (typically), specify a callback function taking the response as first argument, like this:
    req.send(onViewerInfoLoaded);
You can see this going on in the example code below.

Note: we define the utility function $() to replace document.getElementById() because we like Prototype JS framework but don't want to force it on you :-)

 <script type="text/javaScript">
 //just handy
 function $(string)
 {
 return document.getElementById(string); 
 }
 //called after we fetched viewer info
 function onViewerInfoLoaded(response)
 {
 nickname = response.get('viewerInfo').getData().getField('nickname');
 displayName = response.get('viewerInfo').getData().getDisplayName();
 thumbnailUrl = response.get('viewerInfo').getData().getField('thumbnailUrl');
 $('viewerinfo').innerHTML = '<img src="'+thumbnailUrl+'" alt="'+nickname+'" title="'+nickname+'"/>'+displayName;
 gadgets.window.adjustHeight();
 }
 //fetch viewer info, adds a request to fetch info about person 'VIEWER', and sends the request
 function loadViewerInfo()
 {
 alert('getting your user info ...');
 //construct opensocial data request object
 var req = opensocial.newDataRequest(); 
 //extra parameters to pass along with the request:
 var extraParams = {};
 extraParams[opensocial.DataRequest.PeopleRequestFields.PROFILE_DETAILS] = ['nickname', opensocial.Person.Field.THUMBNAIL_URL];
 //add request for info about person 'VIEWER'. String 'viewerData' will identify this piece of data within the response object
 req.add(req.newFetchPersonRequest(opensocial.IdSpec.PersonId.VIEWER, extraParams), "viewerInfo");
 //send request to Netlog and call function onViewerInfoLoaded() once that is done
 req.send(onViewerInfoLoaded);
 }
 function init()
 {
 loadViewerInfo(); 
 }
 gadgets.util.registerOnLoadHandler(init); //init() will be called immediately when application is loaded
 </script>
 <div id="container">
 <div id="viewerinfo" style="background-color:#bbb;border:2px solid black;padding:5px;">Loading viewer info...</div>
 </div>

As you see this is just your everyday HTML, CSS and JS. Several interesting things happen here:

  • gadgets.util.registerOnLoadHandler(init); - tells the container to load the init() function once the app is loaded.
  • gadgets.window.adjustHeight(); - resizes the gadget based on its content. Useful to call after you inject something new in the DOM. Note that this needs a required feature in the ModulePrefs:
     <Require feature="dynamic-height" />
     

When trying out the example code: as you can see in the onViewerFriendsLoaded() function, we assume an element (typically a <div>) with id="friendsList" somewhere on the page.

Fetching the viewer's friends

Here is a JS function that loads the viewer's friends and displays them in the app.

Note that Netlog will return maximum 75 friends per call. However, you can provide a starting index in your request, so you may opt to either use some kind of recursion to fetch all friends, or build a pager that requests a new batch of friends for each page.

Optionally you can tell the container to filter on users that 'installed' the application. Virtually all applications on Netlog can be used without any need for the user to 'install' them though, so be aware that this filtering is likely to leave out people who used but did not install the application. For Netlog games this filtering makes even less sense, since it is impossible for users to 'install' a game.

 //called once the viewer's friends are loaded
 function onViewerFriendsLoaded(response)
 {
 var viewerFriends = data.get('viewerFriends').getData(); 
 var s = '<table>';
 var counter = 0;
 viewerFriends.each(function(person) {
 s += '<tr><td><input class="friendSelect" id="'+person.getId()+'" type="checkbox"></td><td><b>'+person.getField('nickname')+'</b></td></tr>';
 counter++;
 });
 s += '</table>';
 $('friendsList').innerHTML = s;
 }
 //get viewer friends. Set filterOnHasApp to 'YES' to only retrieve friends that have app installed.
 //note that Netlog returns max. 75 people at a time
 function loadViewerFriends(filterOnHasApp)
 {
 alert("loading viewer friends..."); 
 var req = new opensocial.DataRequest();
 var userFriendsIdSpec = opensocial.newIdSpec({'userId':'VIEWER', 'groupId':'FRIENDS'});
 var params = {"max" : 75}
 params[opensocial.DataRequest.PeopleRequestFields.SORT_ORDER] = opensocial.DataRequest.SortOrder.NAME;
 if (filterOnHasApp == 'YES')
 {
 params[opensocial.DataRequest.PeopleRequestFields.FILTER] = opensocial.DataRequest.FilterType.HAS_APP;
 }
 req.add(req.newFetchPeopleRequest(userFriendsIdSpec, params), 'viewerFriends');
 req.send(onViewerFriendsLoaded);
 }

Posting notifications and activities

It has 'social' in the name for a reason: OpenSocial offers ways to reach out to a user's friends: notifications and activities. Both consist of a title and (optionally) body field.

Activities are a great way to make your application viral: they are shown in the activity logs of all the user's friends! Think 'UserX sent a gift to UserY with MyApp!', with a link to your app.

Notifications send a direct message to a specific user, so you would use these to, for example, allow a user to send some kind of message to on or more specificly chosen friends. Think 'UserX sent a gift to you! Click here to see it.' with a link to your app.

Posting an activity

This JS function posts an activity about the user with a given title and body. Note that here we just create an anonymous function on the fly as callback, rather than specifying a function specified elsewhere.

 function postViewerActivity(title, body) 
 {
 var params = {}; 
 params[opensocial.Activity.Field.TITLE] = title;
 params['body'] = 
 var activity = opensocial.newActivity(params); 
 opensocial.requestCreateActivity(activity, opensocial.CreateActivityPriority.HIGH, function(){
 alert('activity created!');
 });
 }; 
 

Note that some OpenSocial ways for specifying parameters have a shorthand version, e.g. 'body' and opensocial.Activity.Field.BODY can be used interchangeably.

When running this function your friends will see an activity of you with specified body and title. Body and title may contain HTML tags like <a> and <img>.

Sending a notification

Notifications are sent specifically to one or more users, specified by their userIDs. The following function takes an array of userIDs as argument (extract them from the fetched user friends using getId() for example). It prompts the user for a phrase and then sends that as a notification to the friends that the function received.

//sends netlog notification to selected friends
function sendNotification(recipients)
{
 debug('sending notification to selected friends:');
 var count = recipients.length;
 if (count > 0)
 {
 var title = prompt('Will send the following notification to the '+count+' selected friends:');
 if (title)
 {
 var params = {};
 params[opensocial.Message.Field.TYPE] = "notification";
 params[opensocial.Message.Field.TITLE] = title;
 var message = opensocial.newMessage('', params);
 opensocial.requestSendMessage(recipients, message, function(){
 alert("notification "+title+" sent!");
 }); 
 }
 else
 {
 alert('empty notification - wont send anything'); 
 }
 }
 else
 {
 alert('no friends selected - wont send anything'); 
 }
}

Using persistent storage

When storing app-specific information about the user, you can let the container do the work for you, using OpenSocial's persistent storage feature.

Creating an updating a key-value pair happens trough the same call: if the key is already present its value will be overwritten, if not it will be created.

Storing persistent app data

The following example function would store a key-value pair associated with the viewer, and optionally runs a specified callback function when finished. It can accept JSON objects as values, these will be stringified before storing.

function saveViewerAppData(dataKey, dataValue, callBackFunction)
{
 alert('saving this app data for VIEWER with key '+dataKey+' and value '+dataValue);
 if (typeof(dataValue) == 'object')
 {
 dataValue = gadgets.json.stringify(dataValue);
 }
 var req = opensocial.newDataRequest();
 req.add(req.newUpdatePersonAppDataRequest("VIEWER", dataKey, dataValue));
 if (callBackFunction != undefined)
 {
 req.send(callBackFunction);
 }
 else
 {
 req.send(function(){alert('app data saved!')}); 
 }
}

Retrieving persistent app data

The following example function would retrieve appdata for the viewer. It fetches all data stored for this user, but it would of course be easy to modify it to retrieve just the value for one specific key. (it would go in place of the *)

 //loads ALL data (key "*") from viewer in persistent storage
 function loadViewerAppData(callBackFunction)
 {
 debug('loading viewer app data (all keys) ...');
 var req = opensocial.newDataRequest();
 //we need the viewer too because appData has the userId as key
 req.add(req.newFetchPersonRequest("VIEWER"), 'viewer'); 
 var viewer = opensocial.newIdSpec({ "userId" : "VIEWER" });
 req.add(req.newFetchPersonAppDataRequest(viewer, "*"),'appData'); // * means we load ALL data
 req.send(onViewerAppDataLoaded);
 }

Loading an external web site in an iFrame

Very often developers will want to port an existing web-based application to a container-embedded application. Think 'a small version of your site within Netlog'. This is easy to achieve by inserting an iFrame trough JS. For example the following code will simply load an iframe directly after the gadget is loaded:

 //fetch viewer info, adds a request to fetch info about person 'VIEWER', and sends the request
 //load frame
 function loadFrame(responseData)
 {
 alert('loading external site...');
 var srcString ='http://www.myExternalSite.com';
 $('container').innerHTML = '<iframe frameborder="0" width="710" height="650" src="'+srcString+'"></iframe>';
 gadgets.window.adjustHeight(); //so app resizes itself based on content
 }
 function init()
 {
 loadFrame(); 
 }
 gadgets.util.registerOnLoadHandler(init); //init() will be called immediately when application is loaded
 </script>
 <div id="container">
 </div>

Of course that is kind of boring: your external site gets shown now, but it does not do anything at all with the fact that there's a user in a social network looking at it! We can do better than that, by passing information to our iframe with (GET) parameters in the src URL.

At Netlog we value the principle of 'single sign-on': the users already told Netlog who they are, so they shouldn't have to tell it again to an app. So why not pass the user's nickname and thumbnail URL along in an URL parameter, and have your 'website' detect it and use that as the user 'account'?

Of course there's only so much data you can stuff in URL parameters. No need to worry though: Using OpenSocial REST API you can do OpenSocial calls from within the embedded external site, but you will need to pass a security token along with these requests. So in this case, this security token would be about the only thing you pass along in the src of the iframe.

(In fact there's one other parameter you will need to pass to the iframe but we'll wait until part 2 for that)

To generate a security token that will authenticate REST calls from an embedded site, use this JS function:

 var token = shindig.auth.getSecurityToken();
 

To illustrate all this, the following example shows two JS functions that together do the following:

  • Load some viewer info: userid, nickname and thumbnail URL.
  • The Netlog language version we are on (e.g. 'en'). This is needed to do REST calls (we'll get to those soon).
  • Generate a security token.
  • Pass both on to an embedded iframe
By now it should be easy to follow what happens: getUserInfo() gets the userinfo with a standard OpenSocial JS call, and specifies loadFrame() as callback to work with the response data.

 //load frame
 function loadFrame(responseData)
 {
 //we pass to our iframe some user-specific info
 var userid = responseData.get('viewerData').getData().getId();
 var nickname = responseData.get('viewerData').getData().getField('nickname');
 var thumbnailUrl = responseData.get('viewerData').getData().getField(opensocial.Person.Field.THUMBNAIL_URL);
 var prefs = new gadgets.Prefs(); 
 var language = prefs.getLang(); //URL to do API calls to depends on this
 alert('Got all I need to load the iframe! Userid: ' +userid +'/ nick: '+nickname+'/ language: '+language+' thumburl: '+thumbnailUrl);
 var token = shindig.auth.getSecurityToken(); //to authenticate with 2-legged OAuth so we can use openSocial REST from within the iframe
 var srcString = 'http://wauter.artopia.be/opensocial/mySocialPage.php'+'?st='+token+'&userid='+userid+'&nickname='+nickname+'&language='+language+'&thumbnailUrl='+thumbnailUrl;
 $('container').innerHTML = '<iframe frameborder="0" width="710" height="650" src="'+srcString+'"></iframe>'; 
 gadgets.window.adjustHeight(); //so app resizes itself based on content
 }
 function getUserInfo()
 {
 var req = opensocial.newDataRequest();
 var extraParams = {};
 extraParams[opensocial.DataRequest.PeopleRequestFields.PROFILE_DETAILS] = ['nickname', opensocial.Person.Field.THUMBNAIL_URL];
 req.add(req.newFetchPersonRequest(opensocial.IdSpec.PersonId.OWNER, extraParams), "viewerData");
 req.send(loadFrame);
 }

Of course, we are not passing on this security token for nothing: we can use it to sign OpenSocial REST calls done from the site embedded within the iframe. These calls are regular HTTP requests, and by passing on the security token as a parameter with each request, the container can make sure the requests are not coming from anywhere else.

A concrete explanation on working with OpenSocial REST is the subject of part 2 of this tutorial.