Call/Answer: Displaying another component
David ShafferShaffer Consulting
Basics
How do we change the component we're displaying? So far we have two
components: HelloWorldComponent and
PersonalInformationView which are both roots of
applications (they can be accessed directly). Normally in an
application there is a single root component, the entry point, and
other components are displayed as the result of user actions. How can
we make a link in the first component display the second component?
We accomplish this using the call: method (make the
following changes to HelloWorldComponent):
renderContentOn: html
html heading: 'Hello world' level: 1.
html anchorWithAction: [self editPersonalInformation] text: 'Edit personal information'
editPersonalInformation
self call: PersonalInformationView new.
And then modify the PersonalInformationView so that when
the user presses save they get sent back to the caller (the
HelloWorldComponent):
save
self answer
Notice that the method editPersonalInformationView
creates a new instance of PersonalInformationView and
then "calls" it. When you call a component, you're giving
up control to that component. When that component is done (in this
case the user pressed "save") it should send
answer to return control to the caller. Try browsing
this application now and follow the link. Fill out the resulting form
and hit "save". Notice that you're back to the "hello
world" component. So, you call another component
and when it is done it should answer giving up control of
the display to the caller. Think of the call/answer pair as the
Seaside component equivalent of raising and closing a modal dialog
respectively.1
Sequencing
As I just indicated, calling is a modal interaction. That is the
method call: doesn't return until the component it called
answers. That allows us to do things like this (in
HelloWorldComponent):
editPersonalInformation
| v |
v := PersonalInformationView new.
self call: v.
self inform: 'Hello ' , v name
Here we call the view and then, after the view answers we display a
message. Now, here's something to wrap your brain around...what if
the user fills in the form, presses save, then hits back and changes
the values in the form and saves again? After the first save your
method above is currently calling inform: but when the
user presses back your method backsup into the call: of
PersonalInformationView. Basically Seaside snapshots the
state of execution of your method so that it can "back up" in response
to the back button. We'll go into much more detail about this later
but for now just try it and confirm that things work exactly as you'd
expect.
Returning a value to the caller
There is a version of the answer method which takes an
argument. This version returns a value to the caller. One common use
of this is to return a boolean to indicate if the user canceled or
completed the operation. Since we don't have a cancel button in our
PersonalInformationView let's add one and answer
appropriately. First add the following line to the end of the form:
html spanClass: 'button' with: [html submitButtonWithAction: [self cancel] text: 'Cancel']
And make the following change and addition:
save
self answer: true
cancel
self answer: false
Now we have to change HelloWorldComponent to use this
value:
editPersonalInformation
| v |
v := PersonalInformationView new.
(self call: v) ifFalse: [^nil].
self inform: 'Hello ' , v name
Exercises
- Something with call/answer
A Look at the Built-in dialogs
Now its time to look at the source code to the inform:
method. Here it is from WAComponent (don't enter
this code!!!!!):
inform: aString
self call: (WAFormDialog new addMessage: aString)
inform: uses call: to raise a
WAFormDialog. What other methods are there like this?
Looking through WAComponent reveals:
confirm: -- display a message and "Yes"
and "No" buttons. Returns true if user selected
"Yes", false otherwise.
request:, request:default,
request:label:, request:label:default: --
display a message, an optional label and an input box. The string
entered into the input box is returned. If the
default: argument is specified it is used for the
initial contents of the input box.
We look at these dialogs and creating your own in Standard dialogs.
Exercises
- Something with
request: since we'll need it later
Don't call: from renderContentOn:
One of the most common mistakes of first-time Seaside users is to try
to call: a component from another components rendering
method, renderContentOn:. The rendering method is just
for that, rendering. It should display the state of the current
component just as it is. It is the job of callbacks to change state,
call other components etc. There is never a reason for a typical
Seaside user to invoke call: during rendering.
If you want to render one component inside another one read the
chapter on Embedding components.
Footnotes
1.
One important point I want to make goes back to the notion of
"root component". Seaside manages a list of
"Applications" each of which has a base URL and a root
component. When we send a component class the message
registerAsApplication: it registers itself as the root
component of a new application. You do not need to register all of
your components as roots of applications. The fact that
PersonalInformationView was the root of an application
has no effect on the example above. In fact, we can remove this
application by evaluating:
WADispatcher default entryPoints removeKey: 'personal'
but our call: above still works. That is,
call: displays a component, not a different application.
Keep this distinction between component and application in mind.
C. David Shaffer
Last modified: Sat Jul 9 02:06:56 EDT 2005