Introduction

Writing complex web applications is difficult. The statelessness nature of HTTP and the isolation of the end user's web browser results in a flow of control for a web application that is more akin to a tangled mess of GOTOs rather than the sequential statements and loops that developers are comfortable with. Lakeshore is a library for the Java platform that addresses this disconnect and allows one to program web applications in a more natural style. It allows components to be called in the same way that one would call any Java method.

StoreCart cart = new StoreCart();
do
{
  call(new StoreFillCart(cart));
} while (!isCartComplete(cart));

StoreAddress shipping = getShippingAddress();
StoreAddress billing = shipping;
if(!useAsBillingAddress(shipping))
{
  billing = getBillingAddress();
}
StoreCreditCard creditCard = getPaymentInformation();

placeOrder(shipping, billing, creditCard);
displayConfirmation();
					
Main task from the sushiNet sample application

A pleasant side effect of this more traditional control flow is that it enables the creation of reusable components. It is even possible to reuse the same instance of a component in multiple places within an application. A page created using Lakeshore will be made of one or more of these components and each one may have its own thread of control which enables user interfaces that are impractical in other frameworks. There is also a growing collection of generally applicable components such as the BatchedList component which can be used to provide pagination and navigation of lists.

Screen shot of the
					sushinet sample application with Components outlined
Screen shot of the sushiNet sample application with Components outlined.

Now that it is established that Lakeshore is a panacea for all of the worlds web development problems it is time to mention the catch. There are no templates for generating HTML. No JSPs. No Velocity. All HTML is generated programmatically. While this may sound cumbersome at first, it is liberating in practice so that the "catch" is really a feature. All of the messy details of form handling and links to dynamic content are handled by Lakeshore. Templating systems became ubiquitous because they addressed the very real need to have HTML easily manipulated by graphic artist that designed the pages so that he or she could nest tables five deep and enter dozens of font tags. Times have changed and CSS is widely supported in browsers. Application developers can now concentrate on generating minimal semantic XHTML and graphic artists can tweak the CSS as much as they want. Those that doubt the power and flexibility of CSS should take a brief detour to meditate for a while in the css Zen Garden.

						
						
XHTML snippet along with its corresponding CSS.

Lakeshore was inspired by Borges for Ruby which was in turn inspired by Seaside 2. Web frameworks that allow for this type of straight forward control flow have been referred to variously as Modal Web Development, Synchronous Web Programming, or more commonly Continuation Based Web Framework. It is obviously a difficult thing to describe in three words. Both Borges and Seaside 2 are implemented using continuations, a language feature that Java is lacking. So while the API across all three are similar, the implementation of Lakeshore is radically different. It is implemented using a combination of threads, fancy synchronization, and a temporary suspension of disbelief.

The main players

While there are many classes working behind the scenes in Lakeshore, most applications will only need to use three classes: Component, Renderer, and Task.

The default Renderer class is responsible for emitting strict XHTML: aRenderer.span("Hello") would emit <span>Hello<span> to the browser.

The Component class is the basic building block for the user interface portion of Lakeshore applications. Components are stateful objects that encapsulate a part of a page.

The Task class enables complex, multi-page flows using traditional linear programming methods.

if(user.isCsr())
{
  Integer selectedId = (Integer)call(new SelectCompany(user));
  call(new PlaceOrder(user, getCompany(selectedId.intValue())));
}
else
{
  call(new PlaceOrder(user, user.getCompany()));
}
            
Multipage flow in a linear style.

Reading this code is about as natural as the particular requirement that drove it: "If a user is a CSR, let them pick a company to place an order for. If they a standard user, place an order for their own company."

Custom Components

web.xml and the Dispatcher

Forms and links

Tasks

Custom Renderers

Handling the back button

Bookmarkable links

Further reading