A Seaside Example

Under the /seaside heading, notice the list of apps. One of the examples that you see in the configuration screen is store. Click on it. You'll see SushiNet , one of the more bizarre examples for web frameworks. In the search window, type the word Tuna. Click on two different tunas to add them to your cart. Now click the Back button and notice that you go back to a previous page, just the way it was. Add another tuna to your cart, and you'll notice that the old tuna item is still in your cart. So, you can override the Back button behavior, as needed.


Notice the three boxes across the top of the screen, in Figure 8-3. Seaside is a component-based architecture . Each component has independent rendering, and each has a model behind it.

Figure 8-3. This Seaside application has three major components, each with independent rendering and business logic

This component-oriented approach often makes it much easier to design and refactor complex web screens. For example, here's the rendering for the shopping cart:

      html divNamed: 'cart' with: [
        html small: [html bold: 'Your cart:'].
        html table: [
          cart countsAndItems do:
           [:assoc | self renderRowForCount:
                     assoc key of: assoc value on: html ].
          html spacerRow.
            tableRowWith: ''
            with: ''
            with: [html bold: cart totalPrice printStringAsCents].

Notice that Seaside components have code that generates HTML. Java people don't tend to like this approach either, but it's very productive in Seaside. The code in bold generates the table. First, you see the table message passed to the html object. This will generate table tags around the code block. Next, you'll see a loop that processes the items in the cart, a spacer row, and a row with the totals.

Complex Control Flows

For this application, the most complex series of windows is the checkout. Think of how a traditional stateful application would manage the flow of control. Try out the checkout in the application and see how it works. Add a few pieces of sushi to your cart and click on Checkout. This piece of SushiNet will walk you through a few major steps:

  • You'll verify the contents of your cart. If you like your order, you can click "Proceed with checkout." Otherwise, you'll click "Modify my order." So the user makes a decision, and flow changes based on the user's input.

  • You'll specify a shipping address. You can then choose whether to use this address for your billing address. Again, this decision impacts the flow of the application. If you don't want to use the same address for shipping and billing, SushiNet will reuse the component that renders the shipping address for the billing addresses. Nice.

  • You'll enter your credit card information. If it doesn't verify, you'll go back to the same screen. If it does verify, you'll get a success screen.

  • Users can click the Back button at any time. If the user hits the Back button after his order is submitted, he'll get a message that the page has expired.

So, the flow looks something like Figure 8-4. It's not that complicated. You've got four decisions, and based on the decisions, you route the user to the appropriate place.

If you implemented this flow with Java servlets, you'd need to process four or more independent requests, as in Figure 8-5. Each one would have to first load the current state at the beginning of a request, and store the current state at the end of the request. The web flow would be based on the user's

Figure 8-4. This flow has three different user decisions, and would complicate traditional web apps

decisions, so you'd have several forwards. Changes in flow would lead to potentially major refactoring.

Figure 8-5. Java servlets view the checkout problem as four or more independent requests

With a continuations approach, the logic becomes almost trivial, as you see in Figure 8-6. You can simply look at the flow as one simple component, called Checkout. That component can handle flows involving more than one component, or more than one page! The code looks seductively simple.

Figure 8-6. With Seaside and other continuation servers, the flow becomes a single, integrated method
Debugging and browsing

Since you have a frozen continuation, it's easy for Seaside to provide a complete snapshot of the execution state. Seaside goes a step further and gives you access to a web-enabled browser. At the bottom of the screen, you should see a few links. Seaside creates them by default for all the applications. Notice that you can do profiling or check memory usage, but I've got something else in mind. Click on the link called Toggle Halos.

You should see a frame with three icons appear around each component. These icons give you a full code browser, an inspector, and a cascading style sheet editor. Click on the browser icon (the first one). Notice that you can see exactly where the execution state is frozen. Next, click on (from left to right) Seaside-Examples-Store, WAStoreTask, and Go. You see the code for the store task.

You'll see the code that implements the cart in Figure 8-4:

      | shipping billing creditCard |
      cart _ WAStoreCart new.
      self isolate:
         [[self fillCart.
        self confirmContentsOfCart]

      self isolate:
         [shipping <- self getShippingAddress.
          billing <- (self useAsBillingAddress: shipping)
                        ifFalse: [self getBillingAddress]
                        ifTrue: [shipping].
        creditCard <- self getPaymentInfo.
        self shipTo: shipping billTo: billing payWith: creditCard].

    self displayConfirmation.


In Seaside, tasks handle business logic. Let's zero in on the code in bold. It handles everything after the cart verification. The self isolate method takes a code block and makes sure everything in the block is an atomic operation, or a transaction. The next line of code is interesting:

    [shipping <- self getShippingAddress.

This statement actually presents the getShippingAddress web page to the user, and puts the resulting address into the shipping address. You can see how the framework inverts control. Now, instead of the browser being in control, Seaside lets you direct traffic from the server. The next three lines show a decision:

    billing <- (self useAsBillingAddress: shipping)
                  ifFalse: [self getBillingAddress]
                  ifTrue: [shipping].

The useAsBillingAddress method presents the decision screen. The expression (self useAsBillingAddress: shipping) returns a Boolean, and will trigger either the ifFalse: or ifTrue: methods. ifFalse: will actually trigger the code block [self getBillingAddress], which sends yet another web page to the user.

Though the Smalltalk syntax may seem awkward, if you're a Struts or Servlet developer, you're probably smiling right now. This approach frees you to work at higher abstractions. You can roll up several components, or pages, into a single task, and the continuation server keeps the management simple. State and navigation issues just melt away.