News

[17/Sep/2009:19:20] Release 0.5 includes Open Flash Chart support.

[01/Jul/2009:10:50] Repoze.who authentication tutorial added

[22/Jun/2009:11:36] AJAX calculator tutorial added.

[01/May/2009:14:15] MVC/SQL based wiki tutorial added.



Contact Email:



view source
W1100_1400.
calc.
whiff



Download instructions
at whiff.sourceforge.net
project page
.
WHIFF DOCUMENTION

AJAX based calculator

This discussion explains the implementation of a calculator widget using AJAX techniques with WHIFF.

Contents:
Strategy
HTML layout
Javascript event handlers
The "CLEAR" button press
The server call back
Summary
This discussion explains the implementation of a calculator widget using asynchronous communication between a web page and the web server without reloading the web page -- a methodology that has come to be known as AJAX.  
×
AJAX stands for "Asynchronous Javascript And XML" but like so many web acronyms the meaning has expanded to encompass any background communication between a web page and a web server, even if the communication is not asynchronous or does not use XML.

The embedded frame below displays the calculator application described here.

Please try it out by pushing the buttons and typing values into the input element.  

×
It is easy to implement this simple application using javascript alone without using AJAX callbacks to the web server. This implementation uses AJAX only in order to illustrate techniques for implementing AJAX methods with WHIFF.

Strategy

The calculator web page calc.whiff will maintain four state variables contained in HTML form elements:
<input id="value" name="value" type="hidden" value="0">
The value variable will hold the "old value" waiting to be combined (added or whatever) with the displayed value.
<input id="operation" name="operation" type="hidden" value="">
The operation variable will store the current operation (like "+") which is waiting for the arguments to be complete
<input id="display" name="display" value="0">
The display variable will store value most recently entered by the user in the calculator.
<input id="stack" name="stack" type="hidden" value="">
The stack variable will store a sequence of previous computations stored for future reference.

In addition to the state variables the calc.whiff page will provide a message for displaying the current state of the calculator.

<div id="message"> ready </div>

For the purposes of illustration we will pretend that we need to call back to the server in order to implement the calculator button operations such as "CLEAR" and "+".

To call back to the server the calculator page will send a request to the server that emulates a form submission specifying the CGI values for the four state variables.

In response to the request the server callback implementation calcCallback.py will send a fragment of javascript implementing appropriate changes to the state variable and the message area. For example if the value is 7 and the display shows 3 when the current operation is +, then a response to an ENTER press generates the following javascript response

(function () {
var elt = document.getElementById("message");
elt.innerHTML = "7.0 + 3.0 = 10.0";
} ) ();
    
(function () {
var elt = document.getElementById("display");
elt.value = "10.0";
} ) ();
    
(function () {
var elt = document.getElementById("value");
elt.value = "10.0";
} ) ();
    
(function () {
var elt = document.getElementById("operation");
elt.value = "";
} ) ();
These anonymous function calls display a message, set the display to the result value 10, store the value 10 as the "old value" for future computations, and resets the waiting operation.

When the calc web page receives the javascript response it executes the response to effect the changes to the page.

HTML layout

Below please examine the first part of the calc.whiff configuration template which specifies the layout of the calculator using a table and also declares the state variables for the calculator and the buttons, with attached mouse events.
{{env whiff.content_type: "text/html"/}}

<html>
<head>
<title>Ajax calculator demo</title>
</head>
<body style="font-family: Verdana, Arial, Helvetica, sans-serif;">

<script src="whiff_middleware/whiff.js"></script>

<form action="calc">
<input id="value" name="value" type="hidden" value="0">
<input id="stack" name="stack" type="hidden" value="">
<input id="operation" name="operation" type="hidden" value="">

<div id="message"> ready </div>
<table bgcolor="#555577">
<tr>
	<th colspan=4"> calculator </th>
</tr>
<tr>
	<th colspan="3"> <input id="display" name="display" value="0"> </th>
	<th> <button onclick="callBack('clear');return false;">
	Clear</button> </th>
</tr>

<tr>
	<th> <button onclick="numeric(7);return false;">
	&nbsp; 7 &nbsp;</button> </th>
	<th> <button onclick="numeric(8);return false;">
	&nbsp; 8 &nbsp;</button> </th>
	<th> <button onclick="numeric(9);return false;">
	&nbsp; 9 &nbsp;</button> </th>
	<th> <button onclick="callBack('/');return false;">
	&nbsp; / &nbsp;</button> </th>
</tr>

<tr>
	<th> <button onclick="numeric(4);return false;">
	&nbsp; 4 &nbsp;</button> </th>
	<th> <button onclick="numeric(5);return false;">
	&nbsp; 5 &nbsp;</button> </th>
	<th> <button onclick="numeric(6);return false;">
	&nbsp; 6 &nbsp;</button> </th>
	<th> <button onclick="callBack('x');return false;">
	&nbsp; X &nbsp;</button> </th>
</tr>

<tr>
	<th> <button onclick="numeric(1);return false;">
	&nbsp; 1 &nbsp;</button> </th>
	<th> <button onclick="numeric(2);return false;">
	&nbsp; 2 &nbsp;</button> </th>
	<th> <button onclick="numeric(3);return false;">
	&nbsp; 3 &nbsp;</button> </th>
	<th> <button onclick="callBack('+');return false;">
	&nbsp; + &nbsp;</button> </th>
</tr>

<tr>
	<th> <button onclick="numeric(0);return false;">
	&nbsp; 0 &nbsp;</button> </th>
	<th> <button onclick="numeric('.');return false;">
	&nbsp; . &nbsp;</button> </th>
	<th> <button onclick="callBack('neg');return false;">
	(-)</button> </th>
	<th> <button onclick="callBack('-');return false;">
	&nbsp; - &nbsp;</button> </th>
</tr>

<tr>
	<th> <button onclick="callBack('push');return false;">
	psh</button> </th>
	<th> <button onclick="callBack('pop');return false;">
	pop</button> </th>
	<th colspan="2"> <button onclick="callBack('ENTER');return false;">
	ENTER</button> </th>
</tr>

</table>
... TO BE CONTINUED BELOW ...
</form>
The calculator buttons assign two types of button press event handlers. Simple numeric buttons use use the javascript function numeric
<button onclick="numeric(0);return false;"> &nbsp; 0 &nbsp;</button>
whereas "operation" buttons use the javascript function callBack
<button onclick="callBack('ENTER');return false;"> ENTER</button>

Javascript event handlers

Below please examine the remainder of the calc.whiff configuration which defines the javascript functions to handle the button press events for the calculator.
... CONTINUED...
<script>

function numeric(num) {
    var display = document.getElementById("display");
    var value = display.value;
    if (value=="0") {
        display.value = ""+num;
    } else {
        display.value = display.value + (""+num);
    }
}

function callBack(mode) {
    {{include "whiff_middleware/runAsyncJavascript" Url: "calcCallBack"}}
    	      mode: mode
    {{/include}}
}
</script>
</body></html>
The numeric event handler blindly appends the corresponding character to the value of the display input element.

The callBack event handler implements the AJAX callback to the server which implements the "operation" button presses, using the whiff_middleware/runAsyncJavascript standard middleware.

The whiff_middleware/runAsyncJavascript middleware generates a javascript code fragment which sends a page configuration back to the web server. In this case it sends the page

{{include "calcCallBack"/}}
The server evaluates the page configuration and sends the evaluated result back to the EvalPageFunction requestor. The EvalPageFunction requestor recieves and evaluates the response text as javascript.

The EvalPageFunction implementation automatically passes form element values from the page to the web server. When the web server recieves the request the form element values are installed as CGI parameters for the page evaluation. For this example the state variables display, value, stack, and operation are sent as CGI values for the expansion of the calcCallBack.whiff

The EvalPageFunction implementation allows callbacks to add additional arbitrary values which are also installed as CGI values for the page evaluation. In this case the parameter

mode: mode
adds an additional CGI parameter for the calcCallBack.whiff expansion with the name "mode" and the value determined by the value of the javascript variable mode.

The "CLEAR" button press

For the sake of illustration suppose the calculator state variables have the following values:
display = "65";
value = "-13";
stack = "44 898";
operation = "+";
message = "-13 +"
When the user presses the CLEAR button the following action sequence ensues

The server call back

For completeness, the implementation of the server callback calcCallBack is shown below. The calcCallBack uses the values of the emulated form input parameters display, value, stack, operation and mode to generate an appropriate javascript fragment response for updating the calc page.
from whiff.middleware import misc
from whiff import resolver
from whiff import whiffenv

BINARY_OPERATIONS = ["/", "+", "-", "x"]
UNARY_OPERATIONS = ["neg", "push", "pop"]

class calcCallBack(misc.utility):
    def __call__(self, env, start_response):
        env = resolver.process_cgi(env, parse_cgi=True)
        mode = whiffenv.cgiGet(env, "mode", "")
        display = whiffenv.cgiGet(env, "display", "0")
        value = whiffenv.cgiGet(env, "value", "0")
        stack = whiffenv.cgiGet(env, "stack", "")
        operation = whiffenv.cgiGet(env, "operation", "")
        proxy = Proxy(display, value, stack, operation)
        # validate the display value
        try:
            displayValue = float(display)
        except ValueError:
            displayValid = False
        else:
            displayValid = True
        if mode=="clear":
            proxy.display = "0"
            proxy.operation =""
            proxy.message = "cleared"
        elif not displayValid:
            proxy.message = "invalid numeric data"
            proxy.value = "0"
        elif mode=="ENTER":
            self.doOperation(proxy, value, operation, display)
            proxy.operation = ""
        elif mode in BINARY_OPERATIONS:
            self.doOperation(proxy, value, operation, display)
            proxy.operation = mode
            proxy.message = "%s %s" % (proxy.display, mode)
            proxy.display = 0
        elif mode in UNARY_OPERATIONS:
            self.doOperation(proxy, value, operation, display)
            self.doUnary(proxy, mode)
        else:
            proxy.message = "unknown mode ("+mode+")"
        response = proxy.javascript()
        #response = "alert('hello from calcCallBack')"
        start_response("200 OK", [('Content-Type', 'application/javascript')])
        return [response]
    def doUnary(self, proxy, mode):
        display = float(proxy.display)
        if mode=="neg":
            proxy.display = proxy.value = -display
        elif mode=="push":
            proxy.stack = proxy.stack+" "+str(proxy.display)
            proxy.message = "pushed "+proxy.display
        elif mode=="pop":
            sstack = proxy.stack.split()
            if not sstack:
                proxy.message = "stack underflow"
            else:
                popped = sstack[0]
                proxy.stack = " ".join(sstack[1:])
                proxy.display = popped
                proxy.message = "popped "+str(popped)
        else:
            proxy.message = "unknown unary mode ("+mode+")"
    def doOperation(self, proxy, value, operation, display):
        v = float(value)
        d = float(display)
        if operation=="/":
            if d==0:
                proxy.message = "divide by zero not defined"
                proxy.display = 0
            else:
                proxy.display = proxy.value = r = v/d
                proxy.message = "%s / %s = %s" % (v,d,r)
        elif operation=="x":
            proxy.display = proxy.value = r = v*d
            proxy.message = "%s x %s = %s" % (v,d,r)
        elif operation=="+":
            proxy.display = proxy.value = r = v+d
            proxy.message = "%s + %s = %s" % (v,d,r)
        elif operation=="-":
            proxy.display = proxy.value = r = v-d
            proxy.message = "%s - %s = %s" % (v,d,r)
        elif operation=="":
            # no operation, copy display to value
            proxy.value = proxy.display
        else:
            proxy.message = "unknown operation ("+operation+")"
            proxy.display = 0
        return proxy
    
def setMessage(message):
    "create javascript to display a message"
    return """
    (function () {
    var elt = document.getElementById("message");
    elt.innerHTML = "%s";
    } ) ();
    """ % (message,)
          
def setValue(identity, value):
    "create a javascript fragment to set an input element value"
    return """
    // create and call anonymous function
    (function () {
    var elt = document.getElementById("%s");
    elt.value = "%s";
    } ) ();
    """ % (identity, value)

class Proxy:
    def __init__(self, display, value, stack, operation):
        self.display = self.display0 = display
        self.value = self.value0 = value
        self.stack = self.stack0 = stack
        self.operation = self.operation0 = operation
        self.message = None

    def javascript(self):
        result = ""
        if self.message:
            result += setMessage(self.message)
        if self.display0!=self.display:
            result += setValue("display", self.display)
        if self.value0!=self.value:
            result += setValue("value", self.value)
        if self.stack0!=self.stack:
            result += setValue("stack", self.stack)
        if self.operation0!=self.operation:
            result += setValue("operation", self.operation)
        return result

__middleware__ = calcCallBack
Please notice that the calcCallBack is implemented as a standard WHIFF application using methods already discussed in the previous tutorials.

The only trick worthy of note in the implementation is the use of a "client proxy" object which keeps track of changes in the state variables and generates appropriate javascript fragments reflecting the changes.

Summary

In this tutorial we described how to implement a calculator using AJAX methods within WHIFF. The whiff_middleware/EvalPageFunction provided the primary functionality for configuring a request from the client browser to be evaluated at the server.
0 comments.
Care to comment?
name: (required)
- email (not published):
comment: (required)

<< security number? >>