MySpace Developer Platform

A Place For Developers

Welcome Developers!

in

Welcome!

in

OpenSocial app in the wild: Dream Interpretation

For the purposes of demoing OpenSocial on MySpace to the SixApart Hackathon, I put together a dream interpretation application. This application is intended to demo a piece of third party functionality integrated into MySpace. People asked for the source and a brief explanation, so we'll put that in this blog post. This isn't exactly a working app--it's more of a bunch of source code that you can use to see various clumps of OpenSocial glue working. It highlights how to make requests to a third party site and combine the results with MySpace friend information.

The application profile is here:

A profile with the app installed is here:

To make this application, I took a pre-existing web site (a dream interpretation site I put together a long time ago), called http://www.enhypniomancy.com. This site has existed over the years in several different languages, on several different hosting companies, and using several different databases. Now it�s written in C#, so you�ll have to follow C# code to get the server stuff. If you've encountered c-style syntax before, it should be fairly easy to extrapolate that code into your server-side language of choice.

In order to work, the Dream Application needs to allow users to save dreams, interpret dreams, and request dreams on behalf of themselves and their friends. To enable this on the server-side, enhypniomancy.com exposes a set of JSON-formatted HTTP calls that encompass the following simple functionality:

  • Get dream (dream id)
  • Get dreams (user id)
  • Get friends� dreams (multiple user ids)
  • Add dream (user id, title, narrative)
  • Add interpretation (dream id, user id, interpretation)
  • Delete dream (dream id)

That functionality is available as a simple set of GET URLs. For example, the URL to get a dream is:


        http://www.enhypniomancy.com/Soc.ashx?command=getdream&dreamid=6
    

Simple enough. For those of you familiar with ASP.NET, the .ashx file is a HttpHandler, which is a simple request/response piping mechanism--functionally similar to a Java servlet. When you look at the source, you�ll see some database glue�I�m not going to release the database glue code because it isn�t relevant.

Server side code (hosted on third party site)

Below is the source for the server-side HttpHandler:

code snippet 1 : Dream App Server-Side C# (hosted on www.enhypniomancy.com
<%@ WebHandler Language="C#" Class="Soc" %> using System; using System.Text; using System.Web; using System.Web.Script.Serialization; using Enhypniomancy.DreamDomain.CollectionClasses; using Enhypniomancy.DreamDomain.EntityClasses; public class JSonDreamResponse { public string ErrorMessage; public bool HadError; public string ResponseMessage; } public class JSonDream { public int DreamId; public string Title; public string Narrative; public string Date; public int Interpretations; public int UserId; public System.Collections.Generic.IList<JSonInterp> Interps = new System.Collections.Generic.List<JSonInterp>(); } public class JSonInterp { public string Interpretation; public int UserId; public string InterpretationDate; } public class JSonFriend { public int UserId; public System.Collections.Generic.IList<JSonDream> Dreams = new System.Collections.Generic.List<JSonDream>(); } public class JSonDreamList : JSonDreamResponse { public JSonDream[] Dreams; } public class JSonFriendDreamList : JSonDreamResponse { public JSonFriend[] Friends; } public class Soc : IHttpHandler { public void ProcessRequest (HttpContext context) { string command = context.Request["command"]; switch(command) { case "getdream" : { GetDream(context); break; } case "getdreams" : { GetDreams(context); break; } case "getfrienddreams" : { GetFriendDreams(context); break; } case "adddream" : { EnterDream(context); break; } case "addinterpretation" : { EnterInterpretation(context); break; } case "deletedream" : { DeleteDream(context); break; } default : { throw new ApplicationException("Command: " + command + " is not supported"); } } context.Response.ContentType = "text/json"; context.Response.Cache.SetExpires(DateTime.Now.AddYears(1)); } private int GetUserId(HttpContext context) { return int.Parse(context.Request["userid"]); } public void GetFriendDreams(HttpContext context) { try { string friendArray = context.Request["friendidlist"]; string[] friendIdStringArray = friendArray.Split(new char[]{','},StringSplitOptions.RemoveEmptyEntries); int[] friendIdIntArray = new int[friendIdStringArray.Length]; for(int i = 0; i < friendIdStringArray.Length; i++) { friendIdIntArray[i] = int.Parse(friendIdStringArray[i]); } SocialDreamCollection sdc = new SocialDreamCollection(); SD.LLBLGen.Pro.ORMSupportClasses.IPredicate predicate = new SD.LLBLGen.Pro.ORMSupportClasses.FieldCompareRangePredicate( Enhypniomancy.DreamDomain.HelperClasses.SocialDreamFields.UserId, friendIdIntArray); sdc.GetMulti(predicate); JSonFriendDreamList friendList = new JSonFriendDreamList(); friendList.Friends = new JSonFriend[friendIdIntArray.Length]; for (int i = 0; i < friendIdIntArray.Length; i++ ) { friendList.Friends[i] = new JSonFriend(); friendList.Friends[i].UserId = friendIdIntArray[i]; } for (int i = 0; i < sdc.Count; i++) { SocialDreamEntity sde = sdc[i]; JSonDream dream = new JSonDream(); MapJSonToSocialDream(dream, sde); for(int j = 0; j < friendIdIntArray.Length; j++) { if(friendList.Friends[j].UserId == sde.UserId) { friendList.Friends[j].Dreams.Add(dream); } } } WriteToOutput(context,friendList); } catch(Exception exc) { WriteError(context,exc.ToString()); } } public void DeleteDream(HttpContext context) { try { SocialDreamEntity sde = new SocialDreamEntity(int.Parse(context.Request["dreamid"])); sde.SocialDreamInterpretation.DeleteMulti(); sde.Delete(); WriteResponse(context,"Successfully deleted dream " + int.Parse(context.Request["dreamid"])); } catch(Exception exc) { WriteError(context,exc.ToString()); } } public void EnterInterpretation(HttpContext context) { try { Enhypniomancy.DreamDomain.EntityClasses.SocialDreamInterpretationEntity sde = new SocialDreamInterpretationEntity(); sde.DreamId = int.Parse(context.Request["dreamid"]); sde.InterpreterUserId = int.Parse(context.Request["userid"]); sde.InterpretationDate = DateTime.Now; sde.Interpretation = context.Request["interpretation"]; sde.Save(); WriteResponse(context,"Saved interpreation for dream " + sde.DreamId + " by interpreter " + sde.InterpreterUserId); } catch (Exception exc) { WriteError(context, exc.ToString()); } } public void EnterDream(HttpContext context) { try { Enhypniomancy.DreamDomain.EntityClasses.SocialDreamEntity sde = new SocialDreamEntity(); int userId = GetUserId(context); string title = context.Request["title"]; string narrative = context.Request["narrative"]; sde.DateCreated = DateTime.Now; sde.UserId = userId; sde.Narrative = narrative; sde.Title = title; sde.Save(); WriteResponse(context,"Successfully saved dream " + sde.DreamId + " with title " + sde.Title + " to user " + userId); } catch(Exception exc) { WriteError(context,exc.ToString()); } } private void WriteToOutput(HttpContext context, object obj) { System.Text.StringBuilder output = new StringBuilder(); System.Web.Script.Serialization.JavaScriptSerializer jss = new JavaScriptSerializer(); jss.Serialize(obj, output); context.Response.Write(output); } private void WriteError(HttpContext context, string message) { JSonDreamResponse e = new JSonDreamResponse(); e.ErrorMessage = message; e.HadError = true; WriteToOutput(context,e); } private void WriteResponse(HttpContext context,string message) { JSonDreamResponse jdr = new JSonDreamResponse(); jdr.ResponseMessage = message; jdr.HadError = false; WriteToOutput(context,jdr); } public void GetDream(HttpContext context) { try { SocialDreamEntity sde = new SocialDreamEntity(int.Parse(context.Request["dreamid"])); JSonDreamList jdl = new JSonDreamList(); jdl.Dreams = new JSonDream[1]; JSonDream jdr = new JSonDream(); MapJSonToSocialDream(jdr,sde); SocialDreamInterpretationCollection sdic = new SocialDreamInterpretationCollection(); sdic.GetMulti((Enhypniomancy.DreamDomain.HelperClasses.SocialDreamInterpretationFields.DreamId == jdr.DreamId)); foreach(SocialDreamInterpretationEntity sdie in sdic) { JSonInterp jsi = new JSonInterp(); jsi.Interpretation = sdie.Interpretation; jsi.InterpretationDate = sdie.InterpretationDate.ToShortDateString(); jsi.UserId = sdie.InterpreterUserId; jdr.Interps.Add(jsi); } jdl.Dreams[0] = jdr; WriteToOutput(context,jdl); } catch(Exception exc) { WriteError(context,exc.ToString()); } } private void MapJSonToSocialDream(JSonDream dream, SocialDreamEntity sde) { dream.Date = sde.DateCreated.ToShortDateString(); dream.Narrative = sde.Narrative; dream.DreamId = sde.DreamId; dream.Title = sde.Title; dream.UserId = sde.UserId; dream.Interpretations = sde.SocialDreamInterpretation.Count; } public void GetDreams(HttpContext context) { try { int userId = int.Parse(context.Request["userid"]); Enhypniomancy.DreamDomain.CollectionClasses.SocialDreamCollection sdc = new SocialDreamCollection(); SD.LLBLGen.Pro.ORMSupportClasses.ISortExpression ise = new SD.LLBLGen.Pro.ORMSupportClasses.SortExpression( (Enhypniomancy.DreamDomain.HelperClasses.SocialDreamFields.DateCreated | SD.LLBLGen.Pro.ORMSupportClasses.SortOperator.Descending)); sdc.GetMulti( (Enhypniomancy.DreamDomain.HelperClasses.SocialDreamFields.UserId == userId),15, ise); JSonDreamList jdl = new JSonDreamList(); jdl.Dreams = new JSonDream[sdc.Count]; for(int i = 0; i < sdc.Count; i++) { JSonDream dream = new JSonDream(); SocialDreamEntity sde = sdc[i]; MapJSonToSocialDream(dream, sde); jdl.Dreams[i] = dream; } WriteToOutput(context, jdl); } catch(Exception exc) { WriteError(context,exc.ToString()); } } public bool IsReusable { get { return false; } } }

The handler uses the Microsoft ASP.NET AJAX Framework to serialize objects into JSON. You'll notice the complete lack of security. People could do anything on behalf of anyone else by munging requests. For this to be a secure application, I would enable OAuth support on the server side. As soon as I have that done I'll update this blog post, but this article is really focused more on the client--JavaScript using the OpenSocial API.

OpenSocial JavaScript code on MySpace

Now on to the JavaScript! I�m not trying to wow anyone here with my JavaScript abilities. I do like my little �renderView� system, which takes a set of divs and switches them on or off depending on what view you want to render, but it�s been done better, I�m sure. Unfortunately (or fortunately, depending on your leanings), the app only works on FireFox. If I get it working on IE I�ll send along an update. Below is the entirety of the application (you could cut and paste this into an app of your own and it would work):

working app 1 : Dream App JavaScript Source
<style type="text/css"> body { margin: 0px; background-color: black; } #ui { background-position: bottom; background-color: #000000; width: 430px; height: 430px; color: #FFFFFF; background-image: url(http://www.enhypniomancy.com/images/DreamsTitle.jpg); background-repeat: no-repeat; } #nav { background-color: #000066; border-bottom-color: #FF0000; border-bottom-width: thin; border-bottom-style: dashed; } a { color: #FF00FF; cursor: pointer; } a:active { color: #FF0000; } a:hover { background-color: #00FFFF; } .view_Visible { width: 100%; height: 100%; display: inline; word-wrap: normal; } .view_Invisible { display: none; } #canvas { overflow: auto; border: thin solid #800000; height: 85%; margin: 15px; padding: 5px; } #Text2 { height: 177px; width: 373px; } textarea { height: 68px; width: 375px; } span { font-size: small; } pre { font-family: Arial; font-size: small; font-style: italic; } </style> <div id="ui"> <div id="nav"> <a title="Dreams" onclick="renderView('dreams')">Dreams</a> | <a title="Enter Dream" onclick="renderView('newdream');">Enter Dream</a> | <a onclick="renderView('friends')">Friends' Dreams</a> | <a onclick="renderView('search');">Search</a> | <a onclick="renderView('help');">Help!</a> </div> <div id="canvas"> <div id="dreams" class="view_Invisible"> <div id="lastDreams"> </div> </div> <div id="friends" class="view_Invisible"> Friends </div> <div id="view" class="view_Invisible"> View dream </div> <div id="interpret" class="view_Invisible"> Interpret dream <br/> <p> Title: <span id="interpTitle"></span> <br/> Narrative: <br/> <span id="interpNarrative"></span> </p> <p> Your interpretation: <br/> <textarea id="interpInput"> </textarea> <br/> <input type="button" value="Submit your interpretation" onclick="addInterp()" id="interpButton"/> </p> </div> <div id="search" class="view_Invisible"> Search </div> <div id="help" class="view_Visible"> Welcome to the land of dream interpretations! <br/> <br/> You can use this application to journal your dreams, view your friends' dreams, and interpret your friends' dreams. <br/> <br/> You can also search through your and your friends' dreams. Ever wonder if themes are floating between you and your friends' dreams? Hmm? <br/> <br/> Thought so. </div> <div id="newdream" class="view_Invisible"> Enter a new dream into your journal: <br/> Title: <br/> <input id="dreamTitle" type="text"/> <br/> Narrative: <br/> <textarea id="dreamNarrative"> </textarea> <br/> <br/> <input id="button_EnterDream" type="button" value="Enter dream..." onclick="enterDream()"/> </div> <div id="waiting" class="view_Invisible"> <blink> Working... </blink> </div> </div> <div id="error" class="view_Invisible"> Errors: <br/> </div> </div> <script type="text/javascript"> //####Helper functions#### function showError(errorMessage){ renderView("error"); var errorDiv = document.getElementById("error"); errorDiv.innerHTML += errorMessage + "<br />"; } //####Globals and class definitions#### User.prototype.getDisplayHtml = function(){ var response = "<img width=\"45px\" src=\"" + this.UserThumbnail + "\" /><br /><a href=\"" + this.UserProfileURL + "\">" + this.UserName + "</a><br />"; return response; } var currentUserId = 0; var friendIdList = new Array(); var friendDictionary = {}; var currentUser; var friendList = {}; var viewerId; function User(userId, userName, userThumnail, userProfileURL){ this.UserId = userId; this.UserName = userName; this.UserThumbnail = userThumnail; this.UserProfileURL = userProfileURL; } function init(){ renderView("working"); populateUserData(); } function populateUserData(){ //1: Create the DataRequest. var dataRequest = opensocial.newDataRequest(); //2: Prepare RequestItems to be added to the DataRequest var friendRequest = dataRequest.newFetchPeopleRequest( opensocial.DataRequest.Group.OWNER_FRIENDS); var personRequest = dataRequest.newFetchPersonRequest( opensocial.DataRequest.PersonId.OWNER); var viewerRequest = dataRequest.newFetchPersonRequest( opensocial.DataRequest.PersonId.VIEWER); //3: Add the RequestItems to the DataRequest. dataRequest.add(friendRequest); dataRequest.add(personRequest); //4: Send it along, and expect a function called responseCallback(dataResponse) to be executed when it's done. dataRequest.send(populateUserData_Callback); } function populateUserData_Callback(dataResponse){ var errorHappened = dataResponse.hadError(); if (!errorHappened) { var friendsData = dataResponse.get( opensocial.DataRequest.Group.OWNER_FRIENDS).getData(); var ownerData = dataResponse.get( opensocial.DataRequest.PersonId.OWNER).getData(); var viewer = dataResponse.get( opensocial.DataRequest.PersonId.VIEWER); if (viewer != null) { var viewerData = dataResponse.get( opensocial.DataRequest.PersonId.VIEWER).getData(); viewerId = viewerData.getField(opensocial.Person.Field.ID); } else { viewerId = ownerData.getField(opensocial.Person.Field.ID); } var ownerName = ownerData.getField(opensocial.Person.Field.NAME); var ownerId = ownerData.getField(opensocial.Person.Field.ID); var ownerURL = ownerData.getField(opensocial.Person.Field.PROFILE_URL); var ownerImage = ownerData.getField(opensocial.Person.Field.THUMBNAIL_URL); var ownerObj = new User(ownerId, ownerName, ownerImage, ownerURL); currentUser = ownerObj; currentUserId = ownerId; friendsData.each(function(friendData){ var friendName = friendData.getField(opensocial.Person.Field.NAME); var friendThumbnailUrl = friendData.getField(opensocial.Person.Field.THUMBNAIL_URL); var friendId = friendData.getField(opensocial.Person.Field.ID); var friendURL = friendData.getField(opensocial.Person.Field.PROFILE_URL); var friendObj = new User(friendId, friendName, friendThumbnailUrl, friendURL); friendDictionary[friendId] = friendObj; friendIdList.push(friendId); }); //Let's shove the current user into the "friendDictionary" so we can look him/her up without silly if login friendDictionary[currentUserId] = currentUser; renderView("help"); } else { showError("Error on dataresponse"); } } //End OpenSocial integration point //View management code //Views assume that there is a <div> whose ID corresponds to the name of the view var currentView = "dreams"; var views = new Array("dreams", "friends", "search", "help", "newdream", "view", "interpret", "waiting"); function switchOnWaitingView(message){ for (var i = 0; i < views.length; i++) { var view = views[i]; document.getElementById(view).setAttribute("class", "view_Invisible"); } var waitingDiv = document.getElementById("waiting") waitingDiv.innerHTML = "<blink>" + message + "</blink>"; waitingDiv.setAttribute("class", "view_Visible"); } function switchOffWaitingView(){ var waitingDiv = document.getElementById("waiting") waitingDiv.setAttribute(