Karoo project web client interface


The surf application is great for generating a web page from data in the database. However if you want to communicate back to the server, then you need some sort of web client interface. The Karoo Project has two types of client interfaces: You can post data to the surf application using multipart/form-data or application/x-www-form-urlencoded.

To make it easier, there is a Javascript library.

First, I will describe the mechanism for simply sending and receiving Karoo Project messages. These messages are normally sent as UDP/IP datagrams, which are asynchronous in nature. The Javascript library is hindered by the fact that it must use HTTP, which is synchronous. Hence, the surf application implements a gateway, so that the Javascript client can poll the gateway and get any messages that have arrived. From your Javascript application's point of view, the messages do arrive asynchronously, though with some latency.., and your application does need to manage the polling (start it, set up the frequency, and stop it.)

See the online reference for the Javascript interface:

Javascript Surf Interface

Your Javascript program will need functions from client.js and cave.js... so the HTML page must source those files as so:
 <script type="text/javascript" src="/surf/client.js"></script>
 <script type="text/javascript" src="/surf/cave.js"></script>

Javascript database update

First I will describe the simplest form of client/server message from the Javascript client: the message for updating the database. This one is called a "Cave Query", although in fact it goes via the surf application. Surf just acts as a gateway.

This is actually a "higher level" interface, because the Javascript function hides a lot of the internal workings of the code that communicates with the gateway.

The function is called "sendCaveQuery", and it takes 5 arguments:

Following is an example of a call to the cave:
 var params = new Array();
 params[0] = new caveParam("price", DB_DATA_TYPE_FLOAT, price_field.value);
 params[1] = new caveParam("bedrooms", DB_DATA_TYPE_INT_32, parseInt(bedrooms_field.value));
 params[2] = new caveParam("bathrooms", DB_DATA_TYPE_INT_32, parseInt(bathrooms_field.value));
 params[3] = new caveParam("ensuites", DB_DATA_TYPE_INT_32, parseInt(ensuites_field.value));
 params[4] = new caveParam("garages", DB_DATA_TYPE_INT_32, parseInt(garages_field.value));
 params[5] = new caveParam("carports", DB_DATA_TYPE_INT_32, parseInt(carports_field.value));
 params[6] = new caveParam("id", DB_DATA_TYPE_INT_32, id);
 params[7] = new caveParam("sold", DB_DATA_TYPE_BOOL, sold_field.checked);
 params[8] = new caveParam("sessionid", DB_DATA_TYPE_INT_32, sessionid);
 sendCaveQuery("specs", "set-specs", params, doneSpecsCallback, "ke-cave");

This example matches up with the service which is defined in the cave configuration file:

 <service name="set specifications" id="set-specs">
   <sql transaction="commit">
     select setspecs as success from setSpecs(
     <parameter name="id" type="integer"/>,
     <parameter name="price" type="float"/>,
     <parameter name="bedrooms" type="integer"/>,
     <parameter name="bathrooms" type="integer"/>,
     <parameter name="ensuites" type="integer"/>,
     <parameter name="garages" type="integer"/>,
     <parameter name="carports" type="integer"/>,
     <parameter name="sold" type="boolean"/>,
     <parameter name="sessionid" type="integer"/>);

The sendCaveQuery() function includes a callback. That is important, because it is the only way you can get a reply. The communication is asynchronous, which means that the reply can't simply be returned by the sendCaveQuery() function.

In the example above, the doneSpecsCallback is specified as the callback. The callback function prototype must be as so:

 function callbackFunction(trans)

... where the "trans" parameter is a caveTransaction object containing the results. The trans object is actually an array or arrays, of arrays. Before describing this, I'll first point out that there is a convenience method for dealing with 99% of the responses which will probably just contain a single row success/failure type of response:


 var params = new Array();
 params[0] = new caveParam("success");
 findParametersInTransaction(trans, params);
 if (params[0].value && parseBoolean(params[0].value)) {

In this example, the findParametersInTransaction() will look for just one column named "success". You could of course pass many parameters for it to look for in the reply.

The trans object

The trans object is is a caveTransaction object, defined as so, containing the following:

Now I need to also describe what is contained in the sql array: Each element in this array corresponds to an SQL that was run in the service, and each of these (usually just one) contains an array of rows returned from the SQL. Each row contains an array of columns. each column is a caveParam object with a name, type, and value.


 var number_of_sqls = trans.sqls.length;
 for var i = 0; i < number_of_sqls; i++) {
   var sql = trans.sqls[i];
   var number_of_rows = sql.length;
   for (var j = 0; j < number_of_rows; j++) {
     var row = sql[j];
     var number_of_columns = row.length;
     for (var k = 0; k < number_of_columns; k++) {
       var col = row[k];
       var column_name = col.name;
       var column_value = col.value;
       var column_type = col.type;
       switch (column_type) {
       case DB_DATA_TYPE_NULL:
       case DB_DATA_TYPE_UNSIGNED_8:
       case DB_DATA_TYPE_BOOL:
       case DB_DATA_TYPE_CHAR:
       case DB_DATA_TYPE_UNSIGNED_16:
       case DB_DATA_TYPE_UNSIGNED_32:
       case DB_DATA_TYPE_UNSIGNED_64:
       case DB_DATA_TYPE_INT_8:
       case DB_DATA_TYPE_INT_16:
       case DB_DATA_TYPE_INT_32:
       case DB_DATA_TYPE_INT_64:
       case DB_DATA_TYPE_FLOAT:
       case DB_DATA_TYPE_DOUBLE:
       case DB_DATA_TYPE_STRING:

Sending files to Cave

If you want to send a file (e.g. an image) to the cave session to be used as a parameter in an insert SQL, then the easiest way to do this is to use a FORM in the HTML. The FORM must have the following structure: e.g.

 <form action="/www.yourhost.com/" method="post" enctype="multipart/form-data" target="image-submit-output"
   onsubmit="return prepareFileSend('image-button-hex', 'image', 'set-image', 'id', 2, 'fileDoneCallback1');">
     <input id="image-file-field" type="file" name="image" tabindex="1" />
     <input type="hidden" name="type" value="11"/>
     <input id="image-button-hex" type="hidden" name="hex" value=""/>
     <input type="hidden" name="rock-type" value="cave"/>
     <input type="hidden" name="rock-name" value="ke-cave"/>
     <input type="submit" value="Save" id="new-picture-submit" />
 <iframe name="image-submit-output" id="image-submit-output" src="/surf/foutput.html">

The method called by the submit button (as specified by the onsubmit attribute of the FORM) must set up a few things, and then return true. It must return true, because otherwise the form won't be submitted via an HTTP POST, as it needs to be.

Note that the destination host name must be the action string, enclosed in '/' slash characters.

The function in the above example is not a library function. You need to write your own. I've included this function as an example:

 function prepareFileSend(hex_id, name, id, id_key, id_val, calback_name)
   var callback;
   eval("callback = "+calback_name+";");
   var trans = beginTransaction(name, callback);
   var params = new Array();
   params[0] = new caveParam(id_key, DB_DATA_TYPE_INT_32, id_val);
   params[1] = new caveParam("sessionid", DB_DATA_TYPE_INT_32, sessionid);
   var hex = getHexOfPreparedCaveQuery(name, id, params, trans.seq);
   document.getElementById(hex_id).value = hex;
   return true;

The function must create a transaction, by calling beginTransaction(). Then it must create the beginning of the message, hex encoded, by calling getHexOfPreparedCaveQuery(). (This hex encoded string must be set as the value of the hidden INPUT element named "hex" in the form.)

The getHexOfPreparedCaveQuery() function must be passed the following arguments:

Then finally, the function must make sure that the poller is running by calling cave_poller.poll().

See also

Generated on Tue Feb 16 15:04:29 2010 for Karoo by  doxygen 1.5.8