1 /*--------------------------------------------------------------------
  2  *  This is part of the Cloud Computing interface of the Karoo project.
  3  *
  4  *  (C) 2009 Brian Modra <brian@zwartberg.com>
  5  *
  6  *  This library is free software; you can redistribute them and/or modify
  7  *  it under the terms of the GNU Lesser General Public License as published by
  8  *  the Free Software Foundation; either version 2.1 of the License,
  9  *  or (at your option) any later version.
 10  *
 11  *  This library is distributed in the hope that it will be useful,
 12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 14  *  See the GNU Lesser General Public License for more details.
 15  *
 16  *  You should have received a copy of the GNU Lesser General Public License
 17  *  along with these libraries; if not, write to the Free Software Foundation,
 18  *  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 19  *------------------------------------------------------------------*/
 20 
 21 /** @fileOverview This file contains Javascript functions for the client-side interface of the cave rock
 22  * 
 23  * <p>Although this file is for the cave rock, communication to the cave actually occurs via the surf
 24  * rock.</p>
 25  * 
 26  * <p>The low level interface is implemented in client.js</p>
 27  *
 28  * <p>The Karoo Project System uses UDP for messaging. A "Cloud Computing" interface must use HTTP,
 29  * so there is a gateway. The gateway is the surf rock. It receives "messages" from the web client,
 30  * which are actually pre-packaged UDP messages ready to be sent on their way.
 31  * Hence the binary format has found its way, at least part way, into the javascript interface.
 32  * An integer in Javascript is a maximum of 48 bits, and a float is 64 bits. A compromise was met:
 33  * A 128 bit float will be truncated to 64 bits, and a 64 bit int will be
 34  * truncated to 48 bits. In the other direction, they are expanded.</p>
 35  *
 36  * <p>Please see also <a href="../index.html">The Karoo Project API documentation</a>, and especially, the
 37  * <a href="../web_client_interface_page.html">Karoo project web client interface</a>.</p>
 38  */
 39 
 40 /**
 41  * this is the type of a column when its NULL
 42  * @see caveParam
 43  */
 44 var DB_DATA_TYPE_NULL = 0;
 45 
 46 /**
 47  * this is the type of a database column when it is an 8 bit unsigned integer
 48  * @see caveParam
 49  */
 50 var DB_DATA_TYPE_UNSIGNED_8 = 1; // 
 51 
 52 /**
 53  * this is the type of a database column when it is a boolean value
 54  * @see caveParam
 55  */
 56 var DB_DATA_TYPE_BOOL = 2;
 57 
 58 /**
 59  * this is the type of a database column when it is an 8 bit character
 60  * @see caveParam
 61  */
 62 var DB_DATA_TYPE_CHAR = 3;
 63 
 64 /** 
 65  * this is the type of a database column when it is a 16 bit unsigned integer
 66  * @see caveParam
 67  */
 68 var DB_DATA_TYPE_UNSIGNED_16 = 4;
 69 
 70 /**
 71  * this is the type of a database column when it is a 32 bit unsigned integer
 72  * @see caveParam
 73  */
 74 var DB_DATA_TYPE_UNSIGNED_32 = 5;
 75 
 76 /** 
 77  * this is the type of a database column when it is a 64 bit unsigned integer
 78  * @see caveParam
 79  */
 80 var DB_DATA_TYPE_UNSIGNED_64 = 6;
 81 
 82 /** 
 83  * this is the type of a database column when it is an 8 bit integer
 84  * @see caveParam
 85  */
 86 var DB_DATA_TYPE_INT_8 = 7;
 87 
 88 /**
 89  * this is the type of a database column when it is a 16 bit short integer
 90  * @see caveParam
 91  */
 92 var DB_DATA_TYPE_INT_16 = 8;
 93 
 94 /** 
 95  * this is the type of a database column when it is a 32 bit integer
 96  * @see caveParam
 97  */
 98 var DB_DATA_TYPE_INT_32 = 9;
 99 
100 /**
101  * this is the type of a database column when it is a 64 bit integer
102  * @see caveParam
103  */
104 var DB_DATA_TYPE_INT_64 = 10;
105 
106 /** 
107  * this is the type of a database column when it is a 32 bit float
108  * @see caveParam
109  */
110 var DB_DATA_TYPE_FLOAT = 11;
111 
112 /**
113  * this is the type of a database column when it is a 64 bit float
114  * @see caveParam
115  */
116 var DB_DATA_TYPE_DOUBLE = 12;
117 
118 /**
119  * this is the type of a database column when it is a 128 bit float
120  * @see caveParam
121  */
122 var DB_DATA_TYPE_LONG_DOUBLE = 13;
123 
124 /**
125  * this is the type of a database column when it contains a string (text, char, varchar etc)
126  * @see caveParam
127  */
128 var DB_DATA_TYPE_STRING = 14;
129 
130 /**
131  * The cave_poller is the object that maintains contact with the gateway and sends/gets the messages
132  * @type server_poller
133  */
134 var cave_poller = null;
135 
136 /**
137  * This function initiates the cave interface.
138  *
139  * <p>See also the <a href="../../web_client_interface_page.html">overview and examples</a>.</p>
140  *
141  * @param {number} poll_delay the number of milliseconds between polls, 5000 is recommended (5 seconds).
142  * @param {string} url the URL of the service.
143  * @return there is no return value.
144  */
145 function initCave(poll_delay, url /*, username, password*/)
146 {
147     if (cave_poller == null) {
148 	cave_poller = new server_poller(poll_delay, url, /*username, password,*/ caveHandler);
149 	var img = document.getElementById("busy-anim-img");
150 	if (img) {
151 	    var src = img.getAttribute("src");
152 	    var ind = src.lastIndexOf('_');
153 	    if (ind != -1) {
154 		src = src.substring(0, ind+1);
155 		for (i = 0; i < 36; i++) {
156 		    pic= new Image(100,100); 
157 		    pic.src = src + i + ".png";
158 		}
159 	    }
160 	}
161     }
162 }
163 
164 /**
165  * Stop the poller. If your application knows that there are no messages that will be sent to your 
166  * application that is running in the web browser, then there is no need to keep polling (for
167  * something that will not happen.) Once the poller has been stopped, it will not start again
168  * until you send another message from the web application. Then it will resume polling as it was
169  * previously configured.
170  *
171  * <p>See also the <a href="../../web_client_interface_page.html">overview and examples</a>.</p>
172  */
173 function caveStop()
174 {
175     if (cave_poller) {
176 	cave_poller.stop();
177     }
178 }
179 
180 /** @private */
181 var busyAnimId = null;
182 
183 /** @private */
184 function busyAnimate(ind, busy)
185 {
186     if (busyAnimId == null)
187 	return;
188     var img = document.getElementById("busy-anim-img");
189     if (!img) {
190 	busyAnimId = null;
191 	return;
192     }
193 
194     if (img.complete) {
195 	var src = img.getAttribute("src");
196 	var i = src.lastIndexOf('_');
197 	if (i == -1) {
198 	    busyAnimId = null;
199 	    return;
200 	}
201 	src = src.substring(0, i+1);
202 	img.src = src + ind + ".png";
203 	ind++;
204 	if (ind > 35 || !busy)
205 	    ind = 0;
206     }
207     if (busy) {
208 	busyAnimId = setTimeout("busyAnimate("+ind+",true);", 100);
209     }
210     else {
211 	busyAnimId = null;
212     }
213 }
214 
215 /**
216  * Call this function to cause the busy icon to spin.
217  *
218  * <p>See also the <a href="../../web_client_interface_page.html">overview and examples</a>.</p>
219  *
220  * @param {boolean} busy true if it is busy
221  */
222 function setBusy(busy)
223 {
224     var img = document.getElementById("busy-anim-img");
225     if (!img) {
226 	return;
227     }
228     var src = img.getAttribute("src");
229     var i = src.lastIndexOf('.');
230     if (i == -1) {
231 	return;
232     }
233     src = src.substr(0, i);
234     if (busy) {
235 	img.src = src + ".gif";
236     }
237     else {
238 	img.src = src + ".png";
239     }
240     /*
241     if (busy) {
242 	if (!busyAnimId) {
243 	    busyAnimId = setTimeout("busyAnimate(0,true);", 1);
244 	}
245     }
246     else {
247 	if (busyAnimId) {
248 	    clearTimeout(busyAnimId);
249 	}
250 	busyAnimId = setTimeout("busyAnimate(0,false);", 1);
251     }
252     */
253 }
254 
255 /** @private */
256 var CAVE_MESSAGE_SQL_SUCCESS	= 0x80000001;
257 /** @private */
258 var CAVE_MESSAGE_SQL_FAILURE	= 0x80000002;
259 /** @private */
260 var CAVE_MESSAGE_SQL_ROW	= 0x80000003;
261 /** @private */
262 var CAVE_MESSAGE_SQL_DONE	= 0x80000004;
263 
264 /** @private */
265 function updateCaveTrans(msg, name, seq, sql, row, params)
266 {
267     var trans = null;
268     for (var i = 0; i < caveTransactions.length; i++) {
269 	var c = caveTransactions[i];
270 	if (c.name == name) {
271 	    if (c.arr) {
272 		for (var j = 0; j < c.arr.length; j++) {
273 		    var obj = c.arr[j];
274 		    if (obj.seq == seq) {
275 			trans = obj;
276 			if (msg.message_type == CAVE_MESSAGE_SQL_FAILURE) {
277 			    c.arr.splice(j, 1);
278 			    if (c.arr.length == 0) {
279 				caveTransactions.splice(i, 1);
280 			    }
281 			}
282 			break;
283 		    }
284 		}
285 	    }
286 	    else if (c.failed) {
287 		caveTransactions.splice(i, 1);
288 	    }
289 	    break;
290 	}
291     }
292 
293     if (caveTransactions.length) {
294 	setBusy(true);
295     }
296     else {
297 	setBusy(false);
298     }
299 
300     switch (msg.message_type) {
301     case CAVE_MESSAGE_SQL_SUCCESS:
302 	{
303 	    if (trans) {
304 		trans.rows = row;
305 	    }
306 	}
307 	break;
308     case CAVE_MESSAGE_SQL_FAILURE:
309 	{
310 	    var detail = "sql #"+str+", row#"+row+", message was from "+msg.recipient_name+"("+msg.message_type+")";
311 	    alert("failure: "+params+"\n"+detail);
312 	    if (trans) {
313 		trans.failed = true;
314 	    }
315 	}
316 	break;
317     case CAVE_MESSAGE_SQL_ROW:
318 	{
319 	    if (trans) {
320 		var arr;
321 		if (sql < trans.sqls.length) {
322 		    arr = trans.sqls[sql];
323 		}
324 		else {
325 		    arr = trans.sqls[sql] = new Array();
326 		}
327 		arr[row] = params;
328 		trans.checkDone();
329 	    }
330 	}
331 	break;
332     case CAVE_MESSAGE_SQL_DONE:
333 	{
334 	    if (trans) {
335 		trans.rows = row;
336 		trans.done = true;
337 		trans.checkDone(sql);
338 	    }
339 	}
340 	break;
341     }
342 }
343 
344 /** @private */
345 function caveHandler(poller, msg)
346 {
347     switch (msg.message_type) {
348     case CAVE_MESSAGE_SQL_SUCCESS:
349 	{
350 	    var reply_id_s = msg.readS();
351 	    var reply_id_i = msg.readU64();
352 	    var sql = msg.readU8();
353 	    var row = msg.readU32();
354 	    updateCaveTrans(msg, reply_id_s, reply_id_i, sql, row);
355 	}
356 	break;
357     case CAVE_MESSAGE_SQL_FAILURE:
358 	{
359 	    var reply_id_s = msg.readS();
360 	    var reply_id_i = msg.readU64();
361 	    var sql = msg.readU8();
362 	    var row = msg.readU32();
363 	    var err = msg.readS();
364 	    updateCaveTrans(msg, reply_id_s, reply_id_i, sql, row, err);
365 	}
366 	break;
367     case CAVE_MESSAGE_SQL_ROW:
368 	{
369 	    var reply_id_s = msg.readS();
370 	    var reply_id_i = msg.readU64();
371 	    var sql = msg.readU8();
372 	    var row = msg.readU32();
373 	    var cols = msg.readU8();
374 	    var params = new Array();
375 	    for (var i = 0; i < cols; i++) {
376 		var column = msg.readS();
377 		var type = msg.readU8();
378 		var val = null;
379 		switch (type) {
380 		case DB_DATA_TYPE_NULL:
381 		    break;
382 		case DB_DATA_TYPE_UNSIGNED_8:
383 		    val = msg.readU8();
384 		    break;
385 		case DB_DATA_TYPE_BOOL:
386 		    val = msg.readU8();
387 		    if (val == 0) {
388 			val = false;
389 		    }
390 		    else {
391 			val = true;
392 		    }
393 		    break;
394 		case DB_DATA_TYPE_CHAR:
395 		    val = msg.readU8();
396 		    val = String.fromCharCode(val);
397 		    break;
398 		case DB_DATA_TYPE_UNSIGNED_16:
399 		    val = msg.readU16();
400 		    break;
401 		case DB_DATA_TYPE_UNSIGNED_32:
402 		    val = msg.readU32();
403 		    break;
404 		case DB_DATA_TYPE_UNSIGNED_64:
405 		    val = msg.readU64();
406 		    break;
407 		case DB_DATA_TYPE_INT_8:
408 		    val = msg.readI8();
409 		    break;
410 		case DB_DATA_TYPE_INT_16:
411 		    val = msg.readI16();
412 		    break;
413 		case DB_DATA_TYPE_INT_32:
414 		    val = msg.readI32();
415 		    break;
416 		case DB_DATA_TYPE_INT_64:
417 		    val = msg.readI64();
418 		    break;
419 		case DB_DATA_TYPE_FLOAT:
420 		    val = msg.readF();
421 		    break;
422 		case DB_DATA_TYPE_DOUBLE:
423 		    val = msg.readD();
424 		    break;
425 		case DB_DATA_TYPE_LONG_DOUBLE:
426 		    val = msg.readLD();
427 		    break;
428 		case DB_DATA_TYPE_STRING:
429 		    val = msg.readS();
430 		    break;
431 		}
432 		var param = new caveParam(column, type, val)
433 		params.push(param);
434 	    }
435 	    updateCaveTrans(msg, reply_id_s, reply_id_i, sql, row, params);
436 	}
437 	break;
438     case CAVE_MESSAGE_SQL_DONE:
439 	{
440 	    var reply_id_s = msg.readS();
441 	    var reply_id_i = msg.readU64();
442 	    var sql = msg.readU8();
443 	    var row = msg.readU32();
444 	    updateCaveTrans(msg, reply_id_s, reply_id_i, sql, row);
445 	}
446 	break;
447     }
448 }
449 
450 /** @private */
451 var caveTransactionSeq = 0;
452 
453 /**
454  * remove all queued transactions (so they don't get sent, or if they already have been sent, so their
455  * replies are ignored.
456  *
457  * <p>See also the <a href="../../web_client_interface_page.html">overview and examples</a>.</p>
458  */
459 function zapTransactions()
460 {
461     caveTransactions = new Array();
462     setBusy(false);
463 }
464 
465 
466 /**
467  * Create a new caveTransaction object.
468  * You should not call this directly, but rather use the beginTransaction function,
469  * which also inserts the caveTransacrtion object into a list of current transactions.
470  *
471  * <p>See also the <a href="../../web_client_interface_page.html">overview and examples</a>.</p>
472  *
473  * @constructor
474  * @param {string} name unique transaction name from the client-side. This is used
475  * as a key into the list of waiting transactions, for associating requests with replies.
476  * @param {function} doneCallback the function to call when a reply comes in for this
477  * transaction.
478  */
479 function caveTransaction(name, doneCallback)
480 {
481     this.name = name;
482     this.seq = ++caveTransactionSeq;
483     this.doneCallback = doneCallback;
484     this.sqls = new Array();
485     this.rows = 0;
486     this.done = false;
487     this.failed = false;
488 
489     /** @private */
490     this.checkDone = function(sql)
491     {
492 	var arr = null;
493 
494 	if (!sql) {
495 	    sql = this.sqls.length;
496 	}
497 
498 	var row_count = 0;
499 	for (var i = 0; i < sql; i++) {
500 	    arr = this.sqls[i];
501 	    if (arr) {
502 		for (var j = 0; j < arr.length; j++) {
503 		    if (arr[j]) {
504 			row_count++;
505 		    }
506 		}
507 	    }
508 	}
509 	var processed = this.done && (row_count == this.rows);
510 	if (this.failed) {
511 	    processed = true;
512 	}
513 	
514 	if (processed) {
515 	    for (var i = 0; i < caveTransactions.length; i++) {
516 		var c = caveTransactions[i];
517 		if (c.name == this.name) {
518 		    if (c.arr) {
519 			for (var j = 0; j < c.arr.length; j++) {
520 			    var obj = c.arr[j];
521 			    if (obj.seq == this.seq) {
522 				c.arr.splice(j, 1);
523 				if (c.arr.length == 0) {
524 				    caveTransactions.splice(i, 1);
525 				}
526 				break;
527 			    }
528 			}
529 		    }
530 		    else if (c.failed) {
531 			caveTransactions.splice(i, 1);
532 		    }
533 		    break;
534 		}
535 	    }
536 
537 	    if (this.doneCallback) {
538     		this.doneCallback(this);
539 	    }
540 
541 	    if (caveTransactions.length) {
542 		setBusy(true);
543 	    }
544 	    else {
545 		setBusy(false);
546 	    }
547 	}
548     }
549 }
550 
551 /**
552  * This object is used in many functions for passing parameters to/from the services.
553  *
554  * <p>See also the <a href="../../web_client_interface_page.html">overview and examples</a>.</p>
555  *
556  * @constructor
557  * @param {string} name the name of the parameter
558  * @param {number} type tye type of the parameter (DB_DATA_TYPE_...)
559  * @param value The value (class is determined by the type value)
560  */
561 function caveParam(name, type, value)
562 {
563     this.name = name;
564     this.type = type;
565     this.value = value;
566 }
567 
568 /** @private */
569 var caveTransactions = new Array();
570 
571 /**
572  * Check if there is a transaction already in progress with the same name.
573  * This is useful for stopping overload and confusion if a user keeps clicking a
574  * send button for example.
575  *
576  * <p>See also the <a href="../../web_client_interface_page.html">overview and examples</a>.</p>
577  *
578  * @param {string} name the client-side unique name of the transaction that was used when beginTransaction was called.
579  * @return true if there is a transaction in progress now.
580  */
581 function caveQueryInProgress(name)
582 {
583     for (var i = 0; i < caveTransactions.length; i++) {
584 	var c = caveTransactions[i];
585 	if (c.name == name) {
586 	    return true;
587 	}
588     }
589     return false;
590 }
591 
592 /**
593  * Create a new caveTransaction object and insert it into the list of waiting transactions.
594  *
595  * <p>See also the <a href="../../web_client_interface_page.html">overview and examples</a>.</p>
596  *
597  * @param {string} name unique transaction name from the client-side. This is used
598  * as a key into the list of waiting transactions, for associating requests with replies.
599  * @param {function} doneCallback the function to call when a reply comes in for this
600  * transaction.
601  * @return the transaction object
602  * @type caveTransaction
603  * @see sendCaveQuery
604  */
605 function beginTransaction(name, doneCallback)
606 {
607     var ct = null;
608     for (var i = 0; i < caveTransactions.length; i++) {
609 	var c = caveTransactions[i];
610 	if (c.name == name) {
611 	    ct = c;
612 	    break;
613 	}
614     }
615     var trans = new caveTransaction(name, doneCallback);
616     if (ct) {
617 	ct.arr.push(trans);
618     }
619     else {
620 	ct = new Object();
621 	ct.arr = new Array();
622 	ct.name = name;
623 	ct.arr.push(trans);
624 	caveTransactions.push(ct);
625     }
626     return trans;
627 }
628 
629 /**
630  * This is a convenience function for sending a message to the cave rock.
631  *
632  * <p>See also the <a href="../../web_client_interface_page.html">overview and examples</a>.</p>
633  *
634  * Assuming you have a service defined in your cave rock's configuration file something like this:
635  *
636  * @param {string} name the client-side unique name of the transaction
637  * @param {string} id the ID of the cave service that you are calling
638  * @param {Array} params an array of caveParam objects that are being used to send parameters to the service
639  * @param {function} doneCallback the function to call when the reply has completed
640  * @param {string} rock_name the name of the cave rock.
641  *
642  * @example
643  * <service name="user" id="login">
644  *   <sql transaction="commit">
645  *     select permission,username,sessionid from login(
646  *       <parameter name="username"/>,
647  *       <parameter name="password"/>);
648  *   </sql>
649  * </service>
650  * ... then you could have something like the following as the javascript
651  * that is called when the "login" button is clicked:
652  * @example
653  * function doLogin()
654  * {
655  *   if (!caveQueryInProgress("login")) {
656  *     var params = new Array();
657  *     params[0] = new caveParam("username", DB_DATA_TYPE_STRING, username);
658  *     params[1] = new caveParam("password", DB_DATA_TYPE_STRING, password);
659  *     sendCaveQuery("login", "login", params, doneLoginCallback, "ke-cave");
660  *   }
661  * }
662  *
663  * The callback function will eventually be called and passed one parameter:
664  * the caveTransaction object.
665  * @see findParametersInTransaction
666  *
667  * @example
668  * function doneLoginCallback(trans)
669  * {
670  *   var params = new Array();
671  *   params[0] = new caveParam("permission");
672  *   params[1] = new caveParam("sessionid");
673  *   findParametersInTransaction(trans, params);
674  *   permission = 0;
675  *   sessionid = null;
676  *   if (params[0].value) {
677  *     permission = params[0].value;
678  *   }
679  *   if (params[1].value) {
680  *     sessionid = params[1].value;
681  *   }
682  *   if (permission == 0) {
683  *     alert("invalid username or password");
684  *   }
685  *   else if ((permission & 3) != 3) {
686  *     alert("no permission to make changes");
687  *   }
688  *   if ((permission & 3) != 3) {
689  *     logout();
690  *   }
691  *   else {
692  *     login();
693  *   }
694  *   caveStop(); // stop the polling now, no need to, we have the reply
695  * }
696  */
697 function sendCaveQuery(name, id, params, doneCallback, rock_name)
698 {
699     var trans = beginTransaction(name, doneCallback);
700     var message = new rock_datagram_message();
701     if (!rock_name) {
702 	rock_name = "cave";
703     }
704     message.setRecipientName(rock_name);
705     message.setRecipientType("cave");
706     message.setType(10);
707     message.writeS(id);
708     message.writeS(trans.name);
709     message.writeU64(trans.seq);
710     message.writeU8(params.length);
711     for (var i = 0; i < params.length; i++) {
712 	var p = params[i];
713 	message.writeS(p.name);
714 	message.writeU8(p.type);
715 	switch (p.type) {
716 	case DB_DATA_TYPE_NULL:
717 	    break;
718 	case DB_DATA_TYPE_UNSIGNED_8:
719 	    message.writeU8(p.value);
720 	    break;
721 	case DB_DATA_TYPE_BOOL:
722 	    message.writeU8(p.value? 1:0);
723 	    break;
724 	case DB_DATA_TYPE_CHAR:
725 	    message.writeU8(p.value.charCodeAt());
726 	    break;
727 	case DB_DATA_TYPE_UNSIGNED_16:
728 	    message.writeU16(p.value);
729 	    break;
730 	case DB_DATA_TYPE_UNSIGNED_32:
731 	    message.writeU32(p.value);
732 	    break;
733 	case DB_DATA_TYPE_UNSIGNED_64:
734 	    message.writeU64(p.value);
735 	    break;
736 	case DB_DATA_TYPE_INT_8:
737 	    message.writeI8(p.value);
738 	    break;
739 	case DB_DATA_TYPE_INT_16:
740 	    message.writeI16(p.value);
741 	    break;
742 	case DB_DATA_TYPE_INT_32:
743 	    message.writeI32(p.value);
744 	    break;
745 	case DB_DATA_TYPE_INT_64:
746 	    message.writeI64(p.value);
747 	    break;
748 	case DB_DATA_TYPE_FLOAT:
749 	    message.writeF(p.value);
750 	    break;
751 	case DB_DATA_TYPE_DOUBLE:
752 	    message.writeD(p.value);
753 	    break;
754 	case DB_DATA_TYPE_LONG_DOUBLE:
755 	    message.writeLD(p.value);
756 	    break;
757 	case DB_DATA_TYPE_STRING:
758 	    message.writeS(p.value);
759 	    break;
760 	case DB_DATA_TYPE_BYTEA:
761 	    message.writeS(p.value);
762 	    break;
763 	}
764     }
765     setBusy(true);
766     cave_poller.send(message);
767 }
768 
769 /**
770  * This function will be called by a FORM with via the "onsubmit" attribute. This function
771  * creates a hex encoded message body which will be sent as part of a file submit via a "multipart/form-data"
772  * FORM post.
773  *
774  * <p>See also the <a href="../../web_client_interface_page.html">overview and examples</a>.</p>
775  *
776  * @param {string} name the client-side unique name of the transaction
777  * @param {string} id the cave session ID to be called.
778  * @param {Array} params an array of caveParam objects containing the parameters to be sent to the service
779  * @param {number} seq a unique sequence number
780  * @return a HEX encoded message body reaty for sending to a service
781  * @type string
782  */
783 function getHexOfPreparedCaveQuery(name, id, params, seq)
784 {
785     var message = new rock_datagram_message();
786     message.writeS(id);
787     message.writeS(name);
788     message.writeU64(seq);
789     message.writeU8(params.length);
790     for (var i = 0; i < params.length; i++) {
791 	var p = params[i];
792 	message.writeS(p.name);
793 	message.writeU8(p.type);
794 	switch (p.type) {
795 	case DB_DATA_TYPE_NULL:
796 	    break;
797 	case DB_DATA_TYPE_UNSIGNED_8:
798 	    message.writeU8(p.value);
799 	    break;
800 	case DB_DATA_TYPE_BOOL:
801 	    message.writeU8(p.value? 1:0);
802 	    break;
803 	case DB_DATA_TYPE_CHAR:
804 	    message.writeU8(p.value.charCodeAt());
805 	    break;
806 	case DB_DATA_TYPE_UNSIGNED_16:
807 	    message.writeU16(p.value);
808 	    break;
809 	case DB_DATA_TYPE_UNSIGNED_32:
810 	    message.writeU32(p.value);
811 	    break;
812 	case DB_DATA_TYPE_UNSIGNED_64:
813 	    message.writeU64(p.value);
814 	    break;
815 	case DB_DATA_TYPE_INT_8:
816 	    message.writeI8(p.value);
817 	    break;
818 	case DB_DATA_TYPE_INT_16:
819 	    message.writeI16(p.value);
820 	    break;
821 	case DB_DATA_TYPE_INT_32:
822 	    message.writeI32(p.value);
823 	    break;
824 	case DB_DATA_TYPE_INT_64:
825 	    message.writeI64(p.value);
826 	    break;
827 	case DB_DATA_TYPE_FLOAT:
828 	    message.writeF(p.value);
829 	    break;
830 	case DB_DATA_TYPE_DOUBLE:
831 	    message.writeD(p.value);
832 	    break;
833 	case DB_DATA_TYPE_LONG_DOUBLE:
834 	    message.writeLD(p.value);
835 	    break;
836 	case DB_DATA_TYPE_STRING:
837 	    message.writeS(p.value);
838 	    break;
839 	case DB_DATA_TYPE_BYTEA:
840 	    message.writeS(p.value);
841 	    break;
842 	}
843     }
844     return message.hex;
845 }
846 
847 /** @private */
848 function caveListRowMouseOver(td, id)
849 {
850     var cave_list = findCaveList(id);
851     if (cave_list) {
852 	cave_list.rowChangeClass(td, false);
853     }
854 }
855 
856 /** @private */
857 function caveListRowMouseDown(td, id)
858 {
859 }
860 
861 /** @private */
862 function caveListRowMouseUp(td, id)
863 {
864     var cave_list = findCaveList(id);
865     if (cave_list) {
866 	cave_list.rowChangeClass(td, true);
867     }
868 }
869 
870 /** @private */
871 var htmlns="http://www.w3.org/1999/xhtml";
872 
873 /**
874  * This is the support class for a list widget
875  *
876  * <p>See also the <a href="../../web_client_interface_page.html">overview and examples</a>.</p>
877  *
878  * @constructor
879  * @param {string} id the unique ID of this list widget
880  * @param {document} doc the document (of the DOM)
881  * @param {Array} columns an array of caveColumn objects
882  * @see createCaveList
883  */
884 function caveList(id, doc, columns)
885 {
886     this.id = id;
887     this.doc = doc;
888     this.columns = columns;
889     this.row_data = new Array();
890 
891     /** @private */
892     this.createRow = function(header)
893     {
894 	var tbody = this.doc.getElementById("cave-list-body");
895 	var cols = new Array();
896 	var tr = this.doc.createElementNS(htmlns, "tr");
897 	tr.setAttribute("class", "cave-list");
898 	tbody.appendChild(tr);
899 	for (var i = 0; i < this.columns.length; i++) {
900 	    var td = this.doc.createElementNS(htmlns, header? "th": "td");
901 	    td.setAttribute("class", "cave-list");
902 	    if (!header) {
903 		td.setAttribute("onmouseover", "caveListRowMouseOver(this,'"+this.id+"');");
904 		td.setAttribute("onmousedown", "caveListRowMouseDown(this,'"+this.id+"');");
905 		td.setAttribute("onmouseup", "caveListRowMouseUp(this,'"+this.id+"');");
906 	    }
907 	    tr.appendChild(td);
908 	    cols[i] = td;
909 	}
910 	return cols;
911     }
912 
913     /** @private */
914     this.rowChangeClass = function(td, select)
915     {
916 	var tr = td.parentNode;
917 	var clazz = tr.className;
918 	if (select) {
919 	    if (clazz == "cave-list-select")
920 		return;
921 	}
922 	else {
923 	    if (clazz == "cave-list-highlight")
924 		return;
925 	}
926 	var tbody = tr.parentNode;
927 	for (var r = tbody.firstChild; r; r = r.nextSibling) {
928 	    if (r.nodeType == 1 && r.nodeName == "tr") {
929 		var skip;
930 		clazz = r.className;
931 		var new_clazz = clazz == "cave-list-select-highlight"? "cave-list-select": "cave-list";
932 		if (select) {
933 		    skip = false;
934 		}
935 		else {
936 		    skip = clazz == "cave-list-select";
937 		}
938 		if (!skip) {
939 		    if (r != tr && clazz != new_clazz) {
940 			r.className = new_clazz;
941 			for (var n = r.firstChild; n; n = n.nextSibling) {
942 			    if (n.nodeType == 1 && n.nodeName == "td") {
943 				n.className = new_clazz;
944 			    }
945 			}
946 		    }
947 		}
948 	    }
949 	}
950 
951 	clazz = tr.className;
952 	if (clazz == "cave-list-select") {
953 	    clazz = select? "cave-list-select" : "cave-list-select-highlight";
954 	}
955 	else {
956 	    clazz = select? "cave-list-select" : "cave-list-highlight";
957 	}
958 	tr.className = clazz;
959 	for (var n = tr.firstChild; n; n = n.nextSibling) {
960 	    if (n.nodeType == 1 && n.nodeName == "td") {
961 		n.className = clazz;
962 	    }
963 	}
964     }
965 
966     /**
967      * Get the selected row
968      * @return an array of caveParam objects, representing the selected row.
969      */
970     this.getSelected = function()
971     {
972 	var row_ind = 0;
973 	var tbody = this.doc.getElementById("cave-list-body");
974 	if (tbody) {
975 	    for (var tr = tbody.firstChild; tr; tr = tr.nextSibling) {
976 		if (tr.className == "cave-list-select" || tr.className == "cave-list-select-highlight") {
977 		    return this.row_data[row_ind];
978 		}
979 		row_ind++;
980 	    }
981 	}
982 	return null;
983     }
984 
985     /**
986      * Clear the list of all rows.
987      */ 
988     this.clear = function()
989     {
990 	var tbody = this.doc.getElementById("cave-list-body");
991 	if (tbody) {
992 	    while (tbody.firstChild) {
993 		tbody.removeChild(tbody.firstChild);
994 	    }
995 	}
996 	this.row_data = new Array();
997     }
998 
999     /**
1000      * Fill the list with rows from the caveTransaction object.
1001      * @param {caveTransaction} trans the transaction to get the row data from
1002      */
1003     this.fill = function(trans)
1004     {
1005 	var hdr = this.createRow(true);
1006 	for (var i = 0; i < this.columns.length; i++) {
1007 	    hdr[i].appendChild(this.doc.createTextNode(this.columns[i].title));
1008 	}
1009 	for (var i = 0; i < trans.sqls.length; i++) {
1010 	    var arr = trans.sqls[i];
1011 	    for (var j = 0; j < arr.length; j++) {
1012 		this.row_data.push(arr);
1013 		var row = this.createRow(false);
1014 		var a = arr[j];
1015 		for (var k = 0; k < a.length; k++) {
1016 		    var p = a[k];
1017 		    for (var m = 0; m < this.columns.length; m++) {
1018 			var c = this.columns[m];
1019 			if (p.name == c.name) {
1020 			    row[m].appendChild(this.doc.createTextNode(p.value));
1021 			}
1022 		    }
1023 		}
1024 	    }
1025 	}
1026     }
1027 }
1028 
1029 /** @private */
1030 var caveListMap = new Array();
1031 
1032 /**
1033  * This caveColumn object is used in the caveList object to describe the columns in the table.
1034  *
1035  * <p>See also the <a href="../../web_client_interface_page.html">overview and examples</a>.</p>
1036  *
1037  * @constructor
1038  * @param {string} name the name of the column
1039  * @param {string} title the title of the column
1040  */
1041 function caveColumn(name, title)
1042 {
1043     this.name = name;
1044     this.title = title;
1045 }
1046 
1047 /**
1048  * This is a convenience function for creating a caveList object.
1049  * It also adds the caveList object to an array of lists so that when events happen, the system can
1050  * apply them to the appropriate caveList object.
1051  *
1052  * <p>See also the <a href="../../web_client_interface_page.html">overview and examples</a>.</p>
1053  *
1054  * @param {string} id the unique ID of this list widget
1055  * @param {Array} columns an array of caveColumn objects
1056  */
1057 function createCaveList(id, columns)
1058 {
1059     var frame = document.getElementById(id);
1060     if (!frame)
1061 	return;
1062     var doc = frame.contentWindow.document;
1063     var cave_list = new caveList(id, doc, columns);
1064     caveListMap.push(cave_list);
1065 }
1066 
1067 /**
1068  * This function deletes the specified (by ID) caveList object.
1069  *
1070  * <p>See also the <a href="../../web_client_interface_page.html">overview and examples</a>.</p>
1071  *
1072  * @param {string} id the unique ID of this list widget
1073  */
1074 function deleteCaveList(id)
1075 {
1076     for (var i = 0; i < caveListMap.length; i++) {
1077 	var c = caveListMap[i];
1078 	if (c.id == id) {
1079 	    caveListMap.splice(i, 1);
1080 	    return;
1081 	}
1082     }
1083 }
1084 
1085 /**
1086  * This function finds the specified caveList object in the list.
1087  *
1088  * <p>See also the <a href="../../web_client_interface_page.html">overview and examples</a>.</p>
1089  *
1090  * @param {string} id the unique ID of this list widget
1091  * @return the caveList object or null if not found
1092  * @type caveList
1093  */
1094 function findCaveList(id)
1095 {
1096     for (var i = 0; i < caveListMap.length; i++) {
1097 	var c = caveListMap[i];
1098 	if (c.id == id) {
1099 	    return c;
1100 	}
1101     }
1102     return null;
1103 }
1104 
1105 /**
1106  * This is a generally useful function for parsing a boolean value from an Object.
1107  *
1108  * <p>See also the <a href="../../web_client_interface_page.html">overview and examples</a>.</p>
1109  *
1110  * @param val the object to be parsed
1111  * @return true if the val was "true"
1112  */
1113 function parseBoolean(val)
1114 {
1115     return val == "true" || val == "TRUE" || val == "t" || val == "T" || val == true;
1116 }
1117 
1118 /**
1119  * Pass this function the transaction object and an array of caveParam objects which you are interested it,
1120  * and it will search through the transaction to find the parameters you are intersted in, set their type and
1121  * value. On return from this function you can check teh array of parameters (that you passed to it), 
1122  * and extract the values that it found. Any it could not find will have a null value.
1123  *
1124  * <p>See also the <a href="../../web_client_interface_page.html">overview and examples</a>.</p>
1125  *
1126  * @param {caveTransaction} trans the transaction object from the remote cave object
1127  * @param {Array} params an array of CaveParam objects
1128  */
1129 function findParametersInTransaction(trans, params)
1130 {
1131     for (var m = 0; m < params.length; m++) {
1132 	var p = params[m];
1133 	p.value = null;
1134     }
1135     var found = false;
1136     for (var i = 0; i < trans.sqls.length; i++) {
1137 	var sql = trans.sqls[i];
1138 	for (var j = 0; j < sql.length; j++) {
1139 	    var row = sql[j];
1140 	    for (var k = 0; k < row.length; k++) {
1141 		var col = row[k];
1142 		var done = true;
1143 		for (var m = 0; m < params.length; m++) {
1144 		    var p = params[m];
1145 		    if (col.name == p.name) {
1146 			p.value = col.value;
1147 			p.type = col.type;
1148 		    }
1149 		    if (p.value == null) {
1150 			done = false;
1151 		    }
1152 		}
1153 		if (done) {
1154 		    found = true;
1155 		    break;
1156 		}
1157 	    }
1158 	    if (found) {
1159 		break;
1160 	    }
1161 	}
1162 	if (found) {
1163 	    break;
1164 	}
1165     }
1166 }
1167