ARTICLE

A Day At The Beach

Exploring the Seaside Web Application Framework for Smalltalk

A Dramatic Improvement in Abstraction and Productivity

Sven Van Caekenberghe ( Beta Nine )
November 2003, First Revision
June 2010, Second Revision

Building advanced web applications is notoriously hard and complex. The current state of the art is a modified version of the model-view-controller paradigm, implemented with some kind of server pages, web actions and an object model. But even using the best framework in an advanced environment, there are some major problems. The request-response cycle of HTTP remains visible, the statelessness of HTTP requires often complex session management, form processing remains an annoying, repetitive task and there is no such thing as components that can be combined freely.

Enter Seaside, a Web Application Framework for Smalltalk. In this article we describe our first experiences exploring this framework. First we tried a very simple web application to compute factorials. Next we did something harder: we implemented a simple calculator as a web application. We were absolutely amazed at how easy it was: much, much easier than it would be in any other framework that we know of. We believe Seaside offers a dramatic improvement in abstraction and productivity.

Contents

Photo of North Sea Beach, Klegod, Denmark
North Sea Beach, Klegod, Denmark

Smalltalk

Smalltalk, invented in 1972, is considered by many as the best object oriented programming environment. Smalltalk is a system that consists of a programming language, a large library of reusable objects, a integrated development environment, many end-user applications and a virtual machine. The main principles behind Smalltalk are simple:

That's it: the rest of Smalltalk follows from these principles, by following them to the end. Consider the following snippets of code:

The one aspect of Smalltalk that will unsettle most people is the way source and object code is handled. There simply are no source files, nor individual object files. In Smalltalk you work in an image, you browse to individual classes and work with one method at the time. Everything you do in Smalltalk is considered a change and is logged as such. When you're done, you save your image to keep your work for the next round. You should think of writing software in Smalltalk as adapting and extending an existing system so that it is now capable of doing what you want it to do.

Screenshot of Pharo as an IDE
Figure 1: Pharo as an IDE

Pharo is an open-source Smalltalk implementation based on Squeak in the best tradition of the original Smalltalk-80 standard. Pharo runs bit-identical on many platforms and is efficient enough to run multimedia applications. It is out of the scope of this article to try to explain all the things that Pharo can do, or to try to convince you of how great Pharo is - just try it out. Squeak does look and feel a bit weird, but remember that literally everything can be changed. Pharo has a cleaner and more finished look and feel. There used to be some lack of proper written documentation, but the internet and the source code will be your guide. Currently some great up-to-date books are available to get you started.

Figure 1 shows Pharo with some of its IDE functionality in action. The large window is a Class Browser looking at the class Collection, more particulary at the method #inject:into:. The Workspace window at the bottom (a scratch area) shows a test expression typed in there by the programmer. The programmer selected the expression and asked Smalltalk to evalute it and open an inspector on the result: that is the '45 on Inspector' window.

Seaside

Seaside is an innovative and advanced Web Application Framework first developed for Squeak Smalltalk, since then ported to all major Smalltalk dialects. Seaside hides the HTTP request-response cycle and models the entire user session as a continuous piece of code, with natural, linear control flow. A web application consists of number of composable components with standard call-return semantics. Seaside can handle the backtracking and parallelism inherent to web browsers. An HTML generation framework and an advanced call-back mechanism for links, actions and forms further simplifies development.

The first example we (in true Pair Programming tradition, my colleague Hein Saris and myself) tried was simple: a web user interface to compute factorials. The first step is to make a new subclass of WAComponent, WAFactorial. We add a number instance variable and standard accessors to track the number that we start from. All WAComponents need a #renderContentOn: method to generate their HTML representation. This method is passed an WAHtmlRenderer object that helps you to generate HTML. See listing 1 for the implementation of this method for WAFactorial.

Without going into too much detail, we'll describe what is going on here. The first line just puts a level 1 heading on the page. The second line wraps a block inside a form (the action is handled transparently). Inside the form, we place a text input field on the aspect #number of ourself. This means that our own number value will be used as initial value for the input field and that any value entered by the user will be stored as the new number value in ourself. This will happen when the form is submitted. Finally we add a submit button with a callback block that will be executed when form is submitted and an exclamation point as label. What the block does is interesting: it sends a string like '5 != 120' as argument with #inform:. Now what #inform: does is call out to another component to display the result. This other component will have an 'OK' button to simply return to the caller.

renderContentOn: html
  html heading: 'Factorial Computation'.
  html form: [
    html textInput on: #number of: self.
    html space.
    html submitButton
      callback: [ self inform: (self number, ' ! = ', self number asInteger factorial asString) ];
      with: ' ! ' ]
Listing 1: The method WAFactorial>>renderContentOn:

This is the whole web application. Note that we didn't see any request or response objects, nor did we directly see HTML. If we were to write the same interface for a normal GUI, the code would be similar (and probably longer). All we need to do is use the tool under /config (another Seaside application of course) to define a new web application under /factorial with WAFactorial as main component. Figure 2 shows how we configured an application around WAFactorial.

Screenshot of Seaside web interface to configure a web application around WAFactorial
Figure 2: Configuring a web application around WAFactorial

All Seaside applications have a URL like the one in figure 2: the parameter called _s is the session id, the parameter called _k is the continuation id that tracks the progress or control flow in the session. Seaside applications are configured to have a toolbar at the bottom during development. This is accomplished by wrapping your component inside a containing component. The toolbar allows you do to some amazing stuff: like inspecting the actual Smalltalk objects behind the application, or browsing and modifying the code behind the application! Like any great framework, Seaside gives us an immense amount of functionality for free - all we did was make one subclass and implement one method.

Calculator

We decided to test Seaside by trying to implement a simple four-function calculator as a web application. We started by implementing a Calculator object that models the calculator: it has methods for all keys (like #digit:, #operator:, #dot and #clear) and holds the state of the calculator (like what is the number on the display, what is the last operator pressed, and so on). It is actually a pretty simple class (see the code in resources).

Screenshot of Pharo doing unit tests
Figure 2: Pharo doing unit tests

Being good programmers, we immediately wrote some unit tests to see if the calculator did what we expected it to do. Should we have done true Test Driven Design, we should have implemented the tests before the implementation. Figure 2 shows a Pharo session where a programmer is working on the unit tests for Calculator. Two windows are visible. The first window is a Class Browser on the class CalculatorTest, showing the method #testDotAdditionOperatorHit. You can clearly see how the calculator object is being tested. The second window is an SUnit Test Runner. The class CalculatorTest is selected and all its 8 tests ran successfully.

renderContentOn: html
  html div id: #calculator; with: [
    self renderHeadingOn: html.
    self renderNumpadOn: html.
    self renderOperationsOn: html ].
  self renderByLineOn: html

renderHeadingOn: html
  html heading: calculator display

renderNumpadOn: html
  html div id: #numpad; with: [
    #(#(7 8 9) #(4 5 6) #(1 2 3)) do: [ :eachRow |
      html div class: #row; with: [
        eachRow do: [ :each | 
          self renderButton: each callback: [ calculator digit: each ] on: html ] ] ].
    html div class: #row; with: [
      self renderButton: '0' callback: [ calculator digit: 0 ] on: html.
      self renderButton: '.' callback: [ calculator dot ] on: html.
      self renderButton: '=' callback: [ calculator operator: #= ] on: html ] ]

renderOperationsOn: html
  html div id: #operations; with: [
    #(#+ #- #* #/) do: [ :each | 
      html div class: #row; with: [
        self renderButton: each callback: [ calculator operator: each ] on: html ] ].
    html div class: #row; with: [
      self renderButton: 'C' callback: [ calculator clear ] on: html ] ]

renderButton: label callback: callback on: html
  html anchor callback: callback; with: label

renderByLineOn: html
  html paragraph: 'If you make a mistake, you can use your browser''s back button to undo your last action(s)'
Listing 2: The method WACalculator>>renderContentOn: and its helper methods

Next came the web component WACalculator, yet another subclass of WAComponent. This time, our web component class holds a calculator model object in an instance variable. The most important method is again #renderContentOn: as shown in Listing 2 with its helpers. Apart from the syntax that maybe needs some getting used to, the basic idea is very simple. For each calculator key, we create a link that has a callback that sends one of the 'key' methods to the calculator model. The rest of the code organizes the links in rows and specific areas by wrapping them in divs. We used quite a bit of helper methods, partly because it is a Smalltalk rule to aim for short methods, but also to prepare our class to be subclassed (see Listing 4).

initialize
  super initialize.
  calculator := Calculator new

states
  ^ Array with: calculator
Listing 3: The methods WACalculator>>initialize and WACalculator>>states

By registering our calculator model object for backtracking using the #states method (see listing 3), Seaside starts to perform some of its magic. It now becomes possible to use your browser's back button to effectively undo key clicks on the calculator. No matter how many steps you backtrack, Seaside keeps everything consistent.

Screenshot of the Web Calculator
Figure 3: The Web Calculator

A Seaside component can specify the style sheet to use through a method #style on WAComponent. The actual style sheet (CSS) code is longer than our #renderContentOn: method and can be found in the download in the resources section. The final result is shown in figure 3. You can find a live calculator at the following URL: http://caretaker.wolf359.be:8080.

When you play a little bit with the WACalculator web app you quickly become aware of the fact that it is a true web application: each time you click one of the buttons, a full request/response cycle takes place. Even though everything is quite fast, the page reload feels a bit annoying. Can we do better ? Can't this fancy new AJAX stuff help us ? Yes and yes, and it is surprisingly easy to do in Seaside !

We are going to make a subclass of WACalculator, called WAAjaxCalculator, keeping the basic layout and the same calculator model. See listing 4 for the methods that we override. Remember, a subclass inherits everything from its superclass, so to understand the code in listing 4, you have to add the non-overridden behavior from listings 2 and 3.

renderHeadingOn: html
  displayId := html nextId.
  html heading id: displayId; with: calculator display

renderButton: label callback: callback on: html
  (html div class: #key)
    onClick: ((html jQuery id: displayId) load html: [ :h |
      callback value. 
      h render: calculator display ]);
    onClick: ((html jQuery id: displayId) effect highlight color: 'green');
    onClick: ((html jQuery this) effect highlight color: 'white');
    with: label

renderByLineOn: html
  html paragraph: 'Note how the full page does not reload when you click the buttons'
Listing 4: The methods WAAjaxCalculator overrides

So what did we change ? We added an id to the heading showing our calculator's display. The key method is #renderButton:callback:on: that previously generated a simple anchor with callback and label. We now generate a plain div element with a label and add some onclick handlers.

The first onclick handler is a jQuery AJAX request: it will invoke a sub request on the server and load the result to update the html content of the display heading element. On the server, the block given to #html: is executed: it evaluates our original callback and returns the new calculator's display contents. This will happen asynchroneously: the page will not reload.

To help the user understand what happened, we add two jQuery UI effects: we highlight both the display and the button clicked. These two onclick handlers run completely in the client browser and do not touch the server. Note again that we didn't see and request/response objects, but more important we did not write any JavaScript code ! A clean Smalltalk API was all we needed.

Conclusions

We 'discovered' Seaside in 2003. After just one day of experimenting with it we were convinced about the dramatic improvement in abstraction and productivity it offers. We were absolutely amazed at how easy it was to do the examples described here: much, much easier than it would be in any other framework that we know of. We believe Seaside could be a killer application for Smalltalk.

Today, in 2010, Seaside matured and improved a lot. It was ported to all major Smalltalk dialects. It really became a tool to build industrial quality web applications. At the same time, the Pharo fork of Squeak added a much needed boost towards a more professional Smalltalk environment. The books mentioned in the next section are making it a lot easier to get on board.

Resources

The source code of the examples discussed in this article if available for free as a category file out: ADayAtTheBeach.st. The code is also available from a SqueakSource project called ADayAtTheBeach.

Apart from Seaside's place on the web: http://www.seaside.st/, the following books are highly recommended:

Each of these books are available online for free. Their website are actually Seaside applications!