Call/Answer: Displaying another component

David Shaffer
Shaffer 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

  1. 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: We look at these dialogs and creating your own in Standard dialogs.

Exercises

  1. 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