Tutorial: A Walk on the Seaside

Ported from the original Squeak tutorial beta4.com/seaside2/tutorial.html by Michel Bany
May 13, 2004
Revised, Mar 31, 2005
Revised, Aug 9, 2006
Revised, Nov 20, 2006

This tutorial is an exploratory introduction to the Seaside 2.6 web framework. To get started, you will need:

As you install Seaside, it will ask you to pick a username and password - these are used by Seaside's configuration app, which we'll get to later on. It will also ask you if you want to set up a sample HTTP server and a sample Seaside site. You should answer 'Yes' to both questions for now. Once everything is installed, Seaside should now be listening for requests on port 8008. Whenever you save and restart this image from now on, the Seaside service will come up automatically.

Basic Concepts: Sessions and Components

To test that Seaside is running, point your browser to localhost:8008/seaside/go/counter. This should bring up the most minimal of Seaside applications: a simple counter with increase and decrease links. We're going to be poking around this little application for a while, but for now just play with it and make sure it works: click the "++" link to make the number increase, and the "--" link to make the number decrease. As you play around with the counter, look at the URL in the location bar of your browser. The URL has two extra parts, each of which can tell us something about how Seaside works. First, there is a long, random looking _s= parameter with a series of letters and numbers, that looks something like _s=ueJdkUkAiqkOKxeA, and never changes as you use the application. This is the unique key which identifies the current user session. The session is a central concept to a Seaside application. Unlike in most web frameworks, which provide a session object only as a place to keep state, and focus mostly on individual request/response cycles, Seaside treats a session very much like a thread or a process: it starts a session running at a well defined entry point, and the application proceeds linearly from there, pausing to display web pages to the user and wait for input. We'll look at this more closely when we discuss control flow, later in the tutorial.

You'll notice a link named "New Session" on a grey bar that extends along the bottom of the page. This is the Seaside toolbar, which appears during development; we'll explore some of its options as we go along. The "New Session" link makes it easy to ditch the current process and start a new one from scratch. If you press it now, the only things you'll notice are that the counter gets reset to 0, and the session id changes.

In the URL is another, shorter, _k= parameter with a random string of letters and numbers. This URL parameter doesn't refer to a particular action, or encode the session state; all state is kept on the server side, and a Seaside session progresses linearly, not by jumping around to this or that action. Instead, it simply keeps track of requests in the current session, and allows Seaside to track the user's progress through the application. Similarly, each link or form field on the page is given a unique, sequential id: meaning is only attached to these numbers when they get back to the server. This has the disadvantage of rendering bookmarks unintelligible or even unusable, but it provides a huge amount of flexibility.

Instead of named pages or actions, Seaside applications usually consist of interacting "components", objects which model the state and logic of a portion of the UI. When the session starts, a single component instance is created, and that component enters a response loop: it displays itself to the user, and then waits for input. The input (a clicked link or a submitted form) will trigger a corresponding method on the component, and when that method is done, the component will display itself again. These triggered methods may simply update the state of the application or of the current component, or they may create and transfer control to another component, which will enter a response loop of its own.

The component class that models the "counter" application is WACounter, and we'll take a closer look at it now.

State, Action, Display: Component Essentials

Each component has three responsibilities: maintaining UI state, reacting to user input, and displaying itself as HTML. We'll take a quick tour through how WACounter handles each of them.

State

Even if much of the state of an application is stored in business objects or a database, the user interface often has state of its own. This might include, for example, the current value of a form field, or which database record was being displayed, or which nodes of a tree view are currently expanded. All of these are stored in the instance variables of the components that make up the UI.

In the case of WACounter, the only state to worry about is the count itself. When the instance of WACounter is first created at the beginning of the session, it initializes its 'count' instance variable to 0. By clicking on the "++" and "--" links, you're simply changing the value of this instance variable. A good way to investigate this is by clicking on the "Toggle Halos" link in the toolbar, and then the "eye" little icon (the one in the middle, between the "tool" icon and the "pen" icon). This will bring up a web-based inspector on the current component in another web browser window. You'll see a WACounter with a number of instance variables inherited from its superclass, WAComponent, but you'll also see 'count' at the end. The current value of 'count' will always be the same as the count that was displayed on the page from which you chose "Inspect".

If you want to explore, the link on an instance variable's name will take you to a new inspector for that object; you can use the browser's back button to walk back up the tree. When you're finished, the "Toggle Halos" link in the toolbar will take you back to the regular display of the counter application.

Action

There are two possible user actions in the counter application, and WACounter has a method corresponding to each of them. Clicking on the "++" link will result in a call to #increase, which is a very simple method - here it is in full:

increase
  count := count + 1

This does exactly what you would expect: it increases the value of 'count' by one. When it returns, the response loop continues, and the component displays itself again with the new value.

For fun, let's try modifying this. Click on the "Toggle Halos" link in the toolbar, and then the "tool" little icon (the one on the left). This opens a very functional web-based System Browser (courtesy of Lukas Renggli) in a new web browser window, open to the class of the current component. Find the #increase method and change it so that count increases by two instead of by one. Remember to hit Accept.

Display

When Seaside needs to display a component, it sends that component the message #renderContentOn:, passing an instance of WAHtmlRenderer as the argument. This will be a familiar pattern to anyone who has implemented an applet in Java, or a new subclass of Morph in Squeak: the object is given a canvas, which it is then expected to use to display itself. In this case, rather than shapes and lines, the "canvas" knows how to render elements of HTML. The renderer works like a stream: each message to it will append some text or elements to the document being rendered. Here's WACounter's #renderContentOn: method:

renderContentOn: html
    html heading: count.
    html anchor callback: [self increase]; text: '++'.
    html space.
    html anchor callback: [self decrease]; text: '--'.

Three different messages are sent to the renderer, each of them producing a different kind of HTML element. The first of these, #heading:, produces a simple section heading: if the current value of 'count' were 42, it would append <h1>42</h1> to the document. #space is also very simple, simply appending a non-breaking space character. #anchor is more interesting. It is called, obviously, to produce the "++" and "--" links that appear under the count. And the text: argument is clearly specifying a string to appear in the link. But what do these links point to? Where do they go? The short answer is, don't worry about it. In Seaside, links don't have destinations, they have callbacks: each time you generate a link or a button, it is associated with a block. When that particular link or button is clicked, the block is evaluated. In this case, clicking on the "++" link will result, as you would expect, in a call to self increase.

Let's modify this method slightly: instead of links for increase and decrease, we'll use submit buttons. Find WACounter>>renderContentOn: in a class browser, and change the code to this:

renderContentOn: html
    html form:  [
       html heading: count.
       html submitButton callback: [self increase]; text: '++'.
       html space.
       html submitButton callback: [self decrease]; text: '--'.
     ].

The main thing we did was to change the calls to #anchor to #submitButton. The two methods are used almost identically: it's very easy to switch back and forth between links and buttons as your interface requires. However, submit buttons will only work if they are inside a form. The #form: method is very simple, taking a single block. This time, it's not a callback; instead, it's used structurally, to enclose the contents of the form. Using #form: this way simply ensures that everything ends up inside a pair of form tags. The same convention is used by the table methods, #table:, #tableRow:, and #tableData:.

The Response Loop

Now that you've been introduced to each of a component's responsibilities, it may be useful to return to how they're all related. Each component operates a response loop, displaying itself, waiting for input, processing the input, and then displaying itself again. In terms of the methods we've seen so far, it looks like this:

Looking Deeper: Interactions Between Components

To transfer control to another component, WAComponent provides the special method #call:. This method takes a component as a parameter, and will immediately begin that component's response loop, displaying it to the user. To test this, we can use the WAFormDialog component class - we'll modify #decrease to print a message if the user tries to go below zero:

decrease
  count = 0
     ifFalse: [count := count - 1]
     ifTrue: [self call: (WAFormDialog new
                    addMessage: 'Let''s stay away from negatives.')]

If 'count' is zero, this creates a new instance of WAFormDialog, gives it an informative message to display, and calls it. Now start a new session and try to hit the "--" link. The counter should disappear, and you should see the dialog instead, the message shown as a large heading. Displaying a simple dialog like this is common enough that WAComponent provides an #inform: method for it, mimicking the default #inform: provided by Object. Try changing #decrease to use it, like so:

decrease
  count = 0
     ifFalse: [count := count - 1]
     ifTrue: [self inform: 'Let''s stay away from negatives.']

You'll notice that the dialog has a button labelled "OK". What happens when you press that? Well, the dialog goes away and the counter is displayed once more. Behind the scenes, the instance of WAFormDialog invokes a companion method to #call: named #answer, which causes control to return back to the calling component. In effect, calling another component is a simple subroutine call: if you like, you can think of #call: as pushing a new component onto the stack, and #answer as popping back to the old one. In fact, no such stack is maintained. Instead, what #answer does is a little more complicated: it causes the original message send of #call: to return, and the program continues from that point. Since calling the dialog was the last statement of #decrease, all that happens is that #decrease returns and the counter's response loop continues.

This is completely non-intuitive, and needs a lot of further explanation. Let me break down the sequence of exactly what happens:

  1. WACounter>>decrease makes a call to WAComponent>>inform:
  2. WAComponent>>inform: creates a new instance of WAFormDialog, and passes it into WAComponent>>call:. Let's name this point in the sequence "the call point".
  3. The WAFormDialog begins its response loop, and displays itself to the user's browser.
  4. The user clicks the "OK" button. This causes WAFormDialog to invoke WAComponent>>answer.
  5. Now for the strange part. WAComponent>>answer never returns. That's because it makes a jump: control shifts back to the "call point", just after the send of #call:.
  6. WAComponent>>call: returns to WAComponent>>inform:.
  7. WAComponent>>inform: returns to WACounter>>decrease.
  8. WACounter>>decrease returns to the counter's response loop, and it is displayed.
The really cool thing about #call: is this: if a called component provides an argument to #answer:, that argument will be returned from #call:. In other words, calling a component can yield a result. This is much more powerful than simply pushing and popping components from a stack. For example, it makes it easy to implement #confirm:, which displays a question and returns "true" or "false" depending on what the user clicks, to go with #inform:. Try changing #decrease as follows:

decrease
  count = 0
    ifFalse: [count := count - 1]
    ifTrue:
        [(self confirm: 'Do you want to go negative?')
               ifTrue: [self inform: 'Ok, let''s go negative!'.
	            count := -100]].

If you play with the counter now, you'll realize that within this one method there can be up to three page views: the confirmation dialog, the message dialog for "Ok, let's go negative", and finally back to the counter itself (for bonus points, try using the back button during this sequence and see what happens). This is a typical structure for Seaside applications: rather than having a series of closely coupled pages, each of which knows which pages come before and after it, each page will collect and return a single piece of information from the user, with the logic stringing them together all maintained in one place. The result can be stunningly reusable pieces of code.

Call and answer isn't the only way Seaside components get reused. You may not realize it, but there have been at least two Seaside components on your screen at all times. One of them is the WACounter - what's the other? The answer is, what you've actually been seeing this whole time is an instance of WACounter embedded inside another component, an instance of WAToolFrame, which renders the toolbar. This sort of embedding is very common in Seaside, and pages are often made up of many individual, nested components. The details of embedding components are beyond the scope of this tutorial, but for a simple example you might want to look at WAMultiCounter localhost:8008/seaside/go/multi, which contains several independent WACounters. If you do, make sure to hit the "Toggle Halos" link and poke around

Moving On: Starting Your Own Applications

Hopefully, by now you have some sense of what a Seaside application looks like. To start playing with your own, you may want to look at the configuration app at localhost:8008/seaside/go/config. This makes it easy to add a new, named application, and associate it with a component class of your choice. You'll need the username and password you picked at installation time to get in. If you write a new component and want it to show up as an option in the config app, make sure you implement #canBeRoot on the class side to return true.

Feel free to email comments about this tutorial to the Seaside mailing list: you can sign up at lists.squeakfoundation.org/listinfo/seaside.

See also the new Seaside web site www.seaside.st/