| 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.
