[Seaside] 2.5 docs part I: the render tree

Avi Bryant avi at beta4.com
Wed Apr 21 07:28:53 CEST 2004


I promised more detail on the changes in Seaside 2.5; here's a first 
installment.  This may, in fact, be too much detail - it'll be hard 
slogging for casual users.  If you find any of it too obtuse ("but how 
do we *use* this stuff?"), just let me know.  But I wanted to at least 
get some info out there.

Since we're still in alpha, this is all subject to change, yada, yada.

Note: ASCII diagrams included, so best viewed in a monospace font...

Avi
-------

The current state of the UI of a Seaside application is represented by 
a tree of WAPresenter instances.  The root of this tree is held onto by 
the current WARenderLoop.  The render loop spins in a tight loop, 
iterating once for each response/request pair.  Each iteration involves 
walking the tree several times, for these purposes:

1. Updating the current URL.  Any presenter in the tree may wish to 
append extra information to the current URL, either to make bookmarks 
more meaningful when revisted in the future (once the current session 
state has disappeared), or to affect behavior in the present (eg, by 
adding fragments to the URL that refer to named anchors in the current 
document).

In this pass, each presenter will be sent #updateUrl:, and passed an 
instance of WAUrl.  Most presenters will not take advantage of this, 
which is fine, and so the default implementation is empty.

There was no direct equivalent to this pass in Seaside 2.3, although 
WASession>>addToPath: was used for more or less the same purpose.

2. Rendering content to the response.  The actual message sent to each 
item in the tree is #renderWithContext:, which is passed a 
WARenderingContext, but this is a fairly low level method, and probably 
won't need to be overridden.  In its default implementation, it creates 
a new renderer around the context (usually WAHtmlRenderer, but 
overridable via #rendererClass), and sends #renderAllOn: passing the 
renderer as an argument.  This, in turn, sends #renderContentOn:, which 
is the method that most presenters will override, and which should be 
familiar to users of Seaside 2.3.

3. Processing the request.  This is actually two passes, 
#processRequestInputs:context: and #processRequestActions:context:.  
Each gets passed in a WARequest and a WARenderingContext as arguments.  
By default, each presenter extracts from the rendering context any of 
the callbacks that it caused to be registered during #renderContentOn:, 
and then invokes any that appear in the request.  
#processRequestInputs:context: only invokes value callbacks (for form 
inputs, etc), whereas #processRequestActions:context: invokes action 
callbacks, from links and submit buttons.  This ensures that all of the 
values are set before any actions occur.

In Seaside 2.3, all of the callbacks were processed at once, rather 
than by the individual items in the tree.  This change makes it 
possible for items in the tree to control the request processing of 
items below themselves, for example by wrapping a special exception 
handler around them (useful for validation, eg), or by preventing them 
from processing the request at all (useful for authentication and 
similar).  To easily enable this kind of thing, both request processing 
passes go through #processRequest:do:, which takes the WARequest as its 
first argument and the block as its second.  The default implementation 
simply evaluates the block, which then continues the walk down the 
tree; overriders may put conditional evaluation around evaluating it 
based on the request, or evaluate it inside an exception handler, etc.  
See WATransaction or WABasicAuthentication for examples.

The tree is a somewhat unusual "shape".  The skeleton is made up of 
instances of WAComponent, a subclass of WAPresenter.  Each of these 
must implement the #children method, which returns a (possibly empty) 
collection of its subcomponents.  In the simplest case, walking the 
tree is simply a depth first traversal: from the root component, 
through its children, recursively to their children, and so on.

However, this basic case can be extended in two ways.  The first is by 
"decorating" components.  Each WAComponent can have any number of 
instances of WADecoration (another subclass of WAPresenter) attached to 
it.  When walking the tree, all of a component's decorations are 
visited before it is.  This means that, for the purpose of tree 
traversal, a component is "below" its decorations: they can render 
content that encloses it, or affect its request processing.
At the implementation level, a WAComponent has a (possibly unused) 
pointer to its first (outermost) decoration.  Decorations have a "next" 
pointer to the next decoration or, for the last/innermost decoration, 
to the component itself.

So a simple tree might look like this:

                x ------> y ----> z --
                |  next      next    |
     decoration |                    | next
                |                    |
root --------> A <-------------------
   |   child    |
   |            | child
   |            C
   B

The root has two children, A and B.  A has one child, C, and B has 
none.  A also has three decorations, x, y, and z.  The traversal will 
be in this order: (root, (x, y, z, A, (C)), (B)).  The most important 
thing to note about this is that A's decorations get visited *before* A 
itself does.  Also note that, as indicated by the parentheses, A and C 
are both "controlled" by the x-y-z decorations, whereas the root and B 
are not.

The second way the tree is extended is a special case of the first, 
which is through delegation.  When a component is sent #call: with 
another component, it adds a special WADelegation decoration with the 
new component referenced as the "delegate".  WADelegation is unusual in 
that it adjusts the tree traversal to follow the "delegate" pointer 
instead of the "next" pointer.  This means that, for as long as that 
decoration is present, the component being decorated and all of its 
children will be obscured by the delegate - they won't appear in any 
tree traversals.  When the delegate is sent #answer:, the Delegation 
decoration is removed and the original component becomes visible again.

So, adding a delegation "d" with a component "D" to the above tree, we 
would get:

                                        delegate
                x ------> y -------> d ----------> D
                |  next       next   |
                |                    | next
                |                    |
     decoration |                    z
                |                    | next
root --------> A <-------------------
   |   child    |
   |            | child
   |            C
   B

The traversal order is now (root, (x, y, d, D), (B)).  Nothing "under" 
d (including the decoration z and the components A and C) will get 
rendered.  However, x and y, which are before d in the decoration list, 
do get visited (and have control over D and its children).

Why are x and y before d, and z after?  It's up to the individual 
decoration.  Some decorations (usually those that affect request 
processing, rather than those that render content) will wish to stay 
active during delegation, and some will not.  Those that wish to come 
before delegations in the chain should implement #isGlobal to return 
true.



More information about the Seaside mailing list