DomAPI Home
DomAPI
Author: Darin Kadrioski 06/19/2002
Modified: Darin Kadrioski 06/21/2002
Remote Procedure Calls (RPC)

NOTE: The server-side examples in this tutorial use Coldfusion. You can use any backend language you prefer, the concepts are easy enough to adapt.

Introduction

RPC is short for Remote Procedure Call. It is a method whereby you can send a message to a server and receive back a response. This can used for example to create and edit records in a database on the server, or to return datasets from the server back to the client. The idea is that this interaction between server and client can occur without needing to reload the page.

In order to understand how RPC works, you need a clear understanding of what code executes on the server and which on the client. The client will have the DomAPI loaded and will make a request to the server. This request is nothing more than a URL with parameters describing the request to the server. The server then responds by sending back pure Javascript, formatted in such a way that the RPC unit in DomAPI knows what to do with it. The code returned from the server also is responsible for firing an event on the client to let the RPC engine know that the request is complete. Many requests can be in transit at any given time (although most servers limit connections to 3 at a time) but the engine keeps them all separated by assigning a GUID or Globally Unique IDentifier to each. The DomAPI RPC engine offers you a number of flexible ways to hook into these events to make sure your data gets to where it needs to be.

RPCPacket Object

Data is exchanged between the client and server via an object called RPCPacket. This object takes two parameters at creation: a url for the method call and an optional status string to be displayed in the statusbar while the command is in motion. Once created, you can add extra parameters to be sent to the server via the packet's data property. The data property is a DomAPI List object and the contents of it will be sent to the server with the request. To actually send the request, use the core.rpc.sendPacket() routine.

Here is an example of creating and sending a packet to the server. This example sends a request to add a new user. It is assumed that you have already collected the username, email address and desired password from the user. This example calls a file named addUser.cfm on the server. This file would be responsible for doing the actual work on the server.

var myPacket = new RPCPacket("addUser.cfm","Creating your account...");
  myPacket.data.add("username",usernameVar);
  myPacket.data.add("email",   emailVar);
  myPacket.data.add("password",passwordVar);
  core.rpc.sendPacket(myPacket);

This has the effect of calling the addUser.cfm file on the server, passing it HTTP parameters called "username", "email" and "password". Each request also tacks on a parameter called "guid" which is the id that the DomAPI engine gave the packet when you created it.

Once DomAPI has sent the packet via core.rpc.sendPacket(), it will wait for a response from the server. If it doesn't receive a response before core.rpc.timeout expires (default is 30 seconds) the packet will have timed out and an event is raised on the client. Therefore, it is important that each request processed by the server return a response, even if it's only to say "ok, I got your request". Here is an example of what our addUser.cfm file might look like. The actual business logic is left to you, only the code pertaining to returning a response is shown.

<cfparam name="guid" default="-1">
<cfparam name="username" default="">
<cfparam name="email" default="">
<cfparam name="password" default="">
<!--- perform the addition and whatnot here --->

<cfoutput>
var response = new RPCPacket();
response.guid = "#guid#";
response.data.add("someReturnVar","whatever");
core.rpc.receivePacket(response);
</cfoutput>

This can be a bit confusing the first time you see it. The first half of that code is executed on the server. The second half is actually Javascript that is sent back and executed on the client. In fact, all the client would see is:

var response = new RPCPacket();
response.guid = "G1050665493484616";
response.data.add("someReturnVar","whatever");
core.rpc.receivePacket(response);

Notice that there is no script tags or includes. What the server returns must be pure unadulterated Javascript. This code will execute on the client in the scope of the main window, which is assumed will have the DomAPI library loaded.

Let's look closer at that output from the server.
On the first line, a response packet is created using the same RPCPacket object we used to send the request. Next we set this packet's guid property to match what was sent in with the request. This is extremely important as it's how the RPC engine links up responses with requests. On the next line we add a value to the data collection. This is merely as an example of how you can return data back to the client. You can have the server return as much data as you need using this technique. Also, any valid Javascript Object can be added to the data data collection, including Arrays and other Objects. Finally, the line core.rpc.receivePacket(response); signals the RPC engine that the request was was received and allows it to then dispatch the proper events.

Closing the Loop with Events

There are a couple of ways you can interact with the RPC engine in order to know when your packet requests and returned from the server. The first way is to assign a handler to the core.rpc.onreceive event. This is a global event that fires after each packet is received, passing the packet to the event. For example:

onload = function(){
  core.rpc.onreceive = onreceive;   
};
function onreceive(packet){
  // react to packet being received
  alert(packet.guid);
  alert(data.length);
};

core.rpc.onreceive fires after any packet is received. If you wanted to assign an event that only fires when a certain packet is received, you can assign an event handler when you call core.rpc.sendPacket(). In the first send example we used core.rpc.sendPacket() to send a request to the server:

var myPacket = new RPCPacket("addUser.cfm","Creating your account...");
  core.rpc.sendPacket(myPacket);

... however core.rpc.sendPacket can also take a second parameter which is the event to fire when the packet returns. Unlike core.rpc.onreceive, the event you pass to core.rpc.sendPacket will fire only when that single packet is returned. Here is an example that sends two separate packets, each with their own event handlers:

onload = function(){
  var p1 = new RPCPacket("getTopTable.cfm"   ,"Filling first table..." );
  var p2 = new RPCPacket("getBottomTable.cfm","Filling second table...");
  core.rpc.sendPacket(p1,ontoptable);  
  core.rpc.sendPacket(p2,onbottomtable);
};
function ontoptable(packet){
  // react to packet being received
  core.getElm("table1").setText(packet.data.findValueByName("contents"));
};
function onbottomtable(packet){
  // react to packet being received
  core.getElm("table2").setText(packet.data.findValueByName("contents"));
};

In this way, different event handlers can be setup to handle different packets. One important thing to keep in mind when sending multiple packets at a time, like in this example, is that they may not return in the order they were sent. Packets travel to and from the server in an asynchronous manner. If you have a set of RPC calls that need to be made in a certain order, chain them together by having the receipt of one packet trigger another, etc...

core.rpc.sendPacket() also takes two more optional parameters that you might find useful. The first is a timeout value, which is the number of milliseconds to wait before assuming a packet was lost. The global default for timeouts is 30 seconds, but you can overwrite it for each packet individually if you needed to. The other optional parameter is a timeout event handler. While the RPC engine offers a global timeout handler, this optional parameter allows you to specify an individualized handler for the particular packet being sent. For example:

core.rpc.sendPacket(p1,myReceive,40000,myTimeout);

Additional RPC Properties

So far in this tutorial we have covered

new RPCPacket(url,statusText);
core.rpc.sendPacket(packet,recvHandler,timeout,timeoutHandler);
core.rpc.receivePacket(packet);
core.rpc.onreceive(packet);
and
core.rpc.timeout;

Additional properties you may find useful are:

core.rpc.manageCursor; Boolean value that tells if the engine should control changing the cursor back and forth from the busy icon when packets are in motion.
core.rpc.lastError; If an error should raise, you can examine this property for the message text.
core.rpc.lastUrl; Useful for debugging, contains last request sent.
core.rpc.onsend(packet); This event fires after each packet is sent.
core.rpc.ontimeout(packet); This event fires if a packet times out.

Conclusion

The RPC engine was designed to be simple to use for the novice user, and yet still remain quite powerful and flexible for more advanced users. The examples on this page represent the simplest of circumstances and should fit most user's needs. The engine is flexible enough though to be extended in a myriad of powerful ways.

   
DHTML by www.domapi.com