[Seaside] new Canvas rendering

Avi Bryant avi.bryant at gmail.com
Mon Jun 20 17:18:45 CEST 2005


On 6/20/05, Jason Rogers <jacaetevha at gmail.com> wrote:
> Avi,
> 
> In a few recent posts you have made allusions to the new rendering
> style using Canvas.  Can you summarize for us what that is (what it
> does, how it differs, etc.)?

Certainly.  I've posted about it before, but I don't mind posting
again, especially since it's actually in a usable state now.

The basic problem that Canvas is trying to solve is that of
combinatorial explosion in the protocol of WAHtmlRenderer.  Consider
something like a text input.  The original, basic method for rendering
a text input was #textInputWithValue:callback:

html textInputWithValue: person name callback: [:v | person name: v]

But there's also a variation using the ...On:of: pattern:

html textInputOn: #name of: person

And a variation with a liveUpdate callback:

html textInputWithValue: person name callback: [:v | person name: v]
liveCallback: [:r :v | ...]

Now what if we want to use both of these variations at once?  Do we
also need #textInputOn:of:liveCallback:?  What about
#passwordInputOn:of:liveCallback:?   And so on.  It's relatively easy
to add new convenience methods, but it's a huge pain to combine them,
even when (as in this case) they're totally orthogonal.

The Canvas renderer is just a slight change to the API style that
makes this kind of combination much easier.  It divides what would
have been a single method on HtmlRenderer into three steps:  first,
you tell the canvas what kind of "brush" (tag, usually) you want to
use.  For example, the method #textInput will return a new instance of
WATextInputTag.  Then, you use protocol specific to that brush to
configure it: for example, you might send it some combination of
#value:, #callback:, #liveCallback:, #on:of:, etc.  You almost never
need to use temps when doing this, but can do it with cascades
instead:

html textInput value: person name; callback: [:v | person name: v]

or

html textInput on: #name of: person

or

html textInput
   on: #name of: person;
   liveCallback: [:r :n | ....]

The third step is to render any contents or children (if any) of this
tag.  You do this by passing some renderable (often a block) to the
method #with:.  A text input wouldn't have any, but consider something
like a table:

html tableRow rowSpan: 3; with:
     [html tableData with: [html bold with: person name].
     html tableData colSpan: 2; with: [....]]

Note that instead of using #attributeAt:put: before the element, as in
HtmlRenderer, attributes are set as part of the configuration step -
and so classes like WATableRow can implement convenience methods for
common attributes like #rowSpan: as needed.

If no configuration is needed, you can combine the first and third
steps by using a keyword message instead of a unary message to specify
the brush type, which is more compact:

html tableRow rowSpan: 3 with:
    [html tableData: [html bold: person name].
    html tableData ....]

This style of API still allows streaming - the HTML for the open tag
is generated as soon as #with: is sent.  If #with: is never sent, it's
triggered automatically (with an empty block) whenever the next tag is
started.  That means #with: does have to be the last thing you send,
any configuration done afterwards will have no effect.

Sometimes a temp is handy:

myDiv := html div.
self useSpecialClass ifTrue: [myDiv class: 'specialClass'].
myDiv with: [html text: ....]

But bear in mind that these are basically temporary objects - it won't
do you any good to stash them in an ivar and try to use them later,
for example.

The Canvas implementation in recent 2.6a versions is relatively
complete (we're using it on one of the projects I'm working on), but
there are no doubt still lots of holes to patch up and conveniences to
add, so feel free to pitch in.

Does that help?

Avi


More information about the Seaside mailing list