A couple of weeks ago I picked up Chris Double’s server-side javascript implementation, which uses the Mozilla Project’s Rhino Javascript environment Jetty to provide a Javascript-controlled Java Servlet webserver.

The code’s available both for browsing and for darcs download:

darcs get https://www.lshift.net/~tonyg/javascript-server/

After adding support for Jetty’s SessionHandler class to Chris’s example.js , I downloaded the prototype.js Javascript utility library and got it running in a server-side environment1. The next step was using Rhino’s continuation support to implement the equivalent of PLT Scheme‘s send/suspend/dispatch (also seen in Seaside, under-the-covers as part of the HTML-rendering and workflow aspects of the system, and in SISCWeb, which is at the core of our Icing library).

Here’s a little workflow, roughly equivalent to Seaside’s Counter application:

sv.addEntryPoint ("/count", // [1] function (servlet, bindings) { var finalC = servlet.withState (10, // [2] function (c) { // [3] while ( // [4] servlet.sendAndDispatch (function (embedUrl) { // [5] servlet.replyHtml (doc("Counter", <> <p>{c.value}</p> <p> <a href={embedUrl(function(){ c.value++; return true})} >More</a>; <a href={embedUrl(function(){ c.value--; return true})} >Less</a>; <a href={embedUrl(function(){ return false})} >Stop</a>; </p> </>)); })) { // Nothing to do in the body of the loop. } return c.value; // [6] }); servlet.replyHtml(doc("Bye!", <p>Bye! {finalC}</p>)); // [7] });

Points of interest:

* [1] is where we specify the URL path to this workflow.

* [2] and [3] are about preserving state across use of the back button, about which more below.

* [4] is the point at which control will resume when the user clicks on any of the links produced by the embedUrl argument to the 00function given to sendAndDispatch .

* [5] is the function for producing a document for the user containing links (generated by embedUrl ) that cause the workflow to resume at [4].

* [6] is the point at which one of the embedded link-handlers in [5] has returned false to [4], causing the while-loop to 0terminate. At this point the state held in c is extracted and the stateful part of the workflow is over.

* [7] is where the workflow finally ends, because the final document sent to the user wasn’t sent from within sendAndDispatch and didn’t contain any embedded links to a continuation.

Javascript is a little like Scheme – but not enough like Scheme to avoid the pitfalls of using ordinary local variables in a web workflow. The problem is that there are two ways you might want local variables to behave as the user back-and-forwards around your workflow, both perfectly reasonable and appropriate at different times:

* the contents of variables could be unshared across stages of the workflow, so that backing up and proceeding again from an earlier point can run without being affected by any of the decisions the user has used the back button to, in effect, undo; and

* the contents could be shared across stages of the workflow, so that the user feels like he or she is affecting some real state in the server, and so that the different pages in the workflow appear to all be affecting this separate real object.

The first option seems to me more functional in style, and the second more object-oriented.

To produce the second, object-oriented effect using these Javascript servlets, simply declare variables as Javascript locals and assign to them. The first is trickier: all variables are mutable, and there’s no pleasant syntax for functional-style rebinding of variables, so I’ve resorted to the withState method seen in the example above.

The basic idea is that we should reify functional variables (since they’re the exception rather than the rule in Javascript; in Scheme, we’d probably reify the mutable ones!) and use a system very much like Scheme’s dynamic-wind to make sure the correct values are visible at each stage in the workflow. Here’s a more focussed example of withState usage:

var finalResult = servlet.withState(initialValue, function (stateCell) { // ... code using stateCell ... return finalValue; });

The initialValue gets placed into a fresh managed cell, which is bound to stateCell for the duration of the function. The code in the function should access and modify stateCell.value , and the values will be tracked automatically across the forward and back buttons. The final result of the function is used as the final result of the whole withState call. Once withState returns, stateCell is no longer automatically tracked – it has gone out of scope, in a way.