Seaside design document
Last updated at 12:11 am UTC on 3 November 2005
(Note: this is ancient history... current versions of Seaside are substantially different)
See also a presentation by Lukas Renggli http://renggli.freezope.org/personal/work/
This is taken from http://beta4.com/seaside/design.html. Feel free to mark up this copy ;-).
A Seaside application is made up of one or usually more subclasses of IAComponent. Each of these maintains the state and behavior for a single page or fragment of a page, and has an associated template for its display.
Templates are specified as html; the simplest way to associate a template with an IAComponent is to override the #html method to return a string containing html source. This is parsed and built into a tree of html elements.
Only elements marked in the html as dynamic are included in the tree; everything else is treated as text. Elements are considered dynamic if they include the sea:id attribute. The value of this attribute is that element's name. For historical reasons, oid is an accepted alias for sea:id. For clean interoperation with WYSIWYG editors, any attribute whose value begins with the character @ is also treated as a sea:id.
Dynamic elements are represented in the tree by subclasses of IAElement. The particular subclass can be specified explicitly in the html as the sea:class attribute; however, in almost all cases the proper class is inferred by the parser. For example, an <a sea:id="foo"> tag will be represented by an IAAnchor element, <form sea:id="foo"> will be represented by IAForm, and <input type="checkbox" sea:id="foo"> will be represented by IACheckbox.
Most IAElement types are directly equivalents of html tag types. Some
additional element types such as IARepeat and IAConditional are provided for simple control flow.
The template's element graph is connected to the IAComponent through a set of "bindings". These are configurable channels through which individual template elements can communicate with the component: get and set its instance variables, invoke its methods, and access literal values, as well as converting between internal and external formats. Each element class has a set of named bindings which it accepts: for example, IATextInput takes a #value binding; this is an instance variable that it keeps synchronized with the value of its input. IAAnchor takes an #action binding; this is a block or method which it invokes when its link is clicked on.
Bindings can be specified explicitly in the #addBindingsTo: method
of IAComponent. A simple example might be
(template elementNamed: 'lastNameInput')
bind: #value toPath: 'last'.
(template elementNamed: 'addLink')
set: #action toMethod: #addItem:.
lastNameInput and addLink here refer to the sea:id's of two respectively. #bind: creates a two-way link between the component and the template element: lastNameInput can both get and set the instance variable in the component named "last". This is a "path" binding because it can have multiple segments - if "last" was not directly contained in the component, but was instead a member of a Person object stored in the "person" instance variable of the component, the binding
bind: #value toPath: 'person.last'
would allow the element to get and set the value of "last" for the
the value of "person" for the component. #set: creates a one-way link: #set:toPath: allows an element to get the value of an instance variable but not to set it. Methods are always set, not bound, because they can only be invoked not changed.
Each element has a set of default bindings that it creates based on its name. This eliminates the need for an explicit addBindingsTo: method in many cases. For example, an input named 'message' will automatically create the binding
(template elementNamed: 'message')
bind: #value toPath: 'message'
and an anchor or form named 'remove:' will automatically create the binding
(template elementNamed: 'remove:')
set: #action toMethod: #remove:.
Sometimes the rules for default bindings are more complex. For example, an IARepeat element is created whenever an element has a sea:id of the form "item/list". An IARepeat named "message/log" would create default bindings equivalent to:
(template elementNamed: 'message/log')
set: #iterator to: #message;
set: #list toPath: 'log'.
When a page is rendered to html, the current values for display will be pulled from the bindings. A text input, for example, will be rendered containing the current value of its #value binding. Elements inside an IARepeat may be rendered multiple times, and the value of their bindings may change as they are repeated. This is due to the introduction by IARepeat elements of template-local variables. For example, a repeat created by the tag <tr sea:id="message/log"> will iterate over the collection found at that path "log", and each time set a local variable called #message to the current item. All path lookups check for local variables first, so a path binding "message.time" would evaluate to the value of "time" for the current item in the iteration each time.
When a form is submitted, the values from the submit are pushed back through the bindings. In the case of a repeated element, each copy of that element will push its submitted value through separately, in the same template-local context in which it was rendered. Thus a form, for example, that showed a text input for the last name of each person in a list will correctly update the right person's name with the right value upon submission.
Each form, submit button, or link has an associated #action binding. Once all form submissions have been pushed through, the block or usually method bound to whichever link or form was clicked is invoked. These are known as "action methods", and may modify the component's state, commit data to a database, or whatever else is necessary. If the action method returns normally, the same component will be redisplayed with updated state. The method may also, after performing whatever actions it needs for the current page, choose to jump to another page. IAComponent defines the #jumpToPage: method. Typical use of this is to create a new instance of another component class, initialize it with some relevant data, and perform a jump to it. #jumpToPage: does not return; instead, the component passed to it is immediately displayed. An example might look like
shoppingDone := true.
self jumpToPage: (CheckoutPage new cart: shoppingCart).
In cases where a return value is desired, the #callPage: method may be used instead. This is identical to #jumpToPage:, except that if at a later point an action method of the component jumped to calls #return:, #callPage: will return with the value passed to #return:, and the action method from which #callPage: was sent will continue from that point. Go back and read that sentence again. This permits complicated logic for page flow to be concisely expressed in one place. For example, a more complex checkout method might look like
email := self callPage: GetEmailPage new.
customer := customers at: email
creditCardInfo := self callPage:
whileFalse: [creditCardInfo := self callPage:
Often, however, the #callPage: calls will be put into separate methods so that #checkout would be the more readable
email := self getEmail.
customer := customers
ifAbsentPut: [self signUpCustomerWithEmail: email].
creditCardInfo := self getCreditInfoForCustomer: customer.
whileFalse: [creditCardInfo := self invalidCreditInfo].
Action methods can take either zero arguments, like the #checkout example above, or a single argument. Action methods that take a single argument will be passed the current value of the top of the template-locals stack. Thus if an action is invoked from an element inside a repeat, the action method will be passed the current
item in that repeat. This allows for natural idioms such as:
where an <li> with an associated (remove) link will be generated for each item in the "log" array. When a particular remove link is clicked, #removeLogItem: will be called with the correct value of #message.
On a side note, the "[message]" in the text is a shortcut for <span sea:id="message" sea:class="IAString">, and has a default binding of
(template elementNamed: 'message')
set: #value toPath: 'message'.
This is used to embed the value of a local or instance variable into the html.
This document does not yet discuss
- embedding subcomponents
- session management