PDA

View Full Version : Integrating View/UpdateManager with another JSON source



evan.leonard
27 Dec 2006, 7:46 AM
Hello Jack and All,

I am using the new Image Chooser example to learn about how to use the JsonView and UpdateManager classes to dynamically retrieve data.

Following the example seems pretty straight forward, however in my environment we use json-rpc-java on the server-side to process asyncronous requests. I am interested in trying to tie together the JsonView with the json-rpc-java backend.

I've attempted to do this by making a subclass of UpdateManager with an overriden update method, and a subclass of JsonView that uses this update manager. However, this seems to be more difficult as I anticipated because of the many places that the UpdateManager relies on the transaction being a YAHOO.util.Connect object.

Can you suggest a better insertion point into the View object at which to take control of the asyncronous request? It seems that it should be possible to do. The only differences at the interface level should be supplying a methodName instead of a url, and a param array instead of an object. The output of the async call would be a json object.

Below is the code for as far as I got before deciding to ask for help.

RpcUpdateManager.js:


/**
* @param {String/HTMLElement/YAHOO.ext.Element} el The element to update
* @param {Boolean} forceNew (optional) By default the constructor checks to see if the passed element already has an UpdateManager and if it does it returns the same instance. This will skip that check (useful for extending this class).
*/
RpcUpdateManager = function(el, forceNew)
{
RpcUpdateManager.superclass.constructor.call(this, el, forceNew);
}

YAHOO.extendX(RpcUpdateManager, YAHOO.ext.UpdateManager, {

update : function(methodName, params, callback)
{
if(this.beforeUpdate.fireDirect(this.el) !== false){
if(typeof methodName == 'object'){ // must be config object
var cfg = methodName;
methodName = cfg.method;
params = params || cfg.params;
callback = callback || cfg.callback;
if(callback && cfg.scope){
callback = callback.createDelegate(cfg.scope);
}
if(typeof cfg.nocache != 'undefined'){this.disableCaching = cfg.nocache};
if(typeof cfg.text != 'undefined'){this.indicatorText = '<div class="loading-indicator">'+cfg.text+'</div>'};
if(typeof cfg.scripts != 'undefined'){this.loadScripts = cfg.scripts};
if(typeof cfg.timeout != 'undefined'){this.timeout = cfg.timeout};
}
this.showLoading();

if(typeof params == 'function'){
params = params();
}

this.transaction = new RpcClient();

if(callback == null) callback = function(){};
var req = this.transaction.makeRequest.call(this.transaction, methodName, params, callback);
JSONRpcClient.async_requests.push(req);
JSONRpcClient.kick_async();

}
},

/**
* Returns true if an update is in progress
* @return {Boolean}
*/
isUpdating : function(){
if(this.transaction){
JSONRpcClient.num_req_active > 0;
}
return false;
}
});

RpcView.js:


RpcView = function(container, tpl, config)
{
// Force the update manager to be our own instance.
this.el = getEl(container, true);
this.el.updateManager = new RpcUpdateManager(this.el, this);

// Call the super's constructor
RpcView.superclass.constructor.call(this, container, tpl, config);
}

YAHOO.extendX(RpcView, YAHOO.ext.JsonView, {

/**
* Performs an async request, loading the JSON from the response. If params are specified it uses POST, otherwise it uses GET.
* @param {String/Function} methodName The url for this request or a function to call to get the url or a config object containing any of the following options:
* @param {Array/Function} params (optional) The parameters to pass to the method on the server
* @param {Function} callback (optional) The method to call with the results upon successful completion.
*
<pre><code>
view.load({
methodName: 'ObjectName.methodName',

params: {param1: 'foo', param2: 'bar'}, // or a URL encoded string

callback: yourFunction,

nocache: false,

text: 'Loading...',

timeout: 30,

scripts: false

});
</code></pre>
* The only required property is methodName. The optional properties nocache, text and scripts
* are shorthand for disableCaching, indicatorText and loadScripts and are used to set their associated property on this UpdateManager instance.
*/
load : function(methodName, params, callback){
var um = this.el.getUpdateManager();
um.update.apply(um, arguments);
},

render : function(el, response){
this.clearSelections();
this.el.update('');
var o;
try{
o = YAHOO.ext.util.JSON.decode(response.responseText);
if(this.jsonRoot){
o = eval('o.' + this.jsonRoot);
}
}catch(e){}
/**
* The current json data or null
*/
this.jsonData = o;
this.beforeRender();
this.refresh();
}

});

jack.slocum
28 Dec 2006, 4:01 AM
I don't see where the Rpc code returns the JSON object?

You may be better off overridding the load method on the JSonView class directly and skipping the update manager since it seems to duplicate some functionality.

Once your data comes back from Rpc, you can call call your overridden render method passing in the jsonData object (instead of evaling it).

evan.leonard
28 Dec 2006, 11:57 AM
You're right in not seeing where the json object gets returned... it wasn't quite there yet. Sorry about the incomplete/confusing code. As for skipping the update manager, that makes sense. I guess the eventing that i'm interested in is mostly on the view. For now i've created an alternative method of returning json in a way that's compatible with the existing implementation. However, the java to json serializer i'm using serialized data in a way that still isn't exactly what the Yahoo.ext.Dom.Template was expecting. Take a look:


{"list":[{"javaClass":"com.mindreef.user.User","user":{"title":null,"emailAddress":null,"username":"admin",
"status":1,"firstName":null,"id":32768,"organization":null,"lastName":null}},{"javaClass":"com.mindreef.user.User",
"user":{"title":null,"emailAddress":null,"username":"guest","status":1,"firstName":null,"id":32769,"organization":null,
"lastName":null}}],"javaClass":"java.util.ArrayList"}

Notice the "javaClass" type hint next to the actual data object inside of the "list" array. This caused a problem using templates. I worked around this by extending my template instance with a new applyTemplate method that supports dotted properties like "user.username". Here it is:


re : /\{([\w\.]+)\}/g,

applyTemplate:function(values)
{
var fn=function(match,index){
return YAHOO.util.Lang.getProperty(values, index);
}

return this.html.replace(re,fn);
},

Where i've defined YAHOO.util.Lang.getProperty to be:


getProperty : function(object, property)
{
var dot = (String(property)).indexOf('.');

if(dot > 0) {
return Mindreef.util.Lang.getProperty(object[property.substring(0,dot)], property.substring(dot+1))
}else if(typeof object[property]!='undefined'){
return object[property];
}else{
return '';
}
}

To get filtering to work again as in the ImageChooser example I had to override filter to use the new getProperty method as well. I didn't take the time to figure out how to generate a compiled template using the getProperty method though. If you'd like to integrate this into the Template class so it supports dotted properties that'd be great. I ran into a couple possible bugs too, but i'll open a new topic for those.

Oh, also, may I ask why you had the 'empty' variable defined in the applyTemplate function, instead of just returning ''? Is it that much more efficient?


Thanks again!
Evan