Loading images

roll.html
<html>
<head>
<script type="text/javascript">
var images = [ "A.png", "B.png", "C.png", "D.png" ];
var imageIndex = 0;
function nextImage( img ) {
    imageIndex = (imageIndex+1) % images.length;
    img.src = images[ imageIndex ];
}
</script>
</head>
<body>
<img src="A.png" onclick="nextImage(this)">
</body>
</html>

AJAX: Asynchronous JavaScript and XML

The AJAX approach for developing a distributed client application uses the technologies present in most modern web browsers. These technologies are:

Using DOM, XMLHttpRequest, and Javascript, a complex client can be created. Gmail and Google maps are examples.

XMLHttpRequest

The XMLHttpRequest object available to most browsers allows the browser to request XML (and other document types) from a server without reloading the page making the request. The allows some of the state to be saved in the browser.

The XMLHttpRequest object provides the following methods and properties:

abort()
aborts the current request
getAllResponseHeaders()
return a string containing the HTTP header
getResponseHeader(label)
return the value of the header given by label
open(method, URL, asyncFlag, user, password)
method is GET or POST, URL is the requested document, asyncFlag is true for an asynchronous request, user and password are used when required for authentication, the last three parameters are optional
send(document)
transmit the document when the method is POST, set to null when the method is GET
setRequestHeader(label, value)
set one of the HTTP headers
onreadystatechange
set to the event handler
readyState
0:uninitialized, 1:loading, 2:loaded, 3:interactive, 4:complete
responseText
The entire response as a string
responseXML
the document object, accessible with DOM
status
numeric status response from server, 200 is OK, 404 is missing document
statusText
any status message

Mozilla provides the following additional properties:

onerror
set to the event handler for errors
onload
set to the event handler that is fired with the document is loaded
onprogress
set to the event handler that indicates progress
addEventListener(type,listener,capture)
register an event handler
removeEventListener(type,listener,capture)
remove the specified handler

Testing XMLHttpRequest

A test of XMLHttpRequest that returns the headers and content is:

header.html
<html>
<head>
<script type="text/javascript">

function requestXML( url ) {
    var req = null;
    if (window.XMLHttpRequest) { // not IE
        req = new XMLHttpRequest();
    } else if (window.ActiveXObject) { // IE
        req = new ActiveXObject("Microsoft.XMLHTTP");
    } else  {
        return;
    }
    req.onreadystatechange = function() {
        if ( req.readyState == 4) {
            displayResult( req );
        }
    }
    req.open( "GET", url, true );
    req.send( null );
}

function displayResult( req ) {
    var display = document.getElementById("display-response");
    var status = document.getElementById("display-status");
    var text = document.getElementById("display-text");
    var textmsg = document.getElementById("display-status-message");

    // only works in firefox
    display.textContent = req.getAllResponseHeaders();
    status.textContent = req.status;
    text.textContent = req.responseText;
    textmsg.textContent = req.statusText;
}

function sendrequest() {
    requestXML( "header.html" );
}

</script>
</head>
<body onload="sendrequest()">

<h3>Headers</h3>
<pre id="display-response">
</pre>

<p>
<b>status:</b> <span id="display-status"> </span><br>
<b>message:</b> <span id="display-status-message"> </span>
</p>

<h3>Document text</h3>
<pre id="display-text">
</pre>

</body>
</html>

Sending and receiving with XMLHttpRequest

A html document that contains a text area, button, and place holder for list data is:

list.html
<html>
<head>
<script type="text/javascript" src="list.js">
</script>
</head>
<body>
<textarea rows="5" cols="40" id="formData">
</textarea>
<br>
<button id="sendButton"> send </button>
<br>
<ol id="list">
</ol>
</body>
</html>

Sending and receiving: Javascript

The following javascript code, registers event handlers, send HTTP requests, and process the response.

list.js
/* ajax example, that appends to a list */
function sendForm( evt ) {
    // get text area data
    var text = document.getElementById('formData');

    var xmlDoc = document.implementation.createDocument(null, null, null);
    var xml = xmlDoc.createElement('element');
    xml.appendChild( xmlDoc.createTextNode( text.value) );
    xmlDoc.appendChild( xml );

    // transmit the document
    var req = new XMLHttpRequest();
    req.open("POST","list/add", true );
    req.onreadystatechange = function() {
        if ( req.readyState == 4) {
	    displayResult( req )
	}
    }
    req.send( xmlDoc );
    // clear text area
    text.value = '';
}

function displayResult( req ) {
    var d = req.responseXML.documentElement
    var e = d.getElementsByTagName( 'element' );
    var list = document.getElementById('list');
    for( var i = 0 ; i < e.length; i++ ) {
	var li = document.createElement('li');
	var res = e[i].textContent;
	li.appendChild( document.createTextNode( res ));
	list.appendChild( li );
    }
}

function initializeForm() {
     var b = document.getElementById( 'sendButton' );
     b.addEventListener('click', sendForm, false );
}

window.addEventListener("load", initializeForm, false);

Sending and receiving: Servlet

The servlet code is:

ListItemServlet.java
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.PrintWriter;

public class ListItemServlet extends HttpServlet {
    private int count = 0;

    protected void doPost(
        HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException
    {
        response.setContentType("text/xml");
        PrintWriter out = response.getWriter();
        String path = request.getPathInfo();
	System.out.println("path=" + path );

	BufferedReader rd = request.getReader();
	StringBuilder sb = new StringBuilder();
	int ch;
	while ( (ch=rd.read()) != -1 ) {
	    sb.append( (char)ch);
	}
        System.out.println( sb.toString() );
        out.println( "<?xml version='1.0'?>" );
        out.print( "<list>" );
	out.print( "<element> count"+count+"</element>");
	out.print ( sb.toString() + "</list>" );
	count++;
    }
}

adder application

A sequence of integers is sent by the client to the server to be added together. The sum is returned. One user interface is created with the following HTML file.

add.html
<html>
<head>
<title>add on the server</title>
<script type="text/javascript" src="add.js"/>
</script>
</head>
<body>
<p>
Calculate the sum of:
</p>
<form id="sum" action="javascript:void 1">
<label>A:
<input type="text" id="num1"  value="0" size="8">
</label>
and
<label>B:
<input type="text" id="num2"  value="0" size="8">
</label>
<input type="submit" value="=">
<input type="text" id="result"  value="0" size="8">
</form>
</body>
</html>

Javascript XMLHttpRequest for add

The Javascript code extracts the values from the form, creates a URL of the integers, and makes the XMLHttpRequest request. A XML document is returned containing the sum. This value is retrieved and used to update the form.

add.js
function doSum( evt ) {
    // get add input fields
    var num1 = document.getElementById('num1')
    var num2 = document.getElementById('num2')

    var url = 'add/' + num1.value + '/' + num2.value

    // transmit the document with the values to add
    var req = new XMLHttpRequest()
    req.open("GET", url, true )
    req.onreadystatechange = function() {
        if ( req.readyState == 4) {
            displayResult( req )
        }
    }
    req.send( null )
}

// extract and display the results
function displayResult( req ) {
    var d = req.responseXML.documentElement
    // since there is only one text value, the answer
    // can be extract with textContent
    var ans = d.textContent
    document.getElementById('result').value = ans
}

// initialize the event handlers
function init_add() {
    var sum = document.getElementById( 'sum' )
    sum.addEventListener('submit', doSum, false );
}

// start the setup after the page loads
window.addEventListener('load', function(evt){init_add()}, false)

AddServlet

The AddServlet computes the sum of the integers encoded in the path. A XML file containing the sum is them sent back to the client.

AddServlet.java
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

public class AddServlet extends HttpServlet {

    protected void doGet(
        HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException
    {
        response.setContentType("text/xml");
        PrintWriter out = response.getWriter();
        String path = request.getPathInfo();
        path = path.substring(1);
        String[] args = path.split("/");
        int sum = 0;
        for( int i = 0 ; i < args.length; i++ ) {
            sum += Integer.parseInt( args[i] );
        }
        out.println( "<?xml version='1.0'?>" );
        out.println( "<add-result>" + sum + "</add-result>" );
    }
}

AJAX for reporting weather

Construct an AJAX application that returns a weather description for a given city. The description is returned as an XML file, an example is:

w.xml
<?xml version='1.0'?>
<weather>
    <location> Ottawa</location>
    <temperature> -10.0</temperature>
    <synopsis> snowing</synopsis>
</weather>

WeatherReportServlet

The weather reporting servlet is:

WeatherReportServlet.java
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;

public class WeatherReportServlet extends HttpServlet {

    private static WeatherDesc[] testData = {
        new WeatherDesc("St. John's", 5.1, "sunny"),
        new WeatherDesc("Ottawa", -10, "snowing"),
        new WeatherDesc("Montreal", 2, "rain"),
    };

    private HashMap<String,WeatherDesc> weather
        = new HashMap<String,WeatherDesc>();

    public void init() {
        for( WeatherDesc w : testData ) {
            weather.put(w.getLocation(), w );
        }
    }

    private WeatherDesc lookup( String loc ) {
        return weather.get( loc );
    }

    protected void doGet(
        HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException
    {
        response.setContentType("text/xml");
        PrintWriter out = response.getWriter();
        String path = request.getPathInfo();
        path = path.substring(1);
        WeatherDesc w = lookup( path );
        out.println( "<?xml version='1.0'?>" );
        out.println( "<weather>" );
        out.println( "<location>" );
        out.print( w.getLocation() );
        out.println( "</location>" );
        out.println( "<temperature>" );
        out.print( w.getTemperature() );
        out.println( "</temperature>" );
        out.println( "<synopsis>" );
        out.print( w.getSynopsis() );
        out.println( "</synopsis>" );
        out.println( "</weather>" );
    }
}

Using the weather reporting service

A web page that uses the weather reporting service is:

weather.html
<html>
<head>
<script type="text/javascript">

function requestWeather( city ) {
    var req = new XMLHttpRequest();
    req.onreadystatechange = function() {
        if ( req.readyState == 4) {
            displayResult( req );
        }
    }
    var url = 'weather/' + escape(city)
    req.open( "GET", url, true );
    req.send( null );
}

function displayResult( req ) {
    var location = document.getElementById("w-location");
    var temp = document.getElementById("w-temp");
    var synopsis = document.getElementById("w-synopsis");

    var doc = req.responseXML;
    var l = doc.getElementsByTagName('location')[0].textContent;
    var t = doc.getElementsByTagName('temperature')[0].textContent;
    var s = doc.getElementsByTagName('synopsis')[0].textContent;

    // only works in firefox
    location.textContent = l;
    temp.textContent = t;
    synopsis.textContent = s;
}

function sendrequest() {
    requestWeather("St. John's")
}

</script>
</head>
<body onload="sendrequest()">

<p>
<select name="city" onchange="requestWeather(this.value)">
<option>St. John's</option>
<option>Ottawa</option>
<option>Montreal</option>
</select>
<br>
<b>location:</b> <span id="w-location"> </span><br>
<b>temperature:</b> <span id="w-temp"> </span><br>
<b>synopsis:</b> <span id="w-synopsis"> </span><br>
</p>

</body>
</html>

Page counter

In the early days of the web, page counters were very popular. A page counter, displays the number of times a particular page was viewed. Construct an AJAX application to count pages. The application should also generate a report on the hosts that viewed the page.

The CountServlet implements the server-side of the counter application. This servlet counts the visits and records the visitors with:

    private static class CountInfo {
        String host;
        int port;
        int seqNo;
        CountInfo( String host, int port, int seqNo ) {
            this.host = host;
            this.port = port;
            this.seqNo = seqNo;
        }
        public String toString() {
            return host + ":" + port + " " + seqNo;
        }
    }

    private int count = 0;
    private ArrayList<CountInfo> sites =
        new ArrayList<CountInfo>();

What is the visibility of host in CountInfo. Is the visibility important?

Counting the visits

The number of visits is counted with:

    private synchronized int nextCount() {
        return count++;
    }

Returning a count

The doGet method for this servlet is:

    protected void doGet(
        HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException
    {
        String path = request.getPathInfo();
        if ( path != null ) {
            if ( path.equals("/report") ) {
                report( request, response );
                return;
            }
        }

        int seq = nextCount();
        CountInfo info =
            new CountInfo(
                request.getRemoteHost(),
                request.getRemotePort(), seq );
        synchronized( this ) {
            sites.add( info );
        }
        response.setContentType("text/xml");
        PrintWriter out = response.getWriter();
        out.println( "<?xml version='1.0'?>" );
        out.println( "<count>" + seq + "</count>" );
    }

Generating a count report

    private synchronized void report(
        HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException
    {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println( htmlHead );
        out.println( "<ol>" );
        Iterator<CountInfo> it = sites.iterator();
        while ( it.hasNext() ) {
            out.println( "<li>" + it.next() + "</li>" );
        }
        out.println( "</ol>" );
        out.println( htmlTail );
    }

Using the counter

The counter can be used on a web page by:

counttest.html
<html>
<head>
<script type="text/javascript">

function requestXML( url ) {
    var req = new XMLHttpRequest();
    req.onreadystatechange = function() {
        if ( req.readyState == 4) {
            displayResult( req );
        }
    }
    req.open( "GET", url, true );
    req.send( null );
}

function displayResult( req ) {
    var counter = document.getElementById("counter");

    var doc = req.responseXML;
    var c = doc.getElementsByTagName('count')[0].textContent;
    counter.textContent = c;
}

function sendrequest() {
    requestXML("count")
}

</script>
</head>
<body onload="sendrequest()">

<p>
This is a very exciting web page.
</p>

<p>
<b>visits:</b> <span id="counter"> </span><br>
</p>

</body>
</head>

Source for CountServlet

CountServlet.java
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Iterator;

public class CountServlet extends HttpServlet {
    private static String htmlHead = "<html><head></head><body>";
    private static String htmlTail = "</body></html>";

    private static class CountInfo {
        String host;
        int port;
        int seqNo;
        CountInfo( String host, int port, int seqNo ) {
            this.host = host;
            this.port = port;
            this.seqNo = seqNo;
        }
        public String toString() {
            return host + ":" + port + " " + seqNo;
        }
    }

    private int count = 0;
    private ArrayList<CountInfo> sites =
        new ArrayList<CountInfo>();

    private synchronized int nextCount() {
        return count++;
    }

    private synchronized void report(
        HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException
    {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println( htmlHead );
        out.println( "<ol>" );
        Iterator<CountInfo> it = sites.iterator();
        while ( it.hasNext() ) {
            out.println( "<li>" + it.next() + "</li>" );
        }
        out.println( "</ol>" );
        out.println( htmlTail );
    }

    protected void doGet(
        HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException
    {
        String path = request.getPathInfo();
        if ( path != null ) {
            if ( path.equals("/report") ) {
                report( request, response );
                return;
            }
        }

        int seq = nextCount();
        CountInfo info =
            new CountInfo(
                request.getRemoteHost(),
                request.getRemotePort(), seq );
        synchronized( this ) {
            sites.add( info );
        }
        response.setContentType("text/xml");
        PrintWriter out = response.getWriter();
        out.println( "<?xml version='1.0'?>" );
        out.println( "<count>" + seq + "</count>" );
    }
}

WebAppServer

Assuming that the directory with the java files contains the war directory, then the web application is created with:

comp
javac -d war/WEB-INF/classes/ AddServlet.java CountServlet.java \
WeatherDesc.java WeatherReportServlet.java ListItemServlet.java \
AddXMLServlet.java
javac WebAppServer.java

It is run with:

run
java WebAppServer

The container for the servlets is:

WebAppServer.java
import org.mortbay.jetty.Server;
import org.mortbay.jetty.webapp.WebAppContext;
import org.mortbay.thread.BoundedThreadPool;
import org.mortbay.log.Log;

public class WebAppServer {
    public static void main(String[] args) {
	int port = 4004;
	if ( args.length >= 1 ) {
	    try {
		port = Integer.parseInt( args[0] );
	    }
	    catch( Exception ex ){ /*ignore*/ }
	}
	try {
	    Server server = new Server( port );
	    // set up a thread pool
	    BoundedThreadPool threadPool = new BoundedThreadPool();
	    threadPool.setMaxThreads( 100 ); // is this a good idea?
	    server.setThreadPool(threadPool);

	    server.addHandler(new WebAppContext( "war", "/"));

	    server.setStopAtShutdown(true);
	    server.setSendServerVersion(true);
	    
	    server.start();
	    server.join();
	}
	catch( Exception ex ) {
	    Log.warn( ex );
	}
    }
}