What you should already know: You should already know how to build a web page and about the client/server nature of web page requests. You should understand how CGI programs work and should have some experience with CGI programming. We use about seven lines of PHP in our exercises but you can replace this with C or Perl if you wish.
Requirements for the examples: To do the exercises in this tutorial you will need a browser with JavaScript enabled. JavaScript is enabled by default on most browsers so this requirement shouldn't require any action on your part. You will also need to have a web server and the ability to write web pages for the web server. The web server needs to support CGI and PHP. You should be ready to go if you already have Apache and PHP installed on your PC, and our exercises assume that you're using your PC for the web server.
(answering_machine -> syslog -> fifo -> Apache -> web_page)
Our answering machine logs caller ID information to syslog using LOG_LOCAL3, a facility not used by any other applications on our system. A syslog rule in syslog.conf directs messages with this facility to a fifo located in the Apache document directory. This tutorial deals with getting the strings written to the fifo to display on a web page. Writing to a fifo is the asynchronous part of our sample application.
<?php $fp = fopen("ajaxfifo", "r"); if ($fp) { $ajaxstring = fgets($fp, 128); fclose($fp); } header("Content-Type: text/html"); print($ajaxstring); ?>Create a fifo in the same directory.
mkfifo ajaxfifoTry to view getfifo.php using your web browser. Your web browser should hang waiting for the read of ajaxfifo to complete. Write some data to the fifo before the browser times out.
echo "Hello, World" > ajaxfifoThe text that you entered should appear in your browser window. Continue on to the next exercise if the data was displayed properly. If the data did not appear, go back and verify that the web server and PHP are installed and working properly.
The following exercise verifies that JavaScript is running on your
browser by creating a named region on a web page, and putting some
text into that named region. Copy the following program to
hello.html in the document directory of your web server.
<html>
<head>
<title>Exercise 2: Use DOM to Test JavaScript</title>
<script language=javascript type="text/javascript">
<!-- Hide script from non-JavaScript browsers
// SayHello() prints a message in the document's "hello" area
function SayHello() {
document.getElementById("hello_area").innerHTML="Hello, World";
return;
}
-->
</script>
</head>
<body onload="SayHello()">
<h3><center>Exercise 2: Use DOM to Test JavaScript</center></h3>
</p>
<div id="hello_area">This text is replaced.</div>
</body>
</html>
The above code identifies a section of the web page as "hello_area"
and uses the subroutine SayHello() to put text in the named
section. Load hello.html and verify that your browser's
JavaScript displays "Hello, World" on the web page.
<html> <head> <title>Exercise 3: A JavaScript Callback Demo</title> <script language=javascript type="text/javascript"> <!-- Hide script from non-JavaScript browsers var count; count = 0; // Put the current count on the page function DisplayCount() { // Put current time in the "count" area of the web page document.getElementById("count_area").innerHTML= "The count is: " + count++; // Schedule next call to DisplayCount setTimeout("DisplayCount()", 2000); return; } --> </script> </head> <body onload="DisplayCount()"> <h3><center>Exercise 3: A JavaScript Callback Demo</center></h3> <div id="count_area">This text is replaced.</div> </body> </html>
The following exercise adds only XMLHttpRequest() to the previous
exercises. We request the data from getfifo.php using
XMLHttpRequest() and we tie a callback to the arrival of the
response. The response callback displays the data using a named
division per the DOM. Copy the following code into
async.html.
<html>
<head>
<title>Exercise 4: An XMLHttpRequest() Demo</title>
<script language=javascript type="text/javascript">
<!-- Hide script from non-JavaScript browsers
var req_fifo;
// GetAsyncData sends a request to read the fifo.
function GetAsyncData() {
url = "getfifo.php";
// branch for native XMLHttpRequest object
if (window.XMLHttpRequest) {
req_fifo = new XMLHttpRequest();
req_fifo.abort();
req_fifo.onreadystatechange = GotAsyncData;
req_fifo.open("POST", url, true);
req_fifo.send(null);
// branch for IE/Windows ActiveX version
} else if (window.ActiveXObject) {
req_fifo = new ActiveXObject("Microsoft.XMLHTTP");
if (req_fifo) {
req_fifo.abort();
req_fifo.onreadystatechange = GotAsyncData;
req_fifo.open("POST", url, true);
req_fifo.send();
}
}
}
// GotAsyncData is the read callback for the above XMLHttpRequest() call.
// This routine is not executed until data arrives from the request.
// We update the "fifo_data" area on the page when data does arrive.
function GotAsyncData() {
// only if req_fifo shows "loaded"
if (req_fifo.readyState != 4 || req_fifo.status != 200) {
return;
}
document.getElementById("fifo_data").innerHTML=
req_fifo.responseText;
// Schedule next call to wait for fifo data
setTimeout("GetAsyncData()", 100);
return;
}
-->
</script>
</head>
<body onload="GetAsyncData()">
<h3>Exercise 4: An XMLHttpRequest() Demo</h3><p>
<div id="fifo_data"> </div>
</body>
</html>
Load async.html and, if all has gone well, everything sent
to ajaxfifo will appear in the browser window. For example:
echo "Hello, AJAX" > ajaxfifo
Copy async.html to webui.html and Add the following
subroutine into the JavaScript code:
// setColor updates the "color_area" with the color specified
function setColor(new_color) {
color_text = "<table border=2 bgcolor=";
if ("Blue" == new_color) {
color_text += "\"Blue\">"
}
else if ("Red" == new_color) {
color_text += "\"Red\">"
}
else { // shouldn't get here
color_text += "\"Green\">"
}
color_text += "<tr><td>A little color</td></tr></table>";
document.getElementById("color_area").innerHTML=color_text;
}
Add a form to the HTML of webui.html:
<body onload="GetAsyncData()">
<h3>Exercise 5: A Trivial Web UI</h3><p>
<div id="fifo_data"> </div>
<p>
<div id="color_area">No color yet.</div><p>
<form>
<input type=button value="Blue" onClick="setColor('Blue')">
<input type=button value="Red" onClick="setColor('Red')">
</form>
</body>
Fifo versus /dev/fanout: Open a two windows with async.html and write some data into the fifo. Notice that the data that you write appears in only one window. This is because the fifo passes the data to only one of the readers. In our real application we use a fanout device so that the data is written to all readers. Fanout is described in a previous Linux Gazette article (here) and on the author's home page (here).
XML: If you are getting only a single piece of data from your web server, you can use the code in this article. As soon as you ask for more than one piece of data, you should switch to XML. It does not need to be difficult.
Let's say that you've modified getfifo.php to use the caller
ID to look up the time and duration of the last time you spoke with
the caller, and instead of returning just the caller ID string, you
want to return the last call time and duration. If we ignore the
PHP code to do the database lookup, the PHP to build an XML response
for the three pieces of information might look like this:
<?php
header("Content-Type: text/xml");
print("<?xml version=\"1.0\" ?>\n");
print("<caller_info>\n");
printf("<caller_id>%s</caller_id>\n", "Mary");
printf("<lastcalltime>%s</lastcalltime>\n", "10:24 am");
printf("<lastcalldur>%s</lastcalldur>\n", "12:05");
print("</caller_info>\n");
?>
Instead of using req_fifo.responseText to get the whole
body of the reply, you would use the following to extract each
of the three fields you want:
callid = req_fifo.responseXML.getElementsByTagName("caller_id");
calltm = req_fifo.responseXML.getElementsByTagName("lastcalltime");
calldur = req_fifo.responseXML.getElementsByTagName("lastcalldur");
callid[0].childNodes[0].nodeValue;
document.getElementById("caller_data").innerHTML=
callid[0].childNodes[0].nodeValue;
document.getElementById("lastcalltime").innerHTML=
calltm[0].childNodes[0].nodeValue;
document.getElementById("lastcalldur").innerHTML=
calldur[0].childNodes[0].nodeValue;
There are simpler and better ways to do this, but the above code
should give you an idea of what to expect.
Further reading: The Apple developer's web site has a good article on XMLHttpRequest(). You can find it here:
You may find the tutorials at the JavaScript Kit web site to be a big
help if filling in the details that this article omits. You can find
their tutorials here:
http://www.javascriptkit.com/javatutors/
The IBM DeveloperWorks web site has some great articles on AJAX
and XML. We found this one particularly useful:
http://www.ibm.com/developerworks/web/library/wa-ajaxintro1.html
Finally, you might be interested in the low cost answering machine
that's at the heart of the author's appliance:
http://www.linuxtoys.org/answer/answering_machine.html