Sunday, August 17, 2008

SMashups - PART III

SMashups: Scalable, Server-less, Social Mashups for Web 2.0
Part III: Restoring Context:
Building a Simple OpenSocial XMLHTTPRequest Object
---------------------------------------------------------------------

let's skip all the pontification, and get right to the code. what we are going to attempt to do is build our own simple version of xmlhttprequest, we'll call it nolyXMLHTTPGet, and we'll keep it simple to highlight the main issue of this article, specifically with regard to opensocial api. with that in mind, consider the following object:



function nolyXMLHTTPGet(url, onSuccess, onError, params){
//initialize parameters
this.url = url;
this.onSuccess = onSuccess || function(response){return true;};
this.onError = onError || function(response){return false;};
this.response = null;
this.incall = true;

var _params={};
_params[gadgets.io.RequestParameters.METHOD] = gadgets.io.MethodType.GET;
_params[gadgets.io.RequestParameters.CONTENT_TYPE]=gadgets.io.ContentType.DOM;


//call makeRequest, passing in __internalHandler as the callback
gadgets.io.makeRequest(url, this.__internalHandler, _params);

//this is the internal handler
this.__internalHandler = function(response){
//flag that we are no longer in call
this.incall = false;
this.response = response;
//process response
if(response.errors.length > 0){
this.onError(this);
}
else{
this.onSuccess(this);
}
}
}




so now we have a very simple xmlhttprequest object that tries to wrap the makerequest, and make it a bit more friendly. in this example, the constructor accepts a url, onSuccess and onError handlers, and parameters. besides the event handlers, the object has four properties: url, params, response, and incall (to signal that the object has a call open and is awaiting a response from opensocial). the object also has one '__internalHandler' function, which is used to detect an error or success and make the approrpriate callback. also notice that the callback handlers both pass references to 'this', the instance of the object.



the logic is simple, but for the newbies who may be lurking, i'll give at least a summary explanation. the object first initializes some internal properties based upon the input paramaters, flags that it is in a call, and then invokes gadgets.io.makerequest. note that the callback is to the internal function, which allows us to turn off the incall flag, load the response into an internal property, and then inspect the response to raise the appropriate callback handler (error or success). also note that we pass the instance object along when we invoke the outer event handler.



elegant, wouldn't you say?



...only problem is, it doesn't work. the problem is that when the makeRequest returns, the system cannot even find this.__internalHandler because 'this' is all out of context on the return call. in point of fact, 'this' actually points to the iframe that contains most of the OpenSocial container's code. so context is definately all out of whack. but, this is where we just take a slightly more complex, but assuredly successful approach. we're going to preserve context ourselves :) i wish i could remember where i learned this trick because i'd like to cite them, but i didn't take notes on everything in the earliest days of my development, i was just trying to get this stuff to work.



this is the vodoo that we didn't have to do with ajax...so pay close attention newbies :)



function nolyXMLHTTPGet(url, onSuccess, onError){
//set parameters for this object
this.url = url;
this.onSuccess = onSuccess || function(context){return true;};
this.onError = onError || function(context){return false;};
this.response = null;
this.incall = true;

var _params={};
_params[gadgets.io.RequestParameters.METHOD] = gadgets.io.MethodType.GET;
_params[gadgets.io.RequestParameters.CONTENT_TYPE]=gadgets.io.ContentType.DOM;


//encapsulate 'this' object for context restore
var _context = this;

//create a virtual handler function variable
var _virtualhandler = function(response){
//inside virtual handler, calls instance handler
_context.__internalHandler(response,_context);
}; //note this semi-colon, which is required (easy to forget)

//invoke makeRequest, passing in virtual handler for callback
gadgets.io.makeRequest(url, _virtualhandler, _params);

//now we have an internal handler that accepts response and _context
this.__internalHandler = function(response, _context){
//this is just for fun
var _pop = _context;
//flag that we are not in a call
_context.incall = false;
_context.response = response;
//process response
if(response.errors.length > 0){
//call onError event handler
_context.onError(_context);
}
else{
//call onSuccess event handler
_context.onSuccess(_context);
}
}
}



the comments pretty much explain what's going on. essentially, because we have absolutely no real context when we return, we simply pass the context along by wrapping a more powerful callback inside our new virtual function. therefore, when the makeRequest returns and calls the virtual handler, we can restore context internally--instead of using the 'this' keyword, we are using '_context', which holds a pointer to the original instance object where the call was initiated. even if we initialize and run 10 of these one at a time, and they all come back at different times, the context helps us to make sure it all gets sorted out and values assigned to the proper instance objects.



so, how do we use this new object? consider the following code:



function main(){
var url = "http://www.abc.com/api?p1=1&p2=2";
var osh = onSuccessHandler;
var oeh = onErrorHandler;
var myHTTP = new nolyHTTPGet(url, osh, oeh);
myHTTP.onSuccess = doOnsuccess;
myHTTP.onError = doOnError;
}
function doOnSuccess(_context){
alert(_context.response.data.xml);
}
function doOnError(_context){
alert(_context.response.errors[0]);
}



But what have we gained? still, our code does not look so much different from the first lines of code we wrote using makeRequest directly. granted, we do have onSuccess and onError now, which is really helpful, but to make this a really powerful object, and easy to use, we'll need to come up with a much more comprehensive object model... for instance, consider the following class as an example (please note that methods with two underscores __ are internal only and use context):



function nolyXMLHTTP(url, opt_onSuccess, opt_onFailure, opt_onTimeout, opt_timeoutms){
this.name = name;
this.url = url;
this.onSuccess = opt_onSuccess || function(_context){return true;};
this.onError = opt_onError || function(_context){return false;};
this.onTimeout = opt_onTimeout || function(_context){return false;};
this.timeoutms = opt_timeoutms || 10000; // 10 seconds
this.params = {};
this.refreshInterval = 1000; //refresh every second
this.GetXML = function(opt_onSuccess, opt_onError, opt_onTimeout, opt_timeoutms){
this.onSuccess = opt_onSuccess || this.onSuccess || function(_context){return true;};
this.onError = opt_onError || this.onError || function(_context){return false;};
this.onTimeout = opt_onTimeout || this.onTimeout || function(_context){return false;};
this.timeoutms = opt_timeoutms || this.timeoutms || 10000; // 10 seconds
var _params={};
_params[gadgets.io.RequestParameters.METHOD] = gadgets.io.MethodType.GET;
_params[gadgets.io.RequestParameters.CONTENT_TYPE]=gadgets.io.ContentType.DOM;
this.params = _params;
this.__makeCachedRequest();
}
this.postXML = function(postdata, opt_onSuccess, opt_onError, opt_onTimeout, opt_timeoutms){
this.onSuccess = opt_onSuccess || this.onSuccess || function(_context){return true;};
this.onError = opt_onError || this.onError || function(_context){return false;};
this.onTimeout = opt_onTimeout || this.onTimeout || function(_context){return false;};
this.timeoutms = opt_timeoutms || this.timeoutms || 10000; // 10 seconds
var _params={};
_params[gadgets.io.RequestParameters.METHOD] = gadgets.io.MethodType.POST;
_params[gadgets.io.RequestParameters.POST_DATA] = postdata;
_params[gadgets.io.RequestParameters.CONTENT_TYPE] = gadgets.io.ContentType.DOM;
this.params = _params;
this.__makeCachedRequest();
}
this.__makeCachedRequest = function() {
var ts = new Date().getTime();
var sep = "?";
if (this.refreshInterval && this.refreshInterval > 0) {
ts = Math.floor(ts / (this.refreshInterval * 1000));
}
if (this.url.indexOf("?") > -1) {
sep = "&";
}
this._new_cached_url = [ this.url, sep, "nocache=", ts ].join("");

var _context = this;
var callback = function(response){_context.__internalHandler(response,_context);};

var _ontimeout = function(){_context.__timeoutMonitor(_context);};
this.inCall = true;
setTimeout(_ontimeout, _context.timeouttms);
gadgets.io.makeRequest(_context._new_cached_url, callback, _context.params);
}
this.__internalHandler = function(response, _context){
if(!context.inCall){
_context.response = response;
return;
}
_context.inCall = false;
_context.response = response;
if(response.errors.length > 0){
_context.onError(_context);
return false;
}
else{
_context.onSuccess(_context);
return true;
}
}
this.__timeoutMonitor = function(_context){
if(_context.inCall){
_context.onTimeout(_context);
}
}
}



Having this object allows us to simplify our code for handling request, making the situation similar to using the standard XMLHTTPRequest object. For instance, we could do the following:



function main(){
var _url = "http://www.rhapsody.com/api/somecall=1";
var _timeoutms = 5000;
var _onSuccess = function(_context){
alert(_context.response.data.xml);
return true;
};
var _onFailure = function(_context){
alert(_context.errors[0]);
return false;
};
var _onTimeout = function(_context){
alert("Timeout for " + _context.url);
return false;
};
var aCall = new nolyXMLHTTP(_url)
var aCall.GetXML(_onSuccess, _onFailure, _onTimeout, _timeoutms);
}



Of course, we can still expand upon the object and make it a bit more flexible. For instance, we could create a method that allows us to pass in an XSL document and an output DIV object and do a 'FetchAndTransform" that would make this very easy to use. I'm going to create a new open-source project on google code so that we can start building out this object. but for now, i'll end part iii here, and get started with part iv of the series.



END OF PART III

No comments:

Post a Comment

considerate others please