Dynamic Web Development with Seaside

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.

Getting an editor to edit new item

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.

^ 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: html
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

With an item added
Next we redefine the method add to add a new component. It first creates an instance of ToDoItemView whose model is a newly created todo item.

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:

editor := ToDoItemView new model: ToDoItem new.
editor onAnswer: [ :value |
value isNil
ifFalse: [ self model add: value ].
editor := nil ]

Note 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).

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.

Copyright © 21 April 2024 Stéphane Ducasse, Lukas Renggli, C. David Shaffer, Rick Zaccone
This book is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 license.

This book is published using Seaside, Magritte and the Pier book publishing engine.