15.8Embedding Child Components
So far, we have seen how a component displays itself and how a component can invoke another one. This component invocation has behaved like a modal interface in which you can interact only with one dialog at a time. Now, we will demonstrate the real power of Seaside: creating an application by simply plugging together components which may have been independently developed. How do we get several components to display on the same page? By simply having a component identify its subcomponents. This is done by implementing the children
method.
Suppose that we would like to add an item to our list. Normally a web application developer would use a single form which would be used both to edit and to add a todo item, but for demonstration purposes we take a different approach. We would like to display the editor below the list. That is, we want to embed a ToDoItemView
in a ToDoListView
. Our solution is to allow the user to add an item by pressing a button which will display an editor for the new item, as seen in Figure 105.
We begin by adding an instance variable named editor
to the ToDoListView
class as follows:
WAComponent subclass: #ToDoListView
instanceVariableNames: 'editor'
classVariableNames: ''
poolDictionaries: ''
category: 'ToDo-View'
Then, we define the method children
that returns an array containing all the subcomponents of our component. This array contains just the element editor
since list items are rendered by the list component itself. Note that Seaside automatically ignores component children that are nil, so we don’t have to worry if it is not initialized.
ToDoListView>>children
^ Array with: editor
We modify renderContentOn:
to add an Add button and to trigger the add action. Note that when the value of the instance variable editor
is nil the rendering does not show anything.
ToDoListView>>renderContentOn: htmlNext we redefine the method
html heading: self model title.
html form: [
html unorderedList: [ self renderItemsOn: html ].
html submitButton
text: 'Save'.
html submitButton
callback: [ self add ];
text: 'Add' ].
html render: editor
add
to add a new component. It first creates an instance of ToDoItemView
whose model is a newly created todo item.ToDoListView>>add
editor := ToDoItemView new model: ToDoItem new
Notification of answer:
messages. How do we update the todo list model? Suppose the user cancels the editing. How do we handle that situation? We need a way to know when a subcomponent executed the method. You can get notified of answer:
execution by using the method onAnswer:
. Using onAnswer:
involves attaching a handler from the parent once the child component is instantiated. The method onAnswer:
requires a block whose argument represents the object that got answered (parent onAnswer: [ :object | ... ]
).
The onAnswer:
block will be executed with the answered object as its argument. Since the editor will return nil
when the user cancels editing, we need to check the value passed in. We modify the add
method as follows:
ToDoListView>>addNote that the Save button is different from the Add button since the Save button (so far) does nothing but submit the form. In the AJAX chapter, we will see that this situation can be avoided altogether (see Part V).
editor := ToDoItemView new model: ToDoItem new.
editor onAnswer: [ :value |
value isNil
ifFalse: [ self model add: value ].
editor := nil ]
If you get the error "Children not found while processing callbacks", check that the children
method returns all the direct subcomponents. The halos are another good tool for understanding the nesting and structure of components. We suggest you turn on the halos while developing your applications, as seen in Figure 107.