window.jQuery = window.jQuery || {};
jQuery.rpc = function(url, dataType, onLoadCallback, version, methods) {
return new (function(url, dataType, onLoadCallback, version) {
version = version || "1.0";
dataType = dataType || "json";
if(dataType != "json" && dataType != "xml") {
new Error("IllegalArgument: Unsupported data type");
}
var _self = this;
function pad2(f) {
if(f<=9) {
return "0"+f;
} else {
return ""+f;
}
}
var serializeToXml = function(data) {
switch (typeof data) {
case 'boolean':
return '<boolean>'+ ((data) ? '1' : '0') +'</boolean>';
case 'number':
var parsed = parseInt(data);
if(parsed == data) {
return '<int>'+ data +'</int>';
}
return '<double>'+ data +'</double>';
case 'string':
return '<string>'+ data +'</string>';
case 'object':
if(data instanceof Date) {
return '<dateTime.iso8601>'+ data.getFullYear() + pad2(data.getMonth()) + pad2(data.getDate()) +'T'+ pad2(data.getHours()) +':'+ pad2(data.getMinutes()) +':'+ pad2(data.getSeconds()) +'</dateTime.iso8601>';
} else if(data instanceof Array) {
var ret = '<array><data>'+"\n";
for (var i=0; i < data.length; i++) {
ret += ' <value>'+ serializeToXml(data[i]) +"</value>\n";
}
ret += '</data></array>';
return ret;
} else {
var ret = '<struct>'+"\n";
jQuery.each(data, function(key, value) {
ret += " <member><name>"+ key +"</name><value>";
ret += serializeToXml(value) +"</value></member>\n";
});
ret += '</struct>';
return ret;
}
}
}
var xmlRpc = function(method, params) {
var ret = '<?xml version="'+version+'"?><methodCall><methodName>'+method+'</methodName><params>';
for(var i=0; i<params.length; i++) {
ret += "<param><value>"+serializeToXml(params[i])+"</value></param>";
}
ret += "</params></methodCall>";
return ret;
}
var parseXmlValue = function(node) {
var jnode = jQuery(node);
childs = jnode.children();
// String not enclosed in a <string> tag
if(childs.length==0) {
var s="";
for(var j=0; j<jnode[0].childNodes.length;j++) {
s+=new String(jnode[0].childNodes[j].nodeValue);
}
return s;
}
for(var i=0; i < childs.length; i++) {
switch(childs[i].tagName) {
case 'boolean':
return (jQuery(childs[i]).text() == 1);
case 'int':
case 'i4':
return parseInt(jQuery(childs[i]).text());
case 'double':
return parseFloat(jQuery(childs[i]).text());
case "string":
return jQuery(childs[i]).text();
case "array":
var ret = [];
jQuery("> data > value", childs[i]).each(
function() {
ret.push(parseXmlValue(this));
}
);
return ret;
case "struct":
var ret = {};
jQuery("> member", childs[i]).each(
function() {
ret[jQuery( "> name", this).text()] = parseXmlValue(jQuery("value", this));
}
);
return ret;
case "dateTime.iso8601":
/* TODO: fill me :( */
return 0;
}
}
}
var parseXmlResponse = function(data) {
var ret = {};
ret.version = version;
jQuery("methodResponse params param > value", data).each(
function(index) {
ret.result = parseXmlValue(this);
}
);
jQuery("methodResponse fault > value", data).each(
function(index) {
ret.error = parseXmlValue(this);
}
);
return ret;
}
var rpc_contents = {
'xml':'text/xml'
,'json':'application/json'
};
var _rpc = function(method) {
var params = [];
var async; // whether we're async or not
var callback;
var nargs=arguments.length;
if(nargs > 1 && typeof(arguments[nargs-1])=="function") {
callback = arguments[nargs-1];
nargs=nargs-1;
async=true;
} else {
async=false;
}
for (var i=1; i<nargs; i++) {
params.push(arguments[i]);
}
var data;
if(dataType == 'json') {
data = {"version":version, "method":method, "params":params};
} else {
data = xmlRpc(method, params);
}
if(async) {
jQuery.ajax({
url: url,
dataType: dataType,
type: "POST",
data: data,
success: function(inp) {
var json = inp;
if(dataType == "xml") {
json = parseXmlResponse(inp);
}
// console.log("json response:", json);
callback(json);
},
processData: false,
contentType: rpc_contents[dataType]});
} else {
var result = jQuery.ajax({
url: url,
dataType: dataType,
type: "POST",
data: data,
async: false,
contentType: rpc_contents[dataType]});
if(dataType=="json") {
return eval("("+result.responseText+")");
} else {
var myresult=parseXmlResponse(result.responseXML);
return myresult;
}
}
};
function populate(methods) {
// console.log(json);
/* get the functions */
var proc = null;
for(var i = 0; i<methods.length; i++) {
proc = methods[i];
var obj = _self;
var objStack = proc.split(/\./);
for(var j = 0; j < (objStack.length - 1); j++){
obj[objStack[j]] = obj[objStack[j]] || {};
obj = obj[objStack[j]];
}
/* add the new procedure */
obj[objStack[j]] = (
function(method, obj) {
var _outer = {"method":method,"rpc":_rpc};
return function() {
var params = [];
params.push(_outer.method);
for (var i=0; i<arguments.length; i++) {
params.push(arguments[i]);
}
return _rpc.apply(_self, params);
}
}
)(proc, _rpc);
}
// console.log('Load was performed.');
}
function asyncCallback(json) {
populate(json.result);
if(onLoadCallback) {
onLoadCallback(_self);
}
}
if(!methods) {
_rpc("system.listMethods", asyncCallback);
} else {
populate(methods);
if(onLoadCallback) {
// not sure this works - the intention is to call the callback as soon as this
// javascript function returns control back to the browser, to emulate the behaviour
// that the system.listMethods approach
setTimeout(function(){onLoadCallback(_self);},0);
}
}
})(url, dataType, onLoadCallback, version);
};