How do we turn a Google Gadget into an OpenSocial application? An initial look at the OpenSocial API—what it includes, as well as what it doesn't.
The past year has seen an explosion in the growth of social-networking sites like Facebook. People have jumped at the opportunity to find existing friends, make new ones and spend time communicating and participating in group activities on-line. Facebook might be the best-known site, but LinkedIn, Ning, Hi5, Orkut and others also have become popular.
As we might expect in a competitive marketplace, each of these sites has tried to offer unique features to encourage new people to sign up. During the summer of 2007, Facebook unveiled one of the most interesting and powerful of these features in its developer platform—basically, a way to integrate third-party Web applications into Facebook.
This API has led to a torrent of applications being developed for Facebook. It's not clear whether anyone is making money off these applications or whether there are any that people find truly useful (rather than frivolous). But, there are plenty of indications that Facebook's API is an important milestone for social-networking applications and for Web applications in general. For the first time, we have a Web site that is providing an open platform for application development.
In response to the popularity of Facebook's developer API, a number of competitors announced they would be supporting a similar API, known as OpenSocial. Applications written for OpenSocial should work equally well on all compliant social networks. Thus, instead of writing one application for MySpace and another for Ning, you can write the application once and deploy it on many different networks. The exception, at least for now, is Facebook; whether Facebook decides to join the OpenSocial consortium or provide a compatibility layer remains to be seen.
The OpenSocial specification was spearheaded by Google and is based on the specification known as Google Gadgets, part of the personalized iGoogle page for some time. Last month, we looked at how to build a simple Google Gadget, which packages HTML and JavaScript into an XML wrapper.
This month, we look at how to take our simple Google Gadget and turn it into an OpenSocial-compliant application. We begin to see the pros and cons of the OpenSocial standard and consider ways to make use of its capabilities.
As we saw last month, the simplest possible “Hello, world” Google Gadget looks like the following:
<?xml version="1.0" encoding="UTF-8" ?> <Module> <ModulePrefs title="Hello world" /> <Content type="html"> <![CDATA[ Hello, world! ]]> </Content> </Module>
The gadget comes as an XML file, with a Module section and a Content section. The Module section allows us to specify gadget-specific preferences, using the ModulePrefs tag. The Content section, as you might expect, contains the HTML and JavaScript that will be displayed and executed for the user.
We can turn a simple gadget into an OpenSocial gadget by adding a new Require tag within our Module tag:
<?xml version="1.0" encoding="UTF-8" ?> <Module> <ModulePrefs title="Hello world" /> <Require feature="opensocial-0.6" /> <Content type="html"> <![CDATA[ Hello, world! ]]> </Content> </Module>
The Require tag indicates that our gadget is implementing the OpenSocial standard, version 0.6. (A new version undoubtedly will be released by the time this column is printed. The initial version, 0.5, was superseded by 0.6 in late December 2007.) Other than that single line, this is the same “Hello, world” widget we installed on our iGoogle page last month. In theory, we can go ahead and install this application on the social-networking site (OpenSocial container) of our choice, and it'll work just fine.
“Hello, world” is boring enough as a standalone program; using it as an example of a social-networking API seems almost silly. For a gadget to become a fully fledged OpenSocial application, it needs to demonstrate an ability to interact with other people. More precisely, a socially aware application should be able to find out something about me and my friends, as well as what I (and my friends) do.
The OpenSocial API addresses this by offering three types of functionality:
People and relationships: get information about you, your friends and the various pieces of data associated with those friends. The Person class provides access to this information.
Activities: social-networking sites are interesting because they let you interact with your friends in a variety of activities. These activities can range from exchanging messages to answering questions in an on-line poll to keeping up to date on the latest sports scores. OpenSocial sees an activity as a collection of actions within a particular container. The Activity class provides access to this data.
Persistence: OpenSocial makes it possible for an application to store information between sessions. One of the most interesting aspects of this persistence API is the fact that storage is handled by the OpenSocial container, not by the application. There is no Persistence class for handling such data. Rather, the data is read and written by invoking methods on the overall opensocial object. Note that the persistence layer lets applications store data globally, as well as on a per-user or per-application instance basis, as needed.
Interactions with these three objects, as well as with the OpenSocial API in general, is done via method calls on the opensocial object. Typically, methods execute asynchronously, with a callback method specified as one of the invocation parameters. For example, we can get information about the person currently running (viewing) our application by creating a new OpenSocial data request and indicating what request we want to make:
var req = opensocial.newDataRequest(); req.add(req.newFetchPersonRequest(opensocial.DataRequest. ↪PersonId.VIEWER),"viewer");
We then send the request to our container:
req.send(response);
The response parameter is a function; as soon as the request returns a response, that function will be invoked. Moreover, when the response function is invoked, it will be passed a single parameter that contains the results from our method call.
We can send multiple queries within a single data-request object; all we have to do is invoke req.add multiple times. As you can see from the above line of code, invoking req.newFetchPersonRequest required that we both indicate what we want to request, and that we give it a symbolic name (viewer). This naming allows us to pull apart different types of response data within a single object.
You might be wondering what stops the viewer from being able to retrieve arbitrary data from the OpenSocial container. The answer is that OpenSocial defines two basic types of people: the viewer and the owner. The former, as we have seen, refers to the person who is running and viewing the operation—and might even refer to no one at all, if our system permits anonymous browsing. The owner, by contrast, must be a defined person on the system, and may very well refer to the same person as the viewer. But at least in theory, OpenSocial will provide only limited information to viewers about owners with whom they have no relationship.
Perhaps the simplest type of application we can write with OpenSocial is one that shows the current user's friends. Better yet, because friends on a social-networking site typically upload their pictures, we even can display a list of the viewer's friends.
Last month, we saw how we can modify the HTML in which a Google Gadget—or an OpenSocial application—is running. Create an empty div, build up the HTML in a variable, and then set the div's innerHTML property to be that of the variable. For example:
html = "<p>Hello</p>"; div.innerHTML = html;
In order to display a list of the viewer's friends, we need to retrieve a list of those friends. We then can iterate over those friends, putting their thumbnail image URL in our html variable.
In order to retrieve a list of friends, we must do the following:
viewer_friends = opensocial.DataRequest.Group.VIEWER_FRIENDS; req.add(req.newFetchPeopleRequest(viewer_friends, opt_params), ↪"viewer_friends"); req.send(response);
The above request contains a single query, which we call viewer_friends. (Don't be confused by the viewer_friends variable, which was introduced simply to make the lines easier to understand.)
When the method has finished executing asynchronously, it invokes our response function. We can define it like any other JavaScript function, and Google's documentation even indicates that you can use JavaScript libraries, such as Prototype or Dojo, inside an OpenSocial application.
Google already has included a number of useful JavaScript functions as part of its implementation of gadgets, meaning that a Ruby-like each method is available to us. That method, which typically is invoked on an array, takes a function as a parameter. The function is executed once for each element of the array, with each array element being passed to the function in turn. Thus, we can write our response method as follows:
function response(data) { var viewer_friends = data.get("viewer_friends").getData(); viewer_friends.each(function(person) { var thumb = person.getField(opensocial.Person.Field.THUMBNAIL_URL); html += '<img src="' + thumb + '"/>'; }); document.getElementById('main').innerHTML = html; }
Our response method is invoked only after the request has been sent. Its data parameter is populated with the response to our query, which we can retrieve with its name (viewer_friends). We then use the getData() method on the resulting object to give us the data that interests us, namely an array of person objects.
Each person in OpenSocial has a few required properties, among them the URL of their personal thumbnail picture. You can see from the above example that we retrieve it by invoking the getField() method on a person, indicating which field we want by using a value provided by the OpenSocial framework. We can use several such values, including ID (for their unique ID), NAME (for their name) and even PROFILE_URL (for the person's home page URL on the system). Beyond those basic fields, a well-behaved OpenSocial application must query its container to make sure that it's available.
One of the biggest problems with OpenSocial is its inherent diversity and cross-platform functionality. Programmers who create desktop applications have discovered—often the hard way—that different operating systems have different conventions for how dialog boxes, or even menus, look and feel. These often-subtle design distinctions can play a major role in the usability of an application.
Thus, it'll be interesting to see what happens when OpenSocial applications are unveiled and are supposed to work cleanly on all systems. One of the Facebook platform's great advantages is the fact that it shoehorns application content into a standard look and feel. This is missing with OpenSocial, and although it encourages diversity, I'm far from convinced this will be good for end users.
Another, and more serious, issue with OpenSocial is that it is designed to let applications run in different contexts, not seamlessly join data from diverse social-networking systems. Yes, it's nice that software developers will be able to release their code on multiple platforms at the same time. But as a user as well as a developer, I'm interested in getting a comprehensive list of all my friends/contacts/links from all the social networks to which I belong.
Just a few weeks before I wrote these words, well-known blogger Robert Scoble was kicked off Facebook for downloading his contact list into another program. (His account was reinstated within a few days.) The notion that data should stay locked within one of these systems, rather than be freely downloadable and transferable by the people who entered and approved it, is disappointing.
If I create a forum application using OpenSocial, and I use the persistence API in order to store messages, it might work just fine. But, what if I want the forum to work across different networks, such that forum postings are persistent not only across users, but also across the different OpenSocial containers? That appears to be completely unsupported by the standard. And although such capabilities would seem to be against the interests of the various social-networking companies, it is most certainly in the interest of the individual users.
Of course, given that OpenSocial is nothing more than a specification and set of JavaScript libraries, there's still hope. Perhaps someone will create a JavaScript library that allows OpenSocial client applications to store and retrieve state on a remote server (that is, not on the OpenSocial container's server) in a format that can be unpacked and used across systems easily. Such a library might be difficult to create, particularly given the various user-visibility and privacy issues. But, it would be an additional step toward not just code portability, but data portability, that many people would like to see in OpenSocial.
I should note that I'm not the first or only person to raise some of these concerns. Tim O'Reilly, among others, has expressed his disappointment with the initial versions of OpenSocial (see Resources).
OpenSocial provides a standard library and packaging system for applications that fit into a social-networking site. Assuming that enough sites implement the OpenSocial specification, this will greatly ease the burden from developers, who still will have to develop for Facebook.
This month, we took a short look at what the OpenSocial standard offers developers and how we can create applications that take advantage of these supports. We also saw how OpenSocial applications communicate with the enclosing containers. Finally, we saw how we can even create a simple application in only a few lines of carefully chosen code.
It remains to be seen whether OpenSocial will succeed, either on its own or as a competitor to the Facebook development platform. I do believe that it needs to become more mature before it will be truly useful. But, the intentions are definitely positive, and there is a great deal of potential for good to come out of this standard.