js does xmlrpc
Today I explored some new (to me) technologies to support a feature I was playing with. On a complex form representing a row being edited in a database, I had several 1-to-many relationships that I wanted to edit (i.e. make additions to) without submitting the main form. It is an intranet application, so I have the ability to rely on the fact that users will be using IE, and js will be enabled.
From the usual bag of tricks, my initial thought was you could have javascript submit a hidden form in a non-visible frame, and have the results come back with some onLoad() js that would update the main form (even did a little flow diagram, will attach at the end of the post). But I remembered Harry mentioning using js and php to communicate by serializing data in mutually compatible formats. This is the scriptserver project which he blogged about a while back. Harry also sent me a link to this js library to implement xmlrpc.
I have to admit that I have not played around with xmlrpc to date (at least in terms of actually doing anything with the technology, I have read enough to have the gist of xmlrpc and SOAP). This is mainly because I kept asking my Unix admin at work to compile php with the xmlrpc extension, and he always complained that he could not get the extension to compiled on HP-UX. I have finally started to push for some Linux web servers at work, so that will not persist as an issue in the future. Anyway, I decided to play around with this javascript xmlrpc library and see what I could come up with.
If you have stuck with me this long, then My first step was to try to get a php xmlrpc server up and running. With a fresh php 5.0.1 binary with xmlrpc installed, I hacked out this code for a test server:
<?php
error_reporting(E_ALL|E_STRICT);
$server_handle = xmlrpc_server_create();
xmlrpc_server_register_method(
$server_handle
,'uname'
,'xmlrpc_uname');
header('Content-Type: text/xml');
echo xmlrpc_server_call_method($server_handle, $HTTP_RAW_POST_DATA, null);
xmlrpc_server_destroy($server_handle);
function xmlrpc_uname($method, $parms) {
return array(
'info' => `uname -a`
,'input' => var_export(array($method,$parms),true));
}
?>
and this for a client (in php first, have to stick with what you know 😉 ):
<?php
error_reporting(E_ALL);
require_once 'XML/RPC.php';
$client = new XML_RPC_Client('/~sweatje/test_xmlrpc/', $_SERVER['HTTP_HOST'], 80);
$msg = new XML_RPC_Message('uname', array(new XML_RPC_Value(23, "int")));
//$client->setDebug(1);
$response = $client->send($msg);
$result = $response->value()->structmem('info')->serialize();
var_dump($result);
?>
So here is what I ended up with for the javascript client:
<?php
?>
<html>
<head>
<title>XMLRPC - JS to PHP test</title>
<script type="text/javascript" src="/js/jsolait/init.js"></script>
<script type="text/javascript">
var xmlrpc=null;
try{
var xmlrpc = importModule("xmlrpc");
}catch(e){
reportException(e);
throw "importing of xmlrpc module failed.";
}
onSubmit = function(){
var txRslt = document.getElementById("result");
txRslt.innerHTML = "";
try{
var server = new xmlrpc.ServerProxy("http://intranet.server.com/~sweatje/test_xmlrpc/", ["uname"]);
var rslt = server.uname();
txRslt.innerHTML = rslt.info;
}catch(e){
reportException(e);
}
return false;
}
</script>
</head>
<body>
<h1> testing </h1>
<h2> run </h2>
<button type="button" onclick="onSubmit()">execute</button>
<h2> result</h2>
<div id="result">before test</div>
</body>
</html>
?>
There, now I have everything for a complete proof of concept using a php xmlrpc server and javascript xmlrpc client. In addition, further tests reveal that the $_SESSION persists even with the js client! This is a bonus for me because this is an easy fix for my security concerns (my default security setup tucks user authentication info into the session).
And as promised, here is the initial stab at a frame/javascript base system flow diagram:
I’m actually exploring the same area but I’m wondering if it’s always necessary to use XML. In this case you can simply send your request with:
urllib.getURL(url, null, null, ”, [], response);
And capture your response to put in the DIV.innerHTML container using:
function response(content){DIV.innerHTML = content.responseText;}
The matter is that this way your server can respond sending simple HTML fragments.
I have not settled on final usage yet. I think I may want xmlrpc in this instance because the results might be an error indication that I would want to put into a different DIV than updated results. Probably could be done your way by integrating the error message and the full list into the same HTML response. I was leaning towards sending back the full HTML to be substituted in the xmlrpc response.
One thing I did on another project was use an iframe:
function popOrdDtl(ordUrl) {
popMe('ordDtl');
var tabDiv;
tabDiv = frames['ordDtlFrame'].document.getElementById('tabdiv');
tabDiv.innerHTML = '<h1>Please Wait</h1>Loading Order Information...';
frames['ordDtlFrame'].location.href=ordUrl;
}
with this sort of HTML markup:
<div id="ordDtl" class="dialog" style="width:780px;">
<div class="close"><a href="javascript:hideOrdDtl();">X</a></div>
<div class="close" id="ordDtlHdl" style="width:760px; float:left; position: absolute;">
<b>Order Item Detail Information</b></div>
<div style="clear:both;text-align:center;margin-left:auto;margin-right:auto;">
<iframe id="ordDtlFrame" name="ordDtlFrame" src="blank_ord.html"
style="width:100%;height:150px;" >
Your Browser does not support iFrames!
</iframe>
<button id="ordDtlOk" name="ordDtlOk" type="button" onClick="hideOrdDtl()"> Ok </button>
</div>
</div>
My actual implementation is the following:
Array.prototype.inArray = function (value){
for (var i=0; i < this.length; i++) {
if (this[i] === value) {
return true;
}
}
return false;
};
function JSRemoteTemplate(name, url, textLoading, callback){
var useInner = [‘A’, ‘ABBR’, ‘ACRONYM’, ‘ADDRESS’, ‘B’, ‘BDO’, ‘BIG’, ‘BLOCKQUOTE’, ‘BODY’, ‘BUTTON’, ‘CAPTION’, ‘CITE’, ‘CODE’, ‘DD’, ‘DEL’, ‘DIV’, ‘DFN’, ‘DL’, ‘DT’, ‘EM’, ‘FIELDSET’, ‘FORM’, ‘H1’, ‘H2’, ‘H3’, ‘H4’, ‘H5’, ‘H6’, ‘HEAD’, ‘I’, ‘INS’, ‘KBD’, ‘LABEL’, ‘LEGEND’, ‘LI’, ‘LINK’, ‘MAP’, ‘META’, ‘NOSCRIPT’, ‘OBJECT’, ‘OL’, ‘OPTGROUP’, ‘P’, ‘PARAM’, ‘PRE’, ‘Q’, ‘SAMP’, ‘SCRIPT’, ‘SMALL’, ‘SPAN’, ‘STRONG’, ‘STYLE’, ‘SUB’, ‘SUP’, ‘TD’, ‘TEXTAREA’, ‘TH’, ‘TT’, ‘UL’, ‘VAR’];
var useOuter = [‘SELECT’, ‘STYLE’, ‘TABLE’, ‘TITLE’];
var useOuter1 = [‘COLGROUP’, ‘OPTION’, ‘TBODY’, ‘TFOOT’, ‘THEAD’];
// var useOuter2 = [‘COL’, ‘TR’];
// var excludedByIE = [‘FRAMESET’, ‘HTML’];
// var unknown = [];
var e = document.getElementById(name);
if(!e) return false;
function response(content){
setContent(content);
if(callback){
callback();
}
}
function setContent(content){
if(typeof(content) == ‘object’){content = content.responseText;}
if(useInner.inArray(e.nodeName)){
e.innerHTML = content;
} else if(useOuter.inArray(e.nodeName)){
e.parentNode.innerHTML = openingTag()+content+closingTag();
} else if(useOuter1.inArray(e.nodeName)){
var xml = importModule(‘xml’);
var parentNode = xml.importNode(e.parentNode);
var eNode = findChildNodeById(parentNode, e.getAttribute(‘id’));
var newNode = xml.importNode(xml.parseXML(openingTag()+content+closingTag()).firstChild);
eNode.parentNode.insertBefore(newNode, eNode);
eNode.parentNode.removeChild(eNode);
e.parentNode.parentNode.innerHTML = xml.node2XML(parentNode);
} else {
alert(‘DON’T KNOW HOW TO LEAD WITH THIS!’);
return false;
}
}
function findChildNodeById(node, id){
if(node.attributes && node.attributes[‘id’] && node.attributes[‘id’].nodeValue == id){
return node;
} else {
if(node.hasChildNodes()){
var found = false;
for(var i = 0; i <node.childNodes.length; i++){
found = findChildNodeById(node.childNodes[i], id);
if(found){ return found; }
}
}
}
return false;
}
function openingTag(){
var tagstring = ‘<‘+e.nodeName.toLowerCase();
var atts = e.attributes;
for(var i = 0; i < atts.length; i++){
if(atts[i].nodeName != undefined && atts[i].nodeValue != ” && atts[i].nodeValue != null){
tagstring += ” “+atts[i].nodeName+’=”‘+atts[i].nodeValue+'”‘;
}
}
return tagstring + ‘>’;
}
function closingTag(){
return ‘</>’;
}
if(textLoading){
setContent(textLoading);
e = document.getElementById(name);
}
var urllib = importModule(“urllib”);
urllib.getURL(url, null, null, ”, [], response);
}
It’s very raw right now and just an implementation to see how can it work.
All that the client should know about this script is the use of JSRemoteTemplate(name, url, textLoading, callback) that accept the parameters stated. Callback is optional.
As you can see there are situations where the innerHTML solution is not valid as when you have to replace options inside a select. So I have tweaked out a solution that replace the container with a copy of itself but with the right contents.
Right now I’m implementing this system in a WACT component and I’m finding it quite useful (dynamic load of subrecords/options …).
Some of my code has been cut… if you are interested I can send you an email.
I changed the < and > to HTML entities in you post. I think I may hack something in that will do that automatically for <code> block in comments.
OK – you got me fired up 😉 Gonna blog something extensive in due course
Hi,
I have explored an alternative, simpler, method based on dom. I’m inserting a javascript element into the head element of the page to load dynamically create javascript code. This works very good and I use it in several applications.
http://www.dotvoid.com/view.php?id=13
dotvoid,
Very nice solution. Pretty compact way to implement.
what fun it is to read all this, after reading harry’s blog @ sitepoint
though i have seen any number of examples of mixing php and javascript do achieve this, what we need is a solid api to work from, both client and server side of course, and is cross browser friendly 😉
anyone with any suggestions please ? btw jason, thanks for the examples, most welcome 😀
Thank you, I just wanted to give a greeting and tell you I like your website very much.
i am store the date in the databas which this is posible
Simplest way to do this:
#! /usr/bin/env python
import SimpleXMLRPCServer
# insert real code here
def testing(text):
return text
server = SimpleXMLRPCServer.SimpleXMLRPCServer((“localhost”, 8000))
# register all your functions
server.register_function(testing)
# this is the usual way, but it might be more secure to use a primary server to authenticate and trigger a second
# server on a different port using the handle_request() method.
server.serve_forever()
client is even simpler:
#! /usr/bin/env python
import xmlrpclib
server = xmlrpclib.Server(“http://localhost:8000”)
print_this = server.testing(“hello world!”)
print print_this
>> prints “hello world!”
the jsolait libraries are pretty helpful, especially like the module-style imports, but why would you use php and not something like python (much better suited to the handling of application data) or even go with a .net solution via mono, which I know works on HP_UX? Even Java (and I admit to not being a fan of the language) has more natural power to it than php. Another advantage would be the ease of use of good encryption, although passing data to a C routine through PHP might have gotten better, my last experience with it was nightmarish and horribly insecure. Oh, and if you want a more efficient method that can serve both HTML and XML-RPC you can try this and then just call the double_server class instead of SimpleXMLRPCServer class:
class handler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler, SimpleHTTPServer.SimpleHTTPRequestHandler):
pass
class threaded_socket_server(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
logRequests=1
class server(SimpleXMLRPCServer.SimpleXMLRPCServer, threaded_socket_server):
def __init__(self, addr, requestHandler=handler):
SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self)
threaded_socket_server.__init__(self, addr, requestHandler)
class double_server(handler, threaded_socket_server, server):
def __init__(self, address, mode, *functions):
srvr = server(address, handler)
for function in functions:
srvr.register_function(function)
if mode == True:
srvr.serve_forever()
else:
srvr.handle_request()
—————————————————————————————————–
night all