Ning Developer Network

Ernie H.

Ning OpenSocial Application Tutorials

OpenSocial Examples

Hello, World!

(Note:  Some of the tutorials and tutorial descriptions provided are based on the OpenSocial Tutorial for Orkut.  Due to the open nature of OpenSocial, you may find that a lot of the Ning OpenSocial Applications that you create can easily be ported to other OpenSocial containers such as Orkut, hi5 and MySpace with very little additional development time, and vice versa.)

OpenSocial Applications are essentially XML files that adhere to the OpenSocial specifications.

To create a quick "Hello World" application, paste the following code into the Google Gadget Editor, or an XML file:

<?xml version="1.0" encoding="UTF-8" ?>

<Module>
<ModulePrefs title="Hello, world!">
<Require feature="opensocial-0.7" />
</ModulePrefs>
<Content type="html">
<![CDATA[
<script type="text/javascript">

function init() {
document.getElementById("handler").innerHTML = "Hello, world.";
}
gadgets.util.registerOnLoadHandler(function() { init(); });
</script>

<div id="handler"></div>
]]>
</Content>
</Module>

You can view a live example of this on http://os.ning.com/apps/tutorials/hello-world.xml .

Once the OpenSocial Application is loaded inside a container, registerOnLoadHandler is called.  In this case, the function calls the Javascript init() function which writes "Hello, world." to the "handler" HTML element.

A key element in this example is the use of <Require feature="opensocial-0.7"/> as a parameter of the module declaration. This specifies the release of opensocial required by the module. Containers may or may not implement all possible versions of the OpenSocial API, and when they don't the member will see an error. <![ CDATA[...]]> contains the bulk of the gadget, including all of the HTML, CSS, and JavaScript (or references to such files). The content of this section should be treated like the content of the body tag on a generic HTML page. 

From there, you can simply save your gadget as a file in your network (through WebDav) in a new directory, OpenSocial, with the name for example helloworld.xml. You can also can develop your xml document off an external server.  Once you done that, you can add your XML file to the profile page of your social network.  Detailed instructions on how to add your OpenSocial Application are listed later in this document:  Testing Your OpenSocial Application.

The process of editing an XML file, upload it to a server and re-evaluating the code you just wrote can be a tedious process.  Thankfully, Google has an OpenSocial Application called CodeRunner where you can cut and paste javascript code into a textbox, where the code is run client-side; this tool is pretty handly, especially with developers who are comfortable with Javascript and would just like to experiment with the more OpenSocial based methods. 

Wrapping a Flash Embed

You may want to include a Flash embed in your application. This is a great way to include features such as games, slideshows, media players, etc. Here's an example of what you can do:

<?xml version="1.0" encoding="UTF-8" ?>
<Module>
  <ModulePrefs title="Wrapping a Flash Embed">
    <Require feature="opensocial-0.7" />
    <Require feature="dynamic-height"/>
    <Require feature="views" />
  </ModulePrefs>
  
  <Content type="html">
    <![CDATA[
<embed src="http://static.ning.com/blubbie/widgets/video/flvplayer/flvplayer.swf?v=3.2%3A4925" flashvars="config_url=http%3A%2F%2Fblubbie.ning.com%2Fvideo%2Fvideo%2FshowPlayerConfig%3Fid%3D2064662%253AVideo%253A362%26x%3DuqMUbtbg39I7nFQfI5MuDscnp5Lv3qFi&amp;video_smoothing=on&amp;autoplay=off" width="448" height="364" scale="noscale" wmode="transparent" allowscriptaccess="always" allowfullscreen="true" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" ></embed>

<script type="text/javascript">
gadgets.window.adjustHeight();
</script>
    ]]>
</Content>
</Module>

In the example shown above, we've created an app that displays a video player. To ensure that the dimensions of our application accommodate our embed, we call the adjustHeight() function after specifying the embed code. If and when you call the adjustHeight() function be sure to also include the "dynamic-height" parameter in your module declaration.

Hello, World! in Different Views

Once we understand the basic concept of the Content section and how it's used to show HTML, CSS and Javascript inside, we can expand that idea with the view attribute, used if you want to show conditional content based on if you're viewing the OpenSocial Application in the Canvas View or the Profile View. This can be useful from a User Experience perspective, possibly showing less detailed information where the viewport is smaller.

<?xml version="1.0" encoding="UTF-8" ?>

<Module>
<ModulePrefs title="Multiple Content Sections (version 5)">
<Require feature="opensocial-0.7" />
</ModulePrefs>
<Content type="html" view="canvas,profile">

<![CDATA[
<p>Hello, world!</p>
]]>
</Content>
<Content type="html" view="profile">

<![CDATA[
<p>Hello, Profile View!</p>
]]>
</Content>
<Content type="html" view="canvas">

<![CDATA[
<p>Hello, Canvas View!</p>
]]>
</Content>
</Module>

In the following example, "Hello, Profile View!" and "Hello, Canvas View!" is shown is the Profile and Canvas Views, respectively. "Hello, world!" is displayed for both Canvas and Profile Views.

Navigating Between Views and Passing Parameter Information

The requestNavigateTo method is used to navigate between different application views. You can pass parameter data between these views by passing appropriate data as a parameter in the previously mentioned method. The following sample code illustrates one such example:

<?xml version="1.0" encoding="UTF-8" ?>
<Module>

<ModulePrefs title="" description="" screenshot="" author="" author_email="" author_location="" author_affiliation="">
<Require feature="opensocial-0.7" />
<Require feature="dynamic-height" />

<Require feature="views" />
</ModulePrefs>
<Content type="html">
<![CDATA[
<select id="ddl_canvas">
<!-- option value="home">home. Home is not defined on Ning.</option -->

<option value="profile">profile.left</option>
<option value="canvas">canvas</option>
</select>
<br>
Value One: <input type="text" id="txtParamOne" />

<br>
Value Two: <input type="text" id="txtParamTwo" />
<br>
<input type="button" onclick="navigate()" value="Submit" />

<script type="text/javascript" id="MSOS_HOME">
function navigate(){
var params = {};
var isPrimary = false;
var surfaceName = document.getElementById('ddl_canvas').value;
if (surfaceName === 'canvas') isPrimary = true;
var surfaces = gadgets.views.getSupportedViews();
var surfaceRef = surfaces[surfaceName];
params['param1'] = document.getElementById('txtParamOne').value;
params['param2'] = document.getElementById('txtParamTwo').value;
gadgets.views.requestNavigateTo(surfaceRef, params);
}

function init() {
// Credit: Chad Russell, MySpace Development Team

// Based off of: http://developer.myspace.com/Community/forums/p/521/2448.aspx
// var params1 = opensocial.getEnvironment().getParams();
var params1 = gadgets.views.getParams();

if ('undefined'===typeof(params1) || 'undefined' === typeof(params1['param1'])){
return;
}
document.getElementById('txtParamOne').value = params1['param1'];
document.getElementById('txtParamTwo').value = params1['param2'];
}
init();

</script>
]]>
</Content>
</Module>

For those needing to retrieve the parameters passed from the query string, the view-params object takes parameters in JSON format to be passed into applications.  This is similar to the way that  Orkut
passes query string parameters via gadgets.util.getUrlParameters() in this URL:  http://code.google.com/apis/orkut/docs/orkutdevguide.html.  It is also possible to manually parse the parameters using location.href and using a string matching function or a regular expression.

Displaying Profile Information

Now you can begin creating the actual body of code that uses the OpenSocial API to display some useful information, such as the viewer's name and profile photo.

You can fetch this data by creating an opensocial.newDataRequest object, populating it with a request to fetch the viewer (that's you), and sending the request to the server.

This example uses the key opensocial.DataRequest.PersonId.VIEWER, but you can also use the opensocial.DataRequest.PersonId.OWNER key. If Alice is viewing Bob's profile, then Alice is the viewer and Bob is the owner. You can access information about both users by specifying the appropriate key.

To see this in action, replace the CDATA content in your gadget with the following JavaScript:

<?xml version="1.0" encoding="UTF-8" ?>

<Module>
<ModulePrefs title="Hello, world!">
<Require feature="opensocial-0.7" />
<Require feature="dynamic-height" />
</ModulePrefs>

<Content type="html">
<![CDATA[

<script type="text/javascript">
function response(data) {
var viewer = data.get("req").getData();
var name = viewer.getDisplayName();
var thumb = viewer.getField(opensocial.Person.Field.THUMBNAIL_URL);

var html = '<img src="' + thumb + '"/>' + name;
document.getElementById('dom_handle').innerHTML = html;
};

function request() {
var req = opensocial.newDataRequest();
req.add(req.newFetchPersonRequest(opensocial.DataRequest.PersonId.VIEWER), "req");
req.send(response);
};

request();

</script>
<div id="dom_handle"></div>
]]>
</Content>
</Module>

Note that the send method within the request is asynchronous, and it takes a callback function - in our example, response - as a parameter. This callback function will be invoked when the response comes back from the server.

Displaying Friends' Information

This next portion of the tutorial builds on the previous, but in addition to showing your own information (as the viewer), it will describe how to fetch all of your friends and display their information within the gadget.

First you need to modify your request to ask for the data about the viewer and the viewer's friends. You can batch this request for two data sets by modifying the request function like this:

function request() {
var req = opensocial.newDataRequest();

var opt_params = { };
opt_params[opensocial.DataRequest.PeopleRequestFields.PROFILE_DETAILS] = [ opensocial.Person.Field.PROFILE_URL ];

req.add(req.newFetchPersonRequest(opensocial.DataRequest.PersonId.VIEWER, opt_params), "viewer");
req.add(req.newFetchPeopleRequest(opensocial.DataRequest.Group.VIEWER_FRIENDS, opt_params), "viewer_friends");
req.send(response);
}

Note that you are adding a newFetchPeopleRequest, which will return a collection of many people, as opposed to a newFetchPersonRequest, which only returns one person.  You can also add different parameters to the opt_params[] variable using constants from opensocial.DataRequest.PeopleRequestFields; adding the following line, for example, filters the set of VIEWER_FRIENDS to only include friends that have also added the same OpenSocial Application to your networks:

opt_params[opensocial.DataRequest.PeopleRequestFields.FILTER] = opensocial.DataRequest.FilterType.HAS_APP;

Now you can add code to the response method to print out your friends' profile photos and links to their profile pages.

function response(data) {
var viewer = data.get("viewer").getData();
var name = viewer.getDisplayName();
var thumb = viewer.getField(opensocial.Person.Field.THUMBNAIL_URL);
var profile = viewer.getField(opensocial.Person.Field.PROFILE_URL);

var html = '<img src="' + thumb + '"/>' +
'<a href="' + profile + '" target="_top">' + name + '</a>';

html += '<hr>';

var viewer_friends = data.get("viewer_friends").getData();
viewer_friends.each(function(person) {
var thumb = person.getField(opensocial.Person.Field.THUMBNAIL_URL);
var profile = person.getField(opensocial.Person.Field.PROFILE_URL);
html += '<a href="' + profile + '" target="_top" style="float:left">' +
'<img src="' + thumb + '"/>' +
'</a>';
});

document.getElementById('dom_handle').innerHTML = html;
// This function changes the height of the module. Make sure you add

// <Require feature="dynamic-height"> to your XML component.
gadgets.window.adjustHeight();

}

The object that contains your friends' data is a collection of Person objects. This example uses the each method to iterate through every Person in the collection.

Now your Application will show all your friends smiling back at you! 

Saving and Retrieving Persistent Data

Most OpenSocial calls involve building a DataRequest object, sending it to the container, and processing the response. Reading and writing Application data is included in this flow. To write a value to a specific key, create a DataRequest and add the result of calling newUpdatePersonAppDataRequest to it:

var req = opensocial.newDataRequest();
req.add(
req.newUpdatePersonAppDataRequest(
opensocial.DataRequest.PersonId.VIEWER,
"myKey",
"myValue"),
"set_data");
req.send(set_callback);

The previous piece of code attempts to save the string myValue under the key of myKey, and the result is stored in set_data. It also specifies a callback function named set_callback that will get a DataResponse object indicating whether the update failed or succeeded.  Note that as of this release, Ning allows a maximum of 10k of serialized data stored, including keys, values and serialization overhead.

A function to handle this response could look something like this:

function set_callback(response) {
if (response.get("set_data").hadError()) {
/* The update failed ... insert code to handle the error */
} else {
/* The update was successful ... continue execution */
}
};

If you would like more information about making DataRequest requests, please check out the OpenSocial API Developers' Guide, http://code.google.com/apis/opensocial/articles/persistence.html or the OpenSocial API reference for more information. To retrieve the value of myKey you must create another DataRequest. This time, include a newFetchPersonAppDataRequest result:

var req = opensocial.newDataRequest();
req.add(
req.newFetchPersonAppDataRequest(
opensocial.DataRequest.PersonId.VIEWER,
"myKey"),
"get_data");
req.send(get_callback);

To specify who you want to retrieve data for, you can pass any of the following as the first parameter to the newFetchPersonAppDataRequest function:

  • opensocial.DataRequest.PersonId.VIEWER
  • opensocial.DataRequest.PersonId.OWNER
  • opensocial.DataRequest.Group.VIEWER_FRIENDS
  • opensocial.DataRequest.Group.OWNER_FRIENDS

The meaning of each of these values will be covered in depth later in this article.

The callback specified by this function gets a DataResponse object back that contains the data that was stored, or an error message if something went wrong. A callback function implementation could look something like this:

function get_callback(response) {
if (response.get("get_data").hadError()) {
/* the fetch failed ... insert code to handle the error */
} else {
var data = response.get("get_data").getData();
/* the fetch was successful ... "data" contains the app data */
}
};

Assuming that the request succeeded, the data variable in the function above will be assigned a JSON object with the following layout:

{
XXXXXXXXXXXXXXXXXXX : { myKey : "<value of myKey for user XXXXXX...>" }
}

where XXXXXXXXXXXXXXXXXXXXX is the ID number of the user who the data belongs to. If your request specifies many people to fetch data for, then the result will look like this:

{
XXXXXXXXXXXXXXXXXXXX : { myKey : "<value of myKey for user XXXXXX...>" },
YYYYYYYYYYYYYYYYYYYY : { myKey : "<value of myKey for user YYYYYY...>" },
ZZZZZZZZZZZZZZZZZZZZ : { myKey : "<value of myKey for user ZZZZZZ...>" }
}

If you request multiple keys for each person, the keys will be scoped to each person's ID in the returned data object:

{
XXXXXXXXXXXXXXXXXXXX : { myKey1 : "<value of myKey1 for user XXXXXX...>",
myKey2 : "<value of myKey2 for user XXXXXX...>" },
YYYYYYYYYYYYYYYYYYYY : { myKey1 : "<value of myKey1 for user YYYYYY...>",
myKey2 : "<value of myKey2 for user YYYYYY...>" },
ZZZZZZZZZZZZZZZZZZZZ : { myKey1 : "<value of myKey1 for user ZZZZZZ...>",
myKey2 : "<value of myKey2 for user ZZZZZZ...>" }
}

The following code snippet is a prototype that gets and saves a piece of persistent data in an OpenSocial Application. Note that the output function is a testing method used with an application such as CodeRunner, and any output function such as alert() can also work:

/**
* DEFAULT CODERUNNER SAMPLE CODE
*/
function get_callback(response) {

// response.get("oViewer").getData().getId();
var userid = response.get("oViewer").getData().getId();

if (response.get("get_data").hadError()) {
/* the fetch failed ... insert code to handle the error */
output("get callback failed");
} else {
var data = response.get("get_data").getData();
output(data[userid]["myKey"]);

/* the fetch was successful ... "data" contains the app data */
output("get callback suceeded");
}
};


function set_callback(response) {
if (response.get("set_data").hadError()) {
/* The update failed ... insert code to handle the error */
output("set callback failed");
} else {
/* The update was successful ... continue execution */
output("set callback succeeded");
get_request();
}
};

function get_request() {
var req = opensocial.newDataRequest();
req.add(req.newFetchPersonRequest(opensocial.DataRequest.PersonId.VIEWER), "oViewer");
req.add(req.newFetchPersonAppDataRequest("VIEWER", "myKey"), "get_data");
req.send(get_callback);
};

function request() {
var req = opensocial.newDataRequest();
req.add(req.newUpdatePersonAppDataRequest("VIEWER", "myKey", "myValue"), "set_data");
req.send(set_callback);
};


// Sets a value, and then gets that same value.
request();

Sending Messages Using requestSendMessage

Ning actively supports OpenSocial's requestSendMessage API, which requests Ning to send a message to a specific user or users. Rather than the application send a standard e-mail, all messages will be directed to a section of the social network's messaging system, called alerts. A dialog box will appear for the OpenSocial Application user to confirm the message.

We previously referenced Google's CodeRunner Application, a way to test OpenSocial APIs in real time. For this example, I am friends with one friend on a social network and ran the following code:

var params = {};
params[opensocial.Message.Field.TYPE] = opensocial.Message.Type.EMAIL;
params[opensocial.Message.Field.TITLE] = "This is my test subject";

var message = opensocial.newMessage("This is a test message with some random text.", params);

// VIEWER, OWNER, VIEWER_FRIENDS, OWNER_FRIENDS or ids as a string or an array.
opensocial.requestSendMessage("VIEWER_FRIENDS", message, function(resp){
if (resp.hadError()) {
// put your error handling code here
alert('There was an error!\nResponse code: '+resp.errorCode_+'\nResponse message: '+resp.errorMessage_);
} else {
// handle successful send message logic here.

}
});

Which brings up the following dialog box:

Once the message has been sent, the person who received the message will receive the message in the alerts portion of their Inbox.

NOTE: The requestSendMessage API has a quota of 35 invocations per member, per day. This means that a member can use the send message functionality 35 times per day across all apps. If a user enters multiple people in the 'to' field, that counts as a single invocation.

Reading from and Writing to a Member's Latest Activity on their Profile Page via the Activity Stream

The following code will let an OpenSocial Application on Ning write to the Latest Activity module on a member's profile page, provided that the Application is installed on your profile page and you are viewing it:

<script type="text/javscript">
function postActivity(text) {
var params = {};
params[opensocial.Activity.Field.TITLE] = text;
var activity = opensocial.newActivity(params);
opensocial.requestCreateActivity(activity, opensocial.CreateActivityPriority.HIGH, callback);
};

function callback(data) {
/* callback is an optional function which is called after requestCreateActivity. */
/* do what you would like with the data attribute. */
document.getElementById("dom_handler").innerHTML = "Latest activity will be shown when you refresh your profile page.";
};

postActivity("This is a sample activity, created at " + new Date().toString());

</script>
<div id="dom_handler"></div>

NOTE: The requestCreateActivity API has a quota of 5 invocations per member, per application, per day. This means that a member can call the functionality five times a day for each application.  New Activity Feed data can only be written to a member's Profile Page, not the main page of a social network.  With the exception of the <a> tag, all HTML is stripped from the text before the API is invoked.

Accessing a REST API via JSON

In order to allow your OpenSocial Application to work with data located on remote servers, you can use a function called makeRequest. makeRequest allows you to get data from remote servers, and send data to remote servers.

Last.fm is an example of a site that uses web services to retrieve metadata, including artist photos. Here's an example of fetching Cher's photo from Last.fm:

<script type="text/javascript">

function request() {
var params = {};
// Content types available at http://code.google.com/apis/opensocial/docs/0.7/reference/gadgets.io.ContentType.html
params[gadgets.io.RequestParameters.CONTENT_TYPE] = gadgets.io.ContentType.JSON;
var url = "http://lastfm-api-ext.appspot.com/2.0/?method=artist.getinfo&outtype=json&api_key=b25b959554ed76058ac220b7b2e0a026&artist=Cher";

gadgets.io.makeRequest(url, response, params);
};

function response(obj) {
/** obj.data contains an object corresponding with the JSON-encoded response **/
output("Cher");
output(obj.data.image_large);
};

request();
</script>

Example of a Ning OpenSocial Application: Weather Sharer

We're now ready to combine all the things we've previously learned into one giant OpenSocial Application.  Weather Sharer is a humble OpenSocial Application that allows the member to enter a zipcode, and then use a Google Weather REST API to display the weather conditions on their profile page. The Application also shows thumbnails of the member's friends who have also installed the Application, with links to their profile pages.  For those who just want a direct link to the code, you will be able to find the XML and JS of this sample application available for download.

Mind you, there are a lot of ways we can improve both the features and the performance of the Application, but this will hopefully inspire you to create a better OpenSocial Application.

First off, we'll create a shell:

<?xml version="1.0" encoding="UTF-8" ?>
<Module>

<ModulePrefs title="It's sunny where I am!">
<Require feature="opensocial-0.7" />
<Require feature="dynamic-height" />
<Require feature="analytics" />

<Require feature="skins" />
</ModulePrefs>
<Content type="html">
<![CDATA[
<script type="text/javascript" src="http://yui.yahooapis.com/combo?2.5.2/build/yahoo-dom-event/yahoo-dom-event.js"></script>

<div id="handler">
<div id="statusbar" style="display:none;"><a id="settings" href="#">Settings</a></div>

<div id="statuspanel" style="display:none;">
<label for="loc">Enter a zip code (like 94110)</label>
<input type="textbox" id="loc">

<input type="submit" id="locbtn">
</div>
<div id="mainpanel"></div>
<div id="friendspanel"></div>

<div id="devonlyfooter"><small><a href="http://os.ning.com/apps/weather/os.js" target="_blank">JS Source Code</a>,
<a href="http://os.ning.com/apps/weather/sunnywhereiam.xml" target="_blank">XML Source Code</a></small></div>

<!-- The following line, along with the appropriate Require tag, integrates Google Analytics. --> <!-- replace the UA-string with your own Google Analytics ID for accurate reporting. -->
<!-- You can also call _IG_Analytics multiple times with different IDs to accurately track different states. -->
<script>
_IG_Analytics("UA-942875-1", "/opensocial-main");
</script>

</div>
]]>
</Content>
</Module>

I'm using the YUI Javascript Library to handle all my Event Handling and DOM manipulation, but feel free to use the Javascript library you're most comfortable with.  You'll also notice the addition of different <Require> feature attributes: dynamic-height, analytics, skins.  These are necessary for different methods used by the Gadget object; anayltics are used with _IG_Analytics for the integration of Google Analytics, skins are used with gadget.skins to dynamically get the visual elements of a users theme.

The first thing I've done is declare the user objects and set up the Settings panel. Because I'm imagining my Application to view someone else's API information and their friends list, I'll be getting the OWNER and OWNER_FRIENDS objects:

getUserObjects: function() {
req.add(req.newFetchPersonRequest(opensocial.DataRequest.PersonId.OWNER), "oOwner");

// This optional parameter filters out friends that do NOT have your application installed
var opt_params = [];
opt_params[opensocial.DataRequest.PeopleRequestFields.FILTER] = opensocial.DataRequest.FilterType.HAS_APP;

req.add(req.newFetchPeopleRequest(opensocial.DataRequest.Group.OWNER_FRIENDS), "oOwnerFriends");
req.send(this.onGetUserObjects);
},

onGetUserObjects: function(resp) {
// No var statement - we want to store this in the class.

objOwner = resp.get("oOwner").getData();
objOwnerFriends = resp.get("oOwnerFriends").getData();
console.log("onGetUserObjects -> objOwnerFriends:");
console.log(objOwnerFriends);

if (objOwner.isViewer()) {
// This uses YUI to create the innerHTML and attach the click handlers to the menu & button.
$D.setStyle("statusbar", "display", "block");
$E.on("settings", "click", me.uiToggleSettings);
$E.on("locbtn", "click", me.setUserLocation);
}

// This was originally in init(). We moved this here because we have to wait for objOwnerFriends to be populated.

me.getUserFriends();
}

The callback function is the onGetUserObjects method, which promptly stores the objects called from the getData method to the objOwner and objOwnerFriends variable inside our class, and we call objOwner.isViewer() - a way to find out if a member is viewing their own Profile Page. If so, we'll make the Settings link available for the member.

We also build out our methods to save and retrieve persistent data.

onSetLocation: function(resp) {
console.log("onSetLocation");
if (resp.get("set_data").hadError()) {
/* The update failed ... insert code to handle the error */
console.log("set error");
} else {
/* The update was successful ... continue execution */
console.log("The update was successful ... continue execution");
}
},

setUserLocation: function() {
// We just set a location in the Settings! We'll set the persistant data, turn the settings off, and move on with our lives.

console.log("setUserLocation");
var locval = $("loc").value;
req.add(req.newUpdatePersonAppDataRequest("VIEWER", "loc", locval), "set_data");
req.send(this.onSetLocation);
me.uiToggleSettings();
me.getUserLocation();
},

The method setUserLocation calls the onSetLocation callback function to save a key/value pair of key "loc". We use a similar pair of methods when it's time for the member to retrieve data, through the form of getUserLocation and onGetLocation:

onGetLocation: function(resp) {
// Every user has a unique user id, different from the Ning ID.
var userid = objOwner.getId();

if (resp.get("get_data").hadError()) {
/* the fetch failed ... insert code to handle the error */
alert("get callback failed");
} else {
var data = resp.get("get_data").getData();

if (data[userid]) {
val = data[userid]["loc"];
me.getAPIFeed(val);

} else {
// alert("Hmmm, looks like we couldn't find you yet. You should add your location in the settings.")

$("mainpanel").innerHTML = "Hmmm, looks like we couldn't find you yet. You should add your location in the settings." } } gadgets.window.adjustHeight(); }, getUserLocation: function() { console.log("getUserLocation");
req.add(req.newFetchPersonAppDataRequest("OWNER", "loc"), "get_data");
req.send(this.onGetLocation);
},

onGetLocation tries to find the information from the persitant data; if it doesn't find any appropriate data, it'll spit out an error - otherwise it goes to the getAPIFeed method where the information is tacked onto a XML REST API, the resulting XML parsed, and put inside the Profile Page module.

onLoadAPI: function(obj) {
console.log("onLoadAPI");
var objXML = obj.data;

// Now we'll create a json structure that we can send as a parameter to uiDrawMainPanel.
// Note that the XML structures are specific for this API.

// A sample XML format is at http://www.google.com/ig/api?weather=94110
// TO DO: Fail safely.
var jsonInfo = {
"city": objXML.getElementsByTagName("city")[0].attributes.getNamedItem("data").value,
"condition": objXML.getElementsByTagName("condition")[0].attributes.getNamedItem("data").value,
"temp_f": objXML.getElementsByTagName("temp_f")[0].attributes.getNamedItem("data").value,
"icon": objXML.getElementsByTagName("icon")[0].attributes.getNamedItem("data").value
}

me.uiDrawMainPanel(jsonInfo);
},

getAPIFeed: function(arg) {
console.log("getAPIFeed of " + arg);
var params = {};
params[gadgets.io.RequestParameters.CONTENT_TYPE] = gadgets.io.ContentType.DOM;
url = "http://www.google.com/ig/api?weather=" + arg;

// We're calling the XML API - the callback is onLoadAPI.
gadgets.io.makeRequest(url, me.onLoadAPI, params);
},

Here is what the code looks like with everything in one place:

<?xml version="1.0" encoding="UTF-8" ?>

<Module>
<ModulePrefs title="Weather Sharer" description="This sample OpenSocial Application prototype lets you share how the weather is where you live with your friends." screenshot="http://os.ning.com/apps/weather/weathersharerscreenshot.png" author="Ernie Hsiung" author_email="ernie@ning.com" author_location="Palo Alto, CA, USA" author_affiliation="Ning, Inc.">
<Require feature="opensocial-0.7" />

<Require feature="dynamic-height" />
<Require feature="analytics" />
<Require feature="skins" />

</ModulePrefs>
<Content type="html">
<![CDATA[
<script type="text/javascript" src="http://yui.yahooapis.com/combo?2.5.2/build/yahoo-dom-event/yahoo-dom-event.js"></script>
<script type="text/javascript">

/***
Note: I'm using a JS library so I don't have to worry about different
methods on multiple browsers. I'm using YUI but of course you can use whatever library you want.

- if not logged in, check if owner has preference and if so load it
- read persistant user data and check for existing user preferences
- if none, ask for preference and save on submit
- if there is data, get the preference, do an API call

- then we get the friends list and pull out their info.

***/


// Not everyone has firebug... if you don't have it, get it at http://getfirebug.com. If you do, console.log logs error messages.
if(!console) {
var console = {}
console.log = function(text){
return } } if (!NING) { var NING = {}; }
NING.openSocial = NING.openSocial || {};
NING.openSocial.Weather = function() {

// These are YUI libraries.

var $D = YAHOO.util.Dom, $E = YAHOO.util.Event, $ = $D.get;
var req = opensocial.newDataRequest();
var val = '';
var objOwner = {};
var objOwnerFriends = {};

return me = {

uiDrawMainPanel: function(obj) {
// loc is the location of the owner - call a REST api and fill in the stuff here.

var html = "In " + obj.city + ", it is " + obj.temp_f + " and " + obj.condition + ". ";
html += "<img src=\"http://www.google.com/" + obj.icon + "\">";

$("mainpanel").innerHTML = html;
gadgets.window.adjustHeight();
},

uiToggleSettings: function() {
// This just toggles the CSS display property
var isVisible = ($D.getStyle("statuspanel", "display") == "block");
$D.setStyle("statuspanel", "display", (isVisible ? "none" : "block"));
gadgets.window.adjustHeight();
},

onLoadAPI: function(obj) {
console.log("onLoadAPI");
var objXML = obj.data;

// Now we'll create a json structure that we can send as a parameter to uiDrawMainPanel.

// Note that the XML structures are specific for this API.
// A sample XML format is at http://www.google.com/ig/api?weather=94110
// TO DO: Fail safely.
var jsonInfo = {
"city": objXML.getElementsByTagName("city")[0].attributes.getNamedItem("data").value,
"condition": objXML.getElementsByTagName("condition")[0].attributes.getNamedItem("data").value,
"temp_f": objXML.getElementsByTagName("temp_f")[0].attributes.getNamedItem("data").value,
"icon": objXML.getElementsByTagName("icon")[0].attributes.getNamedItem("data").value
}

me.uiDrawMainPanel(jsonInfo);
},

getAPIFeed: function(arg) {
console.log("getAPIFeed of " + arg);
var params = {};
params[gadgets.io.RequestParameters.CONTENT_TYPE] = gadgets.io.ContentType.DOM;
url = "http://www.google.com/ig/api?weather=" + arg;

// We're calling the XML API - the callback is onLoadAPI.
gadgets.io.makeRequest(url, me.onLoadAPI, params);
},

onGetLocation: function(resp) {
// Every user has a unique user id, different from the Ning ID.
var userid = objOwner.getId();

if (resp.get("get_data").hadError()) {
/* the fetch failed ... insert code to handle the error */
alert("get callback failed");
} else {
var data = resp.get("get_data").getData();

if (data[userid]) {
val = data[userid]["loc"];
me.getAPIFeed(val);

} else {
var html = "Hmmm, looks like we couldn't find you yet. You should add your location in the settings." $("mainpanel").innerHTML = html;
}
}
gadgets.window.adjustHeight();
},

getUserLocation: function() {
console.log("getUserLocation");
req.add(req.newFetchPersonAppDataRequest("OWNER", "loc"), "get_data");
req.send(this.onGetLocation);
},

onSetLocation: function(resp) {
console.log("onSetLocation");
if (resp.get("set_data").hadError()) {
/* The update failed ... insert code to handle the error */
alert("set error");
} else {
/* The update was successful ... continue execution */
alert("The update was successful ... continue execution");
}
},

setUserLocation: function() {
// We just set a location in the Settings! We'll set the persistant data,

// turn the settings off, and move on with our lives.
console.log("setUserLocation");
var locval = $("loc").value;
req.add(req.newUpdatePersonAppDataRequest("VIEWER", "loc", locval), "set_data");
req.send(this.onSetLocation);
me.uiToggleSettings();
me.getUserLocation();
},

onGetUserObjects: function(resp) {
// No var statement - we want to store this in the class.

objOwner = resp.get("oOwner").getData();
objOwnerFriends = resp.get("oOwnerFriends").getData();
console.log("onGetUserObjects -> objOwnerFriends:");
console.log(objOwnerFriends);

if (objOwner.isViewer()) {
// This uses YUI to create the innerHTML and attach the click handlers to the menu & button.
$D.setStyle("statusbar", "display", "block");
$E.on("settings", "click", me.uiToggleSettings);
$E.on("locbtn", "click", me.setUserLocation);
}

// This was originally in init(). We moved this here because we have to

// wait for objOwnerFriends to be populated.
me.getUserFriends();
},

getUserObjects: function() {
req.add(req.newFetchPersonRequest(opensocial.DataRequest.PersonId.OWNER), "oOwner");

// This optional parameter filters out friends that do NOT have your application installed
var opt_params = [];
opt_params[opensocial.DataRequest.PeopleRequestFields.FILTER] = opensocial.DataRequest.FilterType.HAS_APP;

req.add(req.newFetchPeopleRequest(opensocial.DataRequest.Group.OWNER_FRIENDS), "oOwnerFriends");
req.send(this.onGetUserObjects);
},

getUserFriends: function() {
console.log("getUserFriends");
var html = "";

objOwnerFriends.each(function(person) {
var thumb = person.getField(opensocial.Person.Field.THUMBNAIL_URL);
var profile = person.getField(opensocial.Person.Field.PROFILE_URL);
var nickname = person.getDisplayName();
html += '<a href="' + profile + '" target="_top" style="">' +
'<img src="' + thumb + '" style="border:0; margin-right: 3px; width:32px; height:32px;"/>' +
'</a>';
});

// if (html) { html = '<h3>My friends who have this Application</h3>' + html; }

$("friendspanel").innerHTML = html;
gadgets.window.adjustHeight();
},

init: function() {
// We're setting the CSS attributes of devonlyfooter to
// the default colors of the theme skin. We do this in JS. If you're not using YUI, try the following:

// document.getElementById("element").style.bgColor = gadgets.skins.getProperty(gadgets.skins.Property.BG_COLOR);
$D.setStyle("devonlyfooter", "background-color", gadgets.skins.getProperty(gadgets.skins.Property.BG_COLOR));
$D.setStyle($("devonlyfooter").getElementsByTagName("A"), "color", gadgets.skins.getProperty(gadgets.skins.Property.ANCHOR_COLOR));

this.getUserObjects();
this.getUserLocation();
}
};

// return me;

};

gadgets.util.registerOnLoadHandler(function() {
oW = NING.openSocial.Weather();
oW.init();
});
</script>
<style type="text/css">
#statusbar { background-color:#ccc; border-bottom:1px solid #999; }
#statuspanel { background-color:#eee;border-bottom:1px solid #ccc; font:11px Verdana;display: block; }
#settings { font:11px Verdana; }
#mainpanel { text-align:center; padding:20px 0; background-color:#eef; font:21px Georgia; }
#friendspanel { background-color:#ccc; border-top: 1px solid #999; }
#friendspanel img { border:0; margin: 5px; }
</style>

<div id="handler">
<div id="statusbar" style="display:none;"><a id="settings" href="#">Settings</a></div>

<div id="statuspanel" style="display:none;">
<label for="loc">Enter a zip code (like 94110)</label>
<input type="textbox" id="loc">

<input type="submit" id="locbtn">
</div>
<div id="mainpanel"></div>
<div id="friendspanel"></div>

<div id="devonlyfooter" style="clear:both;">
<small>
<a href="http://os.ning.com/apps/weather/os.js" target="_blank">JS Source Code</a>,

<a href="http://os.ning.com/apps/weather/sunnywhereiam.xml" target="_blank">XML Source Code</a>
</small>
</div>

<!-- The following line, along with the appropriate Require tag, integrates Google Analytics. -->
<!-- replace the UA-string with your own Google Analytics ID for accurate reporting. -->
<!-- You can also call _IG_Analytics multiple times with different IDs to accurately track different states. -->
<script>
_IG_Analytics("UA-942875-1", "/opensocial-main");
</script>

</div>
]]>
</Content>
</Module>

Validating Signed Requests

A xoauth_signature_publickey certificate should now be available to validate signed requests using the makeRequest method, along with oauth_consumer_key, opensocial_owner_id, opensocial_viewer_id and opensocial_app_id parameters.  The certificate is available at http://developer.ning.com/certificates/ning.cert. At this time, OpenSocial Applications on Ning aren't able to consume web services requiring three-legged OAuth authorization yet -- that functionality will come in OpenSocial v0.8. 

In the case of the oauth_consumer_key, Ning differs from other social networking containers in that each social network on Ning maps to unique consumer key.  For example, "examplenetwork.ning.com" will be returned rather than "ning.com".  Also note that domain-mapped social networks on Ning also will point to its corresponding non-mapped domain.  THIS IS SUBJECT TO CHANGE IN FUTURE RELEASES.

The way OpenSocial on Ning handles signed requests is based off of Orkut's methods of validating signed requests, with two major changes:

Last updated by Ernie H. Dec. 2, 2008.

© 2009   Created by Ning Developer Admin

Badges  |  Report an Issue  |  Privacy  |  Terms of Service