The changes to your code in this section are presented purely to help you explore the embedding of components: they are not an example of good UI design, and are not required to progress with the following sections.
Let’s define a component that manages our list of contacts using components all the way down. Figure 87 shows that we will build our application out of two components:
ContactView. In addition,
PluggableContactListView will be composed of several
ReadOnlyOneLinerContactView components. We really get a tree of components. This exercise shows that a component should be designed to be pluggable. It also shows how to plug components together.
A Minimal Contact Viewer. We would like to have a compact contact viewer. First we will subclass the
ContactView class to create the class
ReadOnlyOneLinerContactView. This class has an instance variable
parent which will hold a reference to the component that will contain it, since it should know how to invoke the contact editor.
ContactView subclass: #ReadOnlyOneLinerContactView
parent := aParent
When the user clicks on the contact name, we want the associated user object to pass itself to the parent for editing. A similar action should occur when removing a contact from the database. Note that this component does not include a form. This is because only one form should be present on a page at any time, so a component is much more reusable if it does not define a form.
callback: [parent editContact: self contact];
with: self contact name.
html text: self contact emailAddress.
callback: [parent removeContact: self contact];
Class Definition. Now we define the class
PluggableContactListView. Since this component will embed all the contact viewer components, we add an instance variable
contactViewers, that will refer to them. We also define an instance variable to refer to the editor that will show the detailed information of the currently selected contact.
ContactListView subclass: #PluggableContactListView
instanceVariableNames: 'contactViewers editor'
We will use an identity dictionary to keep track of the contact viewer associated with each contact. Initializing our top level component consists of first creating the editor and then creating the viewers for the existing contacts.
^ Contact contacts
editor := ContactView new.
contactViewers := IdentityDictionary new.
self contacts do: [ :each | self addContactViewerFor: each ]
put: (ReadOnlyOneLinerContactView new
parent: self; " <-- added "
| name emailAddress |
name := self request: 'Name'.
emailAddress := self request: 'Email address'.
self addContact: (Contact name: name emailAddress: emailAddress)
editor := anEditor
Children accessing and rendering. We have now to specify that the contact viewers are embedded within the
PluggableContactListView and how to render them.
^ contactViewers values
do: [ :each | html render: each. html break ]
We define a couple of methods to manage contacts.
Contact addContact: aContact.
self addContactViewerFor: aContact
contactViewers removeKey: aContact.
Contact removeContact: aContact
Plugging everything together. Now we are ready to define a new version of
IAddress. We simply subclass
IAddress and pay attention to the fact that the list is now a component. So we initialize it, add it as part of the children of the component and render it.
IAddress subclass: #IAddressTwoComponents
We pass the list component to the editor which has already been initialized in
IAddress. We must also invoke the list’s
askAndCreateContact method, since it is the list that manages the creation of contacts. The rendering of the component includes a form in which the other components are embedded.
list := PluggableContactListView new.
list editor: editor " <-- added "
^ super children , (Array with: list)
callback: [ list askAndCreateContact ];
with: 'Add contact'
html form: [
self renderTitleOn: html.
self renderBarOn: html.
html render: list.
html render: editor ]
Note of course that embedding such an editor under the list of contacts is not a really good UI design. We just use it as a pretext to illustrate component embedding.