| WAComponent
|
I am a class representing a graphical element of a seaside application. A component has state (instance variables, that might be backtracked using #registerObjectForBacktracking:), behavior decorations, children and an appearance that is specified in #renderContentOn:. A component might chose to display another component with #call:.
Child Components:
It is common for a component to display instances of other components while rendering itself. It does this by passing them into the #render: method of WAHtmlRenderer. For example, this #renderContentOn: method simply renders a heading and then displays a counter component
immediately below it:
renderContentOn: html
html heading: 'My Counter' level: 3.
html render: myCounter.
It's important that you use #render:, rather than directly calling the #renderContentOn: method of the subcomponent. The following is *not* correct:
renderContentOn: html
html heading: 'My Counter' level: 3.
myCounter renderContentOn: html. "DON'T DO THIS".
These subcomponents are usually instance variables of the component that is "embedding" them. They are commonly created as part of the components #initialize method:
initialize
myCounter := WACounter new.
They may also be stored in a collection. One fairly common pattern is to keep a lazily initialized dictionary of subcomponents that match a collection of model items. For example, if you wanted a BudgetItemRow subcomponent for each member of budgetItems, you might do something like this:
initialize
budgetRows := Dictionary new.
rowForItem: anItem
^budgetRows at: anItem ifAbsentPut: [ BudgetItemRow item: anItem ].
renderContentOn: html
self budgetItems
do: [ :each | html render: (self rowForItem: each) ]
separatedBy: [ html horizontalLine ].
Each parent component *must* implement a #children method that returns a collection of all of the subcomponents that it might display on the next render. For the above two examples, #children might look like this:
children
^Array with: myCounter
or this:
children
^self budgetItems collect: [ :each | self rowForItem: each ].
Call/Answer:
If a subcomponent makes a #call: to another component, that component will appear in place of the subcomponent. In the first example, if myCounter made a #call: to DateSelector, that DateSelector would appear in the context of the counter's parent, with the 'My Counter' heading
above it.
Since a subcomponent has not been #call:'d, in general #answer: is a no-op. However, the parent may attach an #onAnswer: block to the subcomponent to be notified if it sends #answer:. This allows one component to be used both from #call: and through embedding. For example:
initialize
dateSelector := WADateSelector new
onAnswer: [ :date | self dateChosen: date ].
|
| WATask
|
I am a subclass of WAComponent, specialized for defining workflow. The difference between a task and a component is the following:
Both of them are reusable, embeddable, callable pieces of user interface. A component has state (instance variables), behavior (it may change its state, and it may also choose to display other components with #call:), and appearance (it renders HTML). A Task has only the first two - it doesn't render any HTML directly, but only through the components it calls. This is useful when what you want to encapsulate/embed/call is purely a process (show this component, then this one, then this one).
The key method for WATask is #go - as soon as a task is displayed, this method will get invoked, and will presumably #call: other components.
In terms of implementation, you can think of a WATask in the following way: it is a component which renders two things:
- a link whose callback invokes the #go method
- a header that immediately redirects to the URL of that link
|