Simple AJAX Example

MAKR 2006/10/22: I've looked into AJAX and what it takes to use it with Tcl on the server's side. To get started I used a good example for PHP from [L1 ]. It's really easy. Most of the code involved is Javascript. But have a look for yourself ...

 #!/usr/local/ActiveTcl/bin/tclsh8.4
 package require ncgi
 
 # possible content types in this example
 set ctx "text/xml"
 set cth "text/html"
 
 # Check whether the name is used already
 #  fixed list for simplicity: Alice, Bob
 proc nameInUse {q} {  
   switch -- [string tolower $q] {
     alice - bob {return 1}
     default {return 0}
   }
 }
 
 ::ncgi::parse
 
 if {[::ncgi::exists q]} {
   # Background operation: check query 
   ::ncgi::header $ctx
   puts "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>
 <response>
   <method>checkName</method>
   <result>[nameInUse [::ncgi::value q]]</result>
 </response>"
 
 } else {
   # Initial call: build page
   ::ncgi::header $cth
   puts "<script type=\"text/javascript\">\n<!--"
   puts {var req;
 function processReqChange() 
 {
     // only if req shows 'complete'
     if (req.readyState == 4) {
         // only if 'OK'
         if (req.status == 200) {
             // ...processing statements go here...
       response  = req.responseXML.documentElement;
       method    = response.getElementsByTagName('method')[0].firstChild.data;
       result    = response.getElementsByTagName('result')[0].firstChild.data;
       eval(method + '(\'\', result)');
         } else {
             alert("Problem while retrieving XML data:\n" + req.statusText);
         }
     }
 }
 function loadXMLDoc(url) 
 {
     // branch for native XMLHttpRequest object
     if (window.XMLHttpRequest) {
         req = new XMLHttpRequest();
         req.onreadystatechange = processReqChange;
         req.open("GET", url, true);
         req.send(null);
     // branch for IE/Windows ActiveX version
     } else if (window.ActiveXObject) {
         req = new ActiveXObject("Microsoft.XMLHTTP");
         if (req) {
             req.onreadystatechange = processReqChange;
             req.open("GET", url, true);
             req.send();
         }
     }
 }
 function checkName(input, response)
 {
   if (response != ''){ 
     // Response mode
     message   = document.getElementById('nameCheckFailed');
     if (response == '1'){
       message.className = 'error';
     }else{
       message.className = 'hidden';
     } 
   }else{
     // Input mode
     //url  = 'http://localhost/cgi-bin/webtest.cgi?q=' + input;
     url  = document.URL + '?q=' + input;
     loadXMLDoc(url);
   }
 }
 }
   puts "//-->\n</script>"
   puts "<style type=\"text/css\">\n<!--"
   puts "span.hidden{
   display: none;
 }
 
 span.error{
   display: inline;
   color: black;
   background-color: pink;  
 }"
   puts "-->\n</style>"
   puts "<input id=\"username\" name=\"username\" type=\"text\" onkeyup=\"checkName(this.value,'')\" />"
   puts "<span class=\"hidden\" id=\"nameCheckFailed\">
   This name is in use, please try another. 
 </span>"
 }

Execute this as CGI script. Tried with IE 6.0 and Firefox 1.5...


Some words about the functionality ...

The procedure nameInUse is the backend function. For simplicity it checks against a hardcoded list of names and returns true when it detects Alice or Bob - false otherwise.

The fist call to the script should be made without parameters. The complete page is sent to the browser in this case.

To understand whats happening now, observe your web server's log files. Enter a name. You can see now that whenever you release a key the browser sends a query to the server. And the server answers with a small amount of data. If you happen to enter Alice or Bob a colored box will immediately appear, informing that this name is already in use.


MJ - For a framework that abstracts away most of the javascript complexity, including browser differences, see scriptaculous.


Stanley - Here is cgi.tcl version:

 #!/bin/sh
 #\
 exec tcl "$0" ${1+"$@"}
 
 source cgi.tcl
 
 cgi_input;
 
 if {![catch {cgi_import q}]} {
     cgi_content_type "text/xml";
     cgi_puts "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
     cgi_puts "<response>"
     cgi_puts "      <method>checkName</method>"
     cgi_puts "      <result>$q</result>"
     cgi_puts "</response>"
 } else {
     cgi_content_type "text/html";
     cgi_puts "<script type=\"text/javascript\">\n<!--"
     cgi_puts {
         var req;
         var url;
         function processReqChange()
         {
             // only if req shows 'complete'
             if (req.readyState == 4) {
                 // only if 'OK'
                 if (req.status == 200) {
                     // ...processing statements go here...
                     response  = req.responseXML.documentElement;
                     method    = response.getElementsByTagName('method')[0].firstChild.data;
                     result    = response.getElementsByTagName('result')[0].firstChild.data;
                     eval(method + '(\'\', result)');
                 } else {
                     alert("Problem while retrieving XML data:\n" + req.statusText);
                 }
             }
         }
         function loadXMLDoc(url)
         {
             // branch for native XMLHttpRequest object
             if (window.XMLHttpRequest) {
                 req = new XMLHttpRequest();
                 req.onreadystatechange = processReqChange;
                 req.open("GET", url, true);
                 req.send(null);
                 // branch for IE/Windows ActiveX version
             } else if (window.ActiveXObject) {
                 req = new ActiveXObject("Microsoft.XMLHTTP");
                 if (req) {
                     req.onreadystatechange = processReqChange;
                     req.open("GET", url, true);
                     req.send();
                 }
             }
         }
         function checkName(input, response)
         {
             if (response != ''){
                 // Response mode
                 message   = document.getElementById('nameCheckFailed');
                 if (response == '1'){
                     message.className = 'error';
                 }else{
                     message.className = 'hidden';
                 }
             }else{
                 // Input mode
                 //url  = 'http://localhost/cgi-bin/webtest.cgi?q=' + input;
                 url  = document.URL + '?q=' + input;
                 loadXMLDoc(url);
             }
         }
     }
     cgi_puts "//-->\n</script>"
     cgi_puts "<style type=\"text/css\">\n<!--"
     cgi_puts "span.hidden{ display: none; } span.error{ display: inline; color: black; background-color: pink; }"
     cgi_puts "-->\n</style>"
     cgi_puts "<input id=\"username\" name=\"username\" type=\"text\" onkeyup=\"checkName(this.value,'')\" />"
     cgi_puts "<span class=\"hidden\" id=\"nameCheckFailed\"> This name is in use, please try another. </span>"
 }