9.3.06. Seaside, Rails, Helpers, Formlib, helping web applications through smart components, tools, and linking

A web application framework that truely fascinates me is Seaside. Seaside, written in Smalltalk, has great vision and ideals for web applications by using continuations to suspend and resume code. It breaks free of the typical request/response cycle, and manages state to a fault.

This code, from an example store application for Seaside, is the go method of the WAStoreTask object. This method runs the store. Stay with me now, this is important. This runs the store. This is the flow. It’s native Smalltalk. It’s not XML, YAML, An Array or list. It doesn’t get translated into any structure like that. Upon first visit to the application, this code starts executing.

go
    | shipping billing creditCard |
    cart := WAStoreCart new.
    self isolate: [
      [self fillCart. self confirmContentsOfCart] whileFalse
    ].

    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.

The isolate method calls wrap a block in a WATransaction, making that transaction run over multiple requests. I’m not sure if this WATransaction maps to how I’m used to them as a Zope developer, but I do gather that it cleans extraneous objects up that were racked up in that ‘isolate’ call.

The use of continuations and a sort of invisible ‘session’ management that runs behind it makes for very brief code. Looking at my own Zope 2 e-commerce application, I see many lines spent having to look things back up: getting data out of the Session, getting data out of the Request, getting data out of the Database(s), and so on and so on and so on. With each request/response cycle, especially late into the Checkout process (which depends on all of the accumulated data of the shopping experience), the list of dependencies gets bigger. Guards have to be in place to ensure that the session and account are still active late in the checkout process, and that going to one of the checkout pages with an empty cart is disallowed… So it’s interesting to look above and see just a small handful of expressions boil down the whole experience.

An aspect of Seaside that is especially interesting is action calling, via hyperlinks or form submission. I also like how it does HTML generation, preferring to use objects/methods instead of templates. This is from a WAStoreItemView. renderContentOn: html is a common method that all the views implement. Unlike the so-called ‘MVC’ that is being popularized in Ruby on Rails, Turbogears, etc, this is actually closer to Zope 3’s views wherein it’s an object that wraps (decorates) another object.

renderContentOn: html
    html heading: item title.
    html heading: item subtitle level: 3.
    html paragraph: item description.
    html italic: item price printStringAsCents.
    html form: [
        html submitButtonWithAction: [self addToCart] text: 'Add To Cart'.
        html space.
        html submitButtonWithAction: [self answer] text: 'Done'.
    ]

Still straightforward and readable. Especially interesting are the submitButtonWithAction calls. The first argument is a block that gets executed when that button is clicked. addToCart is straightforward enough:

addToCart
    cart add: item

The page gets rendered as normal, staying on the WAStoreItemView, but each time you click ‘addToCart’ you’ll watch the cart display component fill up and fill up. [html anchorWithAction: [cart remove: anItem] text: '-'] is in that cart display to, of course, remove the item.

Back to the WAStoreItemView renderContentOn buttons, the other interesting one is the button for Done. It calls self answer. What’s it answering? A call! Specifically, this call from WAStoreFillCart. WAStoreFillCart is the component that’s responsible for browsing, searching, and displaying items. Any link to displayItem: anItem calls this:

displayItem: anItem
    main call: (WAStoreItemView new item: anItem; cart: cart)

What calls this display item, and why does it matter? Think of most web applications – there are more than a few ways to get at the data inside the application. There’s searching, there’s browsing by ‘tag’, there are dashboards (big overview pages), there are personal pages, there are direct listings. When you go from one of those listings to something more detailed, how do you go back?

In Seaside, call and answer provide that functionality, among other things. Clicking the ‘Done’ button that calls ‘self answer’ returns control back up to whatever called it. So if you found the store item on page 3 out of 5 in a batched list, you’re sent back to page 3 out of 5. If you got there through a list of links from a search result, you’re taken back to those search results. It feels natural. The developer doesn’t have to maintain information about the calling page to return back to it.

Again, looking at my own big and old Zope 2 based e-commerce application, I have a few ‘handlers’ which are thin scripts over the business logic implemented in core components. Many handlers have paths passed in about which page to report errors to, and which page to go to on success. This allows them to be used from multiple pages, within the structure of the good old scripts’n’templates system (but the scripts are really thin layers to underlying code that does validation, processing, business logic, etc). Man, wouldn’t it be nice to have that be more automatic instead of some hidden fields like ‘errorto’, ‘returnto’ with paths?

All in all, what I’ve really liked about playing around briefly with Seaside is the feeling of liberation from not only the request/response cycle, but also from the Template model. I’m suddenly very tired of templates as string insertion languages being the ‘view’ (coff!). I don’t mind using them for the broadest of layouts. But whether it’s template attribute style languages or string insertion, I’m just suddenly tired of seeing and maintaining things like this:

  <ul>
    <li tal:repeat="item view/listItems">
      <a tal:attributes="href string:${context/absolute_url}/${item/name}" 
        tal:content="item/description">Description</a>
    </li>
  </ul>

  <ul>
    <% for item in view.listItems %>
      <li><a href="./<%= item.name %>"><%= item.description %></a></li>
    <% end %>
  </ul>

My problem with them is not so much the language itself, but the casual callous way important bits like URLs and more are constructed. I’ve generally been a big fan of Zope’s TAL (Template Attribute Language, used in the first example), but sometimes it’s too hard to do simple things. Even more frustrating, lately, has been the lack of good helper functions for use in Python expressions in those templates. Zope 2 has “ZTUtils” which has a small set of useful utility and objects. But if those are in Zope 3, I haven’t found them.

I’ve generally been against the PHP style of ‘templates’ where it all becomes super muddy (how many PHP ‘apps’ do you see with print/echo statements in the code that then have HTML inside of a string?). Most of the Python PHP/ASP/JSP style systems I’ve seen have been especially bad, primarily because of Python’s block structure and lack of ‘end’ statements or closing braces inside the language, so doing the ‘for’ loop like above has varied between different implementations. It’s just not what Python is built for, at all.

My experiences with Ruby on Rails has been better. Ruby has the ‘end’ statement. And it really is a nice concise language that doesn’t look all that bad in Erb templates. But what makes Ruby on Rails especially nice is the large collection of helpers. The helpers are a big collection of functions and objects (much bigger than ZTUtils) that makes it pretty nice to work in the template. So instead of the tag-within-a-tag style shown above to generate the ‘href’ part of the link, the common idiom in rails is to use this:

  <%= link_to article.title, :action => 'show', :id => article %>
  <%= link_to 'Destroy', { :action => 'destroy', :id => article }, :confirm => 'Are you sure?' %>  

This brings it closer to one of the things I like about the Seaside experience: you’re not writing into a string to make a link, you’re linking to objects and methods directly. Not only that, you’re using other options (like shown in the second example above) to do things like common Javascript events, or to do tricks to make a regular hyperlink perform an HTTP POST request. Ultimately, they make the development experience feel more like actual application programming. For the record, there’s a fairly direct Python implementation of many of Rails helpers available too.

Some of the things I’ve done for my Zope 3 development lately has been to take ideas from these systems. I’ve started building my own helpers system, implemented as a Zope adapter for the Request. Since the Request has ‘skin’ information in it, it’s possible to extend the helper with specialized functions for a particular skin, knowing that it might have certain javascript libraries or DOM identifiers / CSS stylings available. I also have a simple HTML builder system, inspired by Nevow’s Stan, but without the more advanced features like mutation, macros, or.. well, pretty much anything advanced. Applying a python getitem trick and making an object that generates any kind of Tag object by name, I can spit out pretty much anything.

With Zope 3.2 and the introduction of viewlets and content providers, I’ve been focusing on building smaller and smaller view components. I’ve stopped thinking of pages in the standard macro + content or header + content + footer sense and have gotten back into thinking of a page as a collection of connected yet independent view objects.

  html = api.htmlHelperFor(self)
  ul = T.ul(class_='menu')
  for item in self.listItems():
      ul << T.li[ html.linkTextTo(item.name, item) ]
  return unicode(ul)

  # Or, for more fun
  html = api.htmlHelperFor(self)
  return unicode(T.ul(class_='menu')[(
      T.li[html.linkTextTo(item.name, html.viewURL(item, 'delete'), confirm='Are you sure?', post=True)]
      for item in self.listItems()
  )])

Added to that, zope.formlib delivers form components with ‘Actions’ bound intelligently to methods. zope.formlib handles dispatching to the proper action instead of just having Zope’s publisher go to a method / view directly to handle forms:

    @form.action(u"Import Articles", condition=form.haveInputFields,
                 failure='handleFailure')
    def importArticles(self, action, data):
        # This gets called when the 'import articles' button is clicked, and
        # only if validation succeeds. The button only gets rendered if there
        # are input fields - a form can be generated to display data on a field
        # by field basis according to security, which can set up 'view' 
        # situations for unprivileged users - and you wouldn't want to have
        # an input button when there are no input fields, right?
        # 'data' has all of the validated form input, and can be easily
        # applied like this:
        form.applyChanges(self.context, self.form_fields, data)

    def handleFailure(self, action, data, errors):
        # Optionally, individual actions can have other methods associated
        # with them to handle action-specific features for validation,
        # validation failure, and so on.

Being actual objects, they can be referred to in the view or view’s template and filtered out if unavailable:

  controls = T.div(class_="controls")
  for action in self.actions:
      if not action.available():
          continue
      controls << action.render()
      controls << '&nbsp;&nbsp;'

I like having tools like this available. It really makes the application feel a lot stronger. ‘formlib’ is pretty simple, conceptually, while providing a lot of flexibility and features and strength on the ‘web application as actual application’ front. That’s the nice thing with Seaside and Rails’ controller/view layer and its helpers too: a lot of tools, some simple concepts, a lot of power.